aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoremersion <contact@emersion.fr>2019-03-07 17:51:43 +0100
committerDrew DeVault <sir@cmpwn.com>2019-04-11 09:19:57 -0600
commit3a233b3fccee189d77878c66cdc0961951b53739 (patch)
treebc648a47e3686e1a908c1739188dc2c6b9223739
parentb6d0de177adaeeb0f5990613b52d50520bc15301 (diff)
Add support for wlr-output-management-unstable-v1
-rw-r--r--include/wlr/types/meson.build1
-rw-r--r--include/wlr/types/wlr_output_management_v1.h66
-rw-r--r--protocol/meson.build1
-rw-r--r--protocol/wlr-output-management-unstable-v1.xml444
-rw-r--r--types/meson.build1
-rw-r--r--types/wlr_output_management_v1.c415
6 files changed, 928 insertions, 0 deletions
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 <wayland-server.h>
+#include <wlr/types/wlr_output.h>
+
+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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="wlr_output_management_unstable_v1">
+ <copyright>
+ 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.
+ </copyright>
+
+ <description summary="protocol to configure output devices">
+ 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.
+ </description>
+
+ <interface name="zwlr_output_manager_v1" version="1">
+ <description summary="output device configuration manager">
+ 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.
+ </description>
+
+ <event name="head">
+ <description summary="introduce a new head">
+ This event introduces a new head.
+ </description>
+ <arg name="head" type="new_id" interface="zwlr_output_head_v1"/>
+ </event>
+
+ <event name="done">
+ <description summary="sent all information about current configuration">
+ 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.
+ </description>
+ <arg name="serial" type="uint" summary="current configuration serial"/>
+ </event>
+
+ <request name="create_configuration">
+ <description summary="create a new output configuration object">
+ Create a new output configuration object. This allows to update head
+ properties.
+ </description>
+ <arg name="id" type="new_id" interface="zwlr_output_configuration_v1"/>
+ <arg name="serial" type="uint"/>
+ </request>
+
+ <request name="stop">
+ <description summary="stop sending events">
+ 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.
+ </description>
+ </request>
+
+ <event name="finished">
+ <description summary="the compositor has finished with the manager">
+ 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.
+ </description>
+ </event>
+ </interface>
+
+ <interface name="zwlr_output_head_v1" version="1">
+ <description summary="output device">
+ 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.
+ </description>
+
+ <event name="mode">
+ <description summary="advertise a supported mode">
+ If the head supports modes, this event is sent once per supported mode.
+ </description>
+ <arg name="mode" type="new_id" interface="zwlr_output_mode_v1"/>
+ </event>
+
+ <event name="name">
+ <description summary="head name">
+ 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.
+ </description>
+ <arg name="name" type="string"/>
+ </event>
+
+ <event name="description">
+ <description summary="head description">
+ 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.
+ </description>
+ <arg name="description" type="string"/>
+ </event>
+
+ <event name="physical_size">
+ <description summary="head physical size">
+ 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).
+ </description>
+ <arg name="width" type="int" summary="width in millimeters of the output"/>
+ <arg name="height" type="int" summary="height in millimeters of the output"/>
+ </event>
+
+ <event name="enabled">
+ <description summary="head is enabled or disabled">
+ 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.
+ </description>
+ <arg name="enabled" type="int" summary="zero if disabled, non-zero if enabled"/>
+ </event>
+
+ <event name="current_mode">
+ <description summary="current mode">
+ This event describes the mode currently in use for this head. It is only
+ sent if the output is enabled and supports modes.
+ </description>
+ <arg name="mode" type="object" interface="zwlr_output_mode_v1"/>
+ </event>
+
+ <event name="position">
+ <description summary="current position">
+ This events describes the position of the head in the global compositor
+ space. It is only sent if the output is enabled.
+ </description>
+ <arg name="x" type="int"
+ summary="x position within the global compositor space"/>
+ <arg name="y" type="int"
+ summary="y position within the global compositor space"/>
+ </event>
+
+ <event name="transform">
+ <description summary="current transformation">
+ This event describes the transformation currently applied to the head.
+ It is only sent if the output is enabled.
+ </description>
+ <arg name="transform" type="int" enum="wl_output.transform"/>
+ </event>
+
+ <event name="scale">
+ <description summary="current scale">
+ This events describes the scale of the head in the global compositor
+ space. It is only sent if the output is enabled.
+ </description>
+ <arg name="scale" type="fixed"/>
+ </event>
+
+ <event name="finished">
+ <description summary="the head has been destroyed">
+ 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.
+ </description>
+ </event>
+ </interface>
+
+ <interface name="zwlr_output_mode_v1" version="1">
+ <description summary="output mode">
+ This object describes an output mode.
+
+ Some heads don't support output modes, in which case modes won't be
+ advertised.
+ </description>
+
+ <event name="size">
+ <description summary="mode size">
+ 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.
+ </description>
+ <arg name="width" type="int" summary="width of the mode in hardware units"/>
+ <arg name="height" type="int" summary="height of the mode in hardware units"/>
+ </event>
+
+ <event name="refresh">
+ <description summary="mode refresh rate">
+ This event describes the mode's fixed vertical refresh rate, if any.
+ </description>
+ <arg name="refresh" type="int" summary="vertical refresh rate in mHz"/>
+ </event>
+
+ <event name="preferred">
+ <description summary="mode is preferred">
+ This event advertises this mode as preferred.
+ </description>
+ </event>
+
+ <event name="finished">
+ <description summary="the mode has been destroyed">
+ 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.
+ </description>
+ </event>
+ </interface>
+
+ <interface name="zwlr_output_configuration_v1" version="1">
+ <description summary="output configuration">
+ 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.
+ </description>
+
+ <enum name="error">
+ <entry name="already_configured_head" value="1"
+ summary="head has been configured twice"/>
+ <entry name="unconfigured_head" value="2"
+ summary="head has not been configured"/>
+ <entry name="already_used" value="3"
+ summary="request sent after configuration has been applied or tested"/>
+ </enum>
+
+ <request name="enable_head">
+ <description summary="enable and configure a head">
+ Enable a head. This request creates a head configuration object that can
+ be used to change the head's properties.
+ </description>
+ <arg name="id" type="new_id" interface="zwlr_output_configuration_head_v1"
+ summary="a new object to configure the head"/>
+ <arg name="head" type="object" interface="zwlr_output_head_v1"
+ summary="the head to be enabled"/>
+ </request>
+
+ <request name="disable_head">
+ <description summary="disable a head">
+ Disable a head. The head's properties are irrelevant in this case.
+ </description>
+ <arg name="head" type="object" interface="zwlr_output_head_v1"
+ summary="the head to be disabled"/>
+ </request>
+
+ <request name="apply">
+ <description summary="apply the configuration">
+ 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.
+ </description>
+ </request>
+
+ <request name="test">
+ <description summary="test the configuration">
+ 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.
+ </description>
+ </request>
+
+ <event name="succeeded">
+ <description summary="configuration changes succeeded">
+ Sent after the compositor has successfully applied the changes or
+ tested them.
+
+ Upon receiving this event, the client should destroy this object.
+ </description>
+ </event>
+
+ <event name="failed">
+ <description summary="configuration changes failed">
+ 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.
+ </description>
+ </event>
+
+ <event name="cancelled">
+ <description summary="configuration has been cancelled">
+ 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.
+ </description>
+ </event>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the output configuration">
+ 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.
+ </description>
+ </request>
+ </interface>
+
+ <interface name="zwlr_output_configuration_head_v1" version="1">
+ <description summary="head configuration">
+ This object is used by the client to update a single head's configuration.
+ </description>
+
+ <enum name="error">
+ <entry name="invalid_mode" value="1" summary="mode doesn't belong to head"/>
+ <entry name="invalid_transform" value="2" summary="transform value outside enum"/>
+ <entry name="invalid_scale" value="3" summary="scale negative or zero"/>
+ </enum>
+
+ <request name="set_mode">
+ <description summary="set the mode">
+ This request sets the head's mode.
+ </description>
+ <arg name="mode" type="object" interface="zwlr_output_mode_v1"/>
+ </request>
+
+ <request name="set_position">
+ <description summary="set the position">
+ This request sets the head's position in the global compositor space.
+ </description>
+ <arg name="x" type="int" summary="x position in the global compositor space"/>
+ <arg name="y" type="int" summary="y position in the global compositor space"/>
+ </request>
+
+ <request name="set_transform">
+ <description summary="set the transform">
+ This request sets the head's transform.
+ </description>
+ <arg name="transform" type="int" enum="wl_output.transform"/>
+ </request>
+
+ <request name="set_scale">
+ <description summary="set the scale">
+ This request sets the head's scale.
+ </description>
+ <arg name="scale" type="fixed"/>
+ </request>
+ </interface>
+</protocol>
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 <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <wlr/types/wlr_output_management_v1.h>
+#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);
+ }
+}