From 3a233b3fccee189d77878c66cdc0961951b53739 Mon Sep 17 00:00:00 2001 From: emersion Date: Thu, 7 Mar 2019 17:51:43 +0100 Subject: [PATCH] Add support for wlr-output-management-unstable-v1 --- include/wlr/types/meson.build | 1 + include/wlr/types/wlr_output_management_v1.h | 66 +++ protocol/meson.build | 1 + .../wlr-output-management-unstable-v1.xml | 444 ++++++++++++++++++ types/meson.build | 1 + types/wlr_output_management_v1.c | 415 ++++++++++++++++ 6 files changed, 928 insertions(+) create mode 100644 include/wlr/types/wlr_output_management_v1.h create mode 100644 protocol/wlr-output-management-unstable-v1.xml create mode 100644 types/wlr_output_management_v1.c diff --git a/include/wlr/types/meson.build b/include/wlr/types/meson.build index fe39db36..fe82aeb5 100644 --- a/include/wlr/types/meson.build +++ b/include/wlr/types/meson.build @@ -23,6 +23,7 @@ install_headers( 'wlr_matrix.h', 'wlr_output_damage.h', 'wlr_output_layout.h', + 'wlr_output_management_v1.h', 'wlr_output.h', 'wlr_pointer_constraints_v1.h', 'wlr_pointer_gestures_v1.h', diff --git a/include/wlr/types/wlr_output_management_v1.h b/include/wlr/types/wlr_output_management_v1.h new file mode 100644 index 00000000..7f0ad4f9 --- /dev/null +++ b/include/wlr/types/wlr_output_management_v1.h @@ -0,0 +1,66 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_OUTPUT_MANAGEMENT_V1_H +#define WLR_TYPES_WLR_OUTPUT_MANAGEMENT_V1_H + +#include +#include + +struct wlr_output_configuration_v1; + +struct wlr_output_manager_v1 { + struct wl_display *display; + struct wl_global *global; + struct wl_list resources; + + struct wlr_output_configuration_v1 *current; + + struct { + struct wl_signal destroy; + } events; + + struct wl_listener display_destroy; + + void *data; +}; + +// TODO: split this into multiple structs (state + output_head + output_configuration_head) +struct wlr_output_configuration_head_v1 { + struct wlr_output_configuration_v1 *config; + struct wlr_output *output; + struct wl_list link; + + // for current config only + struct wl_list resources; + + bool enabled; + + struct wl_listener output_destroy; +}; + +struct wlr_output_configuration_v1 { + struct wl_list heads; + uint32_t serial; +}; + +struct wlr_output_manager_v1 *wlr_output_manager_v1_create( + struct wl_display *display); +void wlr_output_manager_v1_set_configuration( + struct wlr_output_manager_v1 *manager, + struct wlr_output_configuration_v1 *config); + +struct wlr_output_configuration_v1 *wlr_output_configuration_v1_create(void); +void wlr_output_configuration_v1_destroy( + struct wlr_output_configuration_v1 *config); + +struct wlr_output_configuration_head_v1 * + wlr_output_configuration_head_v1_create( + struct wlr_output_configuration_v1 *config, struct wlr_output *output); + +#endif diff --git a/protocol/meson.build b/protocol/meson.build index 295da6c5..7cc10320 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -38,6 +38,7 @@ protocols = [ 'wlr-gamma-control-unstable-v1.xml', 'wlr-input-inhibitor-unstable-v1.xml', 'wlr-layer-shell-unstable-v1.xml', + 'wlr-output-management-unstable-v1.xml', 'wlr-screencopy-unstable-v1.xml', ] diff --git a/protocol/wlr-output-management-unstable-v1.xml b/protocol/wlr-output-management-unstable-v1.xml new file mode 100644 index 00000000..07586ca1 --- /dev/null +++ b/protocol/wlr-output-management-unstable-v1.xml @@ -0,0 +1,444 @@ + + + + Copyright © 2019 Purism SPC + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + This protocol exposes interfaces to get and change output device + configuration. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + This interface is a manager that allows reading and writing the current + output device configuration. + + Output devices that display pixels (e.g. a physical monitor or a virtual + output in a window) are represented as heads. Heads cannot be created nor + destroyed, but they can be enabled or disabled and their properties can be + changed. Each head may have one or more available modes. + + Heads are advertised when the output manager is bound, and whenever they + appear. + + Whenever the number of heads or modes changes, the done event will be + sent. It carries a serial which can be used in a create_configuration + request to change heads properties. + + The information obtained from this protocol should only be used for output + configuration purposes. This protocol is not designed to be a generic + output property advertisement protocol for regular clients. Instead, + protocols such as xdg-output should be used. + + + + + This event introduces a new head. + + + + + + + This event is sent after all information has been sent after binding to + the output manager object and after any subsequent changes. This applies + to child head and mode objects as well. In other words, this event is + sent whenever a head or mode is created or destroyed and whenever one of + their properties has been changed. + + This allows changes to the output configuration to be seen as atomic, + even if they happen via multiple events. + + A serial is sent to be used in a future create_configuration request. + + + + + + + Create a new output configuration object. This allows to update head + properties. + + + + + + + + Indicates the client no longer wishes to receive events for output + configuration changes. However the compositor may emit further events, + until the finished event is emitted. + + The client must not send any more requests after this one. + + + + + + This event indicates that the compositor is done sending manager events. + The compositor will destroy the object immediately after sending this + event, so it will become invalid and the client should release any + resources associated with it. + + + + + + + A head is an output device. The difference with wl_output is that heads + are advertized even if they are turned off. A head object only advertises + properties and cannot be used directly to change them. In order to update + some properties, one needs to create a wlr_output_configuration object. + + A head has some read-only properties: mode, name, description and + physical_size. These cannot be changed by clients. + + enabled and current_mode are physical properties. Updating them might take + some time, depending on hardware limitations. + + position, transform and scale are logical properties. They describe how + the output is mapped in the global compositor space. + + + + + If the head supports modes, this event is sent once per supported mode. + + + + + + + This event describes the head name. + + The naming convention is compositor defined, but limited to alphanumeric + characters and dashes (-). Each name is unique among all wlr_output_head + objects, but if a wlr_output_head object is destroyed the same name may + be reused later. The names will also remain consistent across sessions + with the same hardware and software configuration. + + Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc. However, do + not assume that the name is a reflection of an underlying DRM + connector, X11 connection, etc. + + If the compositor implements the xdg-output protocol and this head is + enabled, the xdg_output.name event must report the same name. + + The name event is sent after a wlr_output_head object is created. This + event is only sent once per object, and the name does not change over + the lifetime of the wlr_output_head object. + + + + + + + This event describes a human-readable description of the head. + + The description is a UTF-8 string with no convention defined for its + contents. Examples might include 'Foocorp 11" Display' or 'Virtual X11 + output via :1'. + + If the compositor implements xdg-output and this head is enabled, + the xdg_output.description must report the same description. + + The description event is sent after a wlr_output_head object is created. + This event is only sent once per object, and the description does not + change over the lifetime of the wlr_output_head object. + + + + + + + This event describes the physical size of the head. This event is only + sent if the head has a physical size (e.g. is not a projector or a + virtual device). + + + + + + + + This event describes whether the head is enabled. A disabled head is not + mapped to a region of the global compositor space. + + When a head is disabled, some properties (current_mode, position, + transform and scale) are irrelevant. + + + + + + + This event describes the mode currently in use for this head. It is only + sent if the output is enabled and supports modes. + + + + + + + This events describes the position of the head in the global compositor + space. It is only sent if the output is enabled. + + + + + + + + This event describes the transformation currently applied to the head. + It is only sent if the output is enabled. + + + + + + + This events describes the scale of the head in the global compositor + space. It is only sent if the output is enabled. + + + + + + + The compositor will destroy the object immediately after sending this + event, so it will become invalid and the client should release any + resources associated with it. + + + + + + + This object describes an output mode. + + Some heads don't support output modes, in which case modes won't be + advertised. + + + + + This event describes the mode size. The size is given in physical + hardware units of the output device. This is not necessarily the same as + the output size in the global compositor space. For instance, the output + may be scaled or transformed. + + + + + + + + This event describes the mode's fixed vertical refresh rate, if any. + + + + + + + This event advertises this mode as preferred. + + + + + + The compositor will destroy the object immediately after sending this + event, so it will become invalid and the client should release any + resources associated with it. + + + + + + + This object is used by the client to describe a full output configuration. + + First, the client needs to setup the output configuration. Each head can + be either enabled (and configured) or disabled. It is a protocol error to + send two enable_head or disable_head requests with the same head. It is a + protocol error to omit a head in a configuration. + + Then, the client can apply or test the configuration. The compositor will + then reply with a succeeded, failed or cancelled event. Finally the client + should destroy the configuration object. + + + + + + + + + + + Enable a head. This request creates a head configuration object that can + be used to change the head's properties. + + + + + + + + Disable a head. The head's properties are irrelevant in this case. + + + + + + + Apply the new output configuration. + + In case the configuration is successfully applied, there is no guarantee + that the new output state matches completely the requested + configuration. For instance, a compositor might round the scale if it + doesn't support fractional scaling. + + After this request has been sent, the compositor must respond with an + succeeded, failed or cancelled event. Sending a request that isn't the + destructor is a protocol error. + + + + + + Test the new output configuration. The configuration won't be applied, + but will only be validated. + + Even if the compositor succeeds to test a configuration, applying it may + fail. + + After this request has been sent, the compositor must respond with an + succeeded, failed or cancelled event. Sending a request that isn't the + destructor is a protocol error. + + + + + + Sent after the compositor has successfully applied the changes or + tested them. + + Upon receiving this event, the client should destroy this object. + + + + + + Sent if the compositor rejects the changes or failed to apply them. The + compositor should revert any changes made by the apply request that + triggered this event. + + Upon receiving this event, the client should destroy this object. + + + + + + Sent if the compositor cancels the configuration because the state of an + output changed and the client has outdated information (e.g. after an + output has been hotplugged). + + The client can create a new configuration with a newer serial and try + again. + + Upon receiving this event, the client should destroy this object. + + + + + + Using this request a client can tell the compositor that it is not going + to use the configuration object anymore. Any changes to the outputs + that have not been applied will be discarded. + + This request also destroys wlr_output_configuration_head objects created + via this object. + + + + + + + This object is used by the client to update a single head's configuration. + + + + + + + + + + + This request sets the head's mode. + + + + + + + This request sets the head's position in the global compositor space. + + + + + + + + This request sets the head's transform. + + + + + + + This request sets the head's scale. + + + + + diff --git a/types/meson.build b/types/meson.build index 04b90311..d507a1ca 100644 --- a/types/meson.build +++ b/types/meson.build @@ -46,6 +46,7 @@ lib_wlr_types = static_library( 'wlr_matrix.c', 'wlr_output_damage.c', 'wlr_output_layout.c', + 'wlr_output_management_v1.c', 'wlr_output.c', 'wlr_pointer_constraints_v1.c', 'wlr_pointer_gestures_v1.c', diff --git a/types/wlr_output_management_v1.c b/types/wlr_output_management_v1.c new file mode 100644 index 00000000..6c09e030 --- /dev/null +++ b/types/wlr_output_management_v1.c @@ -0,0 +1,415 @@ +#include +#include +#include +#include +#include "util/signal.h" +#include "wlr-output-management-unstable-v1-protocol.h" + +#define OUTPUT_MANAGER_VERSION 1 + +enum { + HEAD_STATE_ENABLED = 1 << 0, + // TODO: other properties +}; + +static const uint32_t HEAD_STATE_ALL = HEAD_STATE_ENABLED; + + +// Can return NULL if the head is inert +static struct wlr_output_configuration_head_v1 * + config_head_from_head_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwlr_output_head_v1_interface, NULL)); + return wl_resource_get_user_data(resource); +} + +static void config_head_destroy(struct wlr_output_configuration_head_v1 *head) { + if (head == NULL) { + return; + } + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, &head->resources) { + zwlr_output_head_v1_send_finished(resource); + wl_resource_set_user_data(resource, NULL); // make the resource inert + wl_list_remove(wl_resource_get_link(resource)); + wl_list_init(wl_resource_get_link(resource)); + } + wl_list_remove(&head->link); + wl_list_remove(&head->output_destroy.link); + free(head); +} + +static void config_head_handle_output_destroy(struct wl_listener *listener, + void *data) { + struct wlr_output_configuration_head_v1 *head = + wl_container_of(listener, head, output_destroy); + config_head_destroy(head); +} + +struct wlr_output_configuration_head_v1 * + wlr_output_configuration_head_v1_create( + struct wlr_output_configuration_v1 *config, struct wlr_output *output) { + struct wlr_output_configuration_head_v1 *head = calloc(1, sizeof(*head)); + if (head == NULL) { + return NULL; + } + head->config = config; + head->output = output; + wl_list_init(&head->resources); + wl_list_insert(&config->heads, &head->link); + head->output_destroy.notify = config_head_handle_output_destroy; + wl_signal_add(&output->events.destroy, &head->output_destroy); + return head; +} + + +static const struct zwlr_output_configuration_head_v1_interface config_head_impl; + +static struct wlr_output_configuration_head_v1 *config_head_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwlr_output_head_v1_interface, &config_head_impl)); + return wl_resource_get_user_data(resource); +} + +static const struct zwlr_output_configuration_head_v1_interface config_head_impl = { + 0 // TODO +}; + +static void config_head_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_output_configuration_head_v1 *head = + config_head_from_resource(resource); + config_head_destroy(head); +} + + +static const struct zwlr_output_configuration_v1_interface config_impl; + +static struct wlr_output_configuration_v1 *config_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwlr_output_configuration_v1_interface, &config_impl)); + return wl_resource_get_user_data(resource); +} + +static struct wlr_output_configuration_head_v1 *config_create_head( + struct wlr_output_configuration_v1 *config, struct wlr_output *output) { + struct wlr_output_configuration_head_v1 *head; + wl_list_for_each(head, &config->heads, link) { + if (head->output == output) { + return NULL; // TODO: post already_configured_head error instead + } + } + return wlr_output_configuration_head_v1_create(config, output); +} + +static void config_handle_enable_head(struct wl_client *client, + struct wl_resource *config_resource, uint32_t id, + struct wl_resource *head_resource) { + struct wlr_output_configuration_v1 *config = + config_from_resource(config_resource); + // Can be NULL if the head no longer exists + struct wlr_output_configuration_head_v1 *existing_head = + config_head_from_head_resource(head_resource); + + struct wlr_output_configuration_head_v1 *config_head = NULL; + if (existing_head != NULL) { + config_head = config_create_head(config, existing_head->output); + if (config_head == NULL) { + wl_resource_post_no_memory(config_resource); + return; + } + } + + uint32_t version = wl_resource_get_version(config_resource); + struct wl_resource *resource = wl_resource_create(client, + &zwlr_output_configuration_head_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &config_head_impl, + config_head, config_head_handle_resource_destroy); + + config_head->enabled = true; + + // TODO: when the output is destroyed, make this resource inert +} + +static void config_handle_disable_head(struct wl_client *client, + struct wl_resource *config_resource, + struct wl_resource *head_resource) { + struct wlr_output_configuration_v1 *config = + config_from_resource(config_resource); + struct wlr_output_configuration_head_v1 *existing_head = + config_head_from_head_resource(head_resource); + if (existing_head == NULL) { + return; + } + + struct wlr_output_configuration_head_v1 *config_head = + config_create_head(config, existing_head->output); + if (config_head == NULL) { + wl_resource_post_no_memory(config_resource); + return; + } + + config_head->enabled = false; +} + +static void config_handle_apply(struct wl_client *client, + struct wl_resource *config_resource) { + //struct wlr_output_configuration_v1 *config = + // config_from_resource(config_resource); + // TODO: post already_used if needed + + // TODO +} + +static void config_handle_destroy(struct wl_client *client, + struct wl_resource *config_resource) { + wl_resource_destroy(config_resource); + // TODO: destroy head configurations +} + +static const struct zwlr_output_configuration_v1_interface config_impl = { + .enable_head = config_handle_enable_head, + .disable_head = config_handle_disable_head, + .apply = config_handle_apply, + // TODO: test + .destroy = config_handle_destroy, +}; + +struct wlr_output_configuration_v1 *wlr_output_configuration_v1_create(void) { + struct wlr_output_configuration_v1 *config = calloc(1, sizeof(*config)); + if (config == NULL) { + return NULL; + } + wl_list_init(&config->heads); + return config; +} + +void wlr_output_configuration_v1_destroy( + struct wlr_output_configuration_v1 *config) { + if (config == NULL) { + return; + } + struct wlr_output_configuration_head_v1 *head, *tmp; + wl_list_for_each_safe(head, tmp, &config->heads, link) { + config_head_destroy(head); + } + free(config); +} + +static void config_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_output_configuration_v1 *config = config_from_resource(resource); + wlr_output_configuration_v1_destroy(config); +} + + +static void manager_handle_create_configuration(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id, uint32_t serial) { + struct wlr_output_configuration_v1 *config = + wlr_output_configuration_v1_create(); + config->serial = serial; + + uint32_t version = wl_resource_get_version(manager_resource); + struct wl_resource *resource = wl_resource_create(client, + &zwlr_output_configuration_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &config_impl, + config, config_handle_resource_destroy); +} + +static void manager_handle_stop(struct wl_client *client, + struct wl_resource *manager_resource) { + zwlr_output_manager_v1_send_finished(manager_resource); + wl_resource_destroy(manager_resource); +} + +static const struct zwlr_output_manager_v1_interface manager_impl = { + .create_configuration = manager_handle_create_configuration, + .stop = manager_handle_stop, +}; + +static void manager_handle_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void manager_bind(struct wl_client *client, void *data, uint32_t version, + uint32_t id) { + struct wlr_output_manager_v1 *manager = data; + + struct wl_resource *resource = wl_resource_create(client, + &zwlr_output_manager_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &manager_impl, manager, + manager_handle_resource_destroy); + + wl_list_insert(&manager->resources, wl_resource_get_link(resource)); +} + +static void manager_handle_display_destroy(struct wl_listener *listener, + void *data) { + struct wlr_output_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + wlr_signal_emit_safe(&manager->events.destroy, manager); + wl_list_remove(&manager->display_destroy.link); + wlr_output_configuration_v1_destroy(manager->current); + wl_global_destroy(manager->global); + free(manager); +} + +struct wlr_output_manager_v1 *wlr_output_manager_v1_create( + struct wl_display *display) { + struct wlr_output_manager_v1 *manager = calloc(1, sizeof(*manager)); + if (manager == NULL) { + return NULL; + } + manager->display = display; + + wl_signal_init(&manager->events.destroy); + + manager->global = wl_global_create(display, + &zwlr_output_manager_v1_interface, OUTPUT_MANAGER_VERSION, + manager, manager_bind); + if (manager->global == NULL) { + free(manager); + return NULL; + } + + manager->display_destroy.notify = manager_handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} + +static struct wlr_output_configuration_head_v1 *configuration_get_head( + struct wlr_output_configuration_v1 *config, struct wlr_output *output) { + struct wlr_output_configuration_head_v1 *head; + wl_list_for_each(head, &config->heads, link) { + if (head->output == output) { + return head; + } + } + return NULL; +} + +static void head_send_state(struct wlr_output_configuration_head_v1 *head, + struct wl_resource *resource, uint32_t state) { + if (state & HEAD_STATE_ENABLED) { + zwlr_output_head_v1_send_enabled(resource, head->enabled); + } + + if (!head->enabled) { + return; + } + + // TODO: send other properties +} + +static void head_handle_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void manager_send_head(struct wlr_output_manager_v1 *manager, + struct wlr_output_configuration_head_v1 *head, + struct wl_resource *manager_resource) { + struct wlr_output *output = head->output; + + struct wl_client *client = wl_resource_get_client(manager_resource); + uint32_t version = wl_resource_get_version(manager_resource); + struct wl_resource *resource = wl_resource_create(client, + &zwlr_output_head_v1_interface, version, 0); + if (resource == NULL) { + wl_resource_post_no_memory(manager_resource); + return; + } + wl_resource_set_implementation(resource, NULL, head, + head_handle_resource_destroy); + wl_list_insert(&head->resources, wl_resource_get_link(resource)); + + zwlr_output_manager_v1_send_head(manager_resource, resource); + + // TODO: send modes + + zwlr_output_head_v1_send_name(resource, output->name); + + char description[128]; + snprintf(description, sizeof(description), "%s %s %s (%s)", + output->make, output->model, output->serial, output->name); + zwlr_output_head_v1_send_description(resource, description); + + if (output->phys_width > 0 && output->phys_height > 0) { + zwlr_output_head_v1_send_physical_size(resource, + output->phys_width, output->phys_height); + } + + head_send_state(head, resource, HEAD_STATE_ALL); +} + +static void manager_update_head(struct wlr_output_manager_v1 *manager, + struct wlr_output_configuration_head_v1 *head, + struct wlr_output_configuration_head_v1 *next) { + uint32_t state = 0; + if (head->enabled != next->enabled) { + state |= HEAD_STATE_ENABLED; + head->enabled = next->enabled; + } + // TODO: update other properties + + struct wl_resource *resource; + wl_resource_for_each(resource, &head->resources) { + head_send_state(head, resource, state); + } +} + +void wlr_output_manager_v1_set_configuration( + struct wlr_output_manager_v1 *manager, + struct wlr_output_configuration_v1 *config) { + if (manager->current != NULL) { + // Either update or destroy existing heads + struct wlr_output_configuration_head_v1 *existing_head, *tmp; + wl_list_for_each_safe(existing_head, tmp, + &manager->current->heads, link) { + struct wlr_output_configuration_head_v1 *updated_head = + configuration_get_head(config, existing_head->output); + if (updated_head != NULL) { + manager_update_head(manager, existing_head, updated_head); + config_head_destroy(updated_head); + } else { + config_head_destroy(existing_head); + } + } + + // Heads remaining in `config` are new heads + } else { + manager->current = wlr_output_configuration_v1_create(); + } + + // Move new heads to current config + struct wlr_output_configuration_head_v1 *head, *tmp; + wl_list_for_each_safe(head, tmp, &config->heads, link) { + head->config = manager->current; + wl_list_remove(&head->link); + wl_list_insert(&manager->current->heads, &head->link); + + struct wl_resource *manager_resource; + wl_resource_for_each(manager_resource, &manager->resources) { + manager_send_head(manager, head, manager_resource); + } + } + + manager->current->serial = wl_display_next_serial(manager->display); + struct wl_resource *manager_resource; + wl_resource_for_each(manager_resource, &manager->resources) { + zwlr_output_manager_v1_send_done(manager_resource, + manager->current->serial); + } +}