diff --git a/drm-lease-v1.xml b/drm-lease-v1.xml
new file mode 100644
index 0000000..d4c960a
--- /dev/null
+++ b/drm-lease-v1.xml
@@ -0,0 +1,303 @@
+
+
+
+ Copyright © 2018 NXP
+ Copyright © 2019 Status Research & Development GmbH.
+ Copyright © 2021 Xaver Hugl
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice (including the next
+ paragraph) shall be included in all copies or substantial portions of the
+ Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+
+
+
+
+ This protocol is used by Wayland compositors which act as Direct
+ Renderering Manager (DRM) masters to lease DRM resources to Wayland
+ clients.
+
+ The compositor will advertise one wp_drm_lease_device_v1 global for each
+ DRM node. Some time after a client binds to the wp_drm_lease_device_v1
+ global, the compositor will send a drm_fd event followed by zero, one or
+ more connector events. After all currently available connectors have been
+ sent, the compositor will send a wp_drm_lease_device_v1.done event.
+
+ When the list of connectors available for lease changes the compositor
+ will send wp_drm_lease_device_v1.connector events for added connectors and
+ wp_drm_lease_connector_v1.withdrawn events for removed connectors,
+ followed by a wp_drm_lease_device_v1.done event.
+
+ The compositor will indicate when a device is gone by removing the global
+ via a wl_registry.global_remove event. Upon receiving this event, the
+ client should destroy any matching wp_drm_lease_device_v1 object.
+
+ To destroy a wp_drm_lease_device_v1 object, the client must first issue
+ a release request. Upon receiving this request, the compositor will
+ immediately send a released event and destroy the object. The client must
+ continue to process and discard drm_fd and connector events until it
+ receives the released event. Upon receiving the released event, the
+ client can safely cleanup any client-side resources.
+
+ Warning! The protocol described in this file is currently in the testing
+ phase. Backward compatible changes may be added together with the
+ corresponding interface version bump. Backward incompatible changes can
+ only be done by creating a new major version of the extension.
+
+
+
+
+ Creates a lease request object.
+
+ See the documentation for wp_drm_lease_request_v1 for details.
+
+
+
+
+
+
+ Indicates the client no longer wishes to use this object. In response
+ the compositor will immediately send the released event and destroy
+ this object. It can however not guarantee that the client won't receive
+ connector events before the released event. The client must not send any
+ requests after this one, doing so will raise a wl_display error.
+ Existing connectors, lease request and leases will not be affected.
+
+
+
+
+
+ The compositor will send this event when the wp_drm_lease_device_v1
+ global is bound, although there are no guarantees as to how long this
+ takes - the compositor might need to wait until regaining DRM master.
+ The included fd is a non-master DRM file descriptor opened for this
+ device and the compositor must not authenticate it.
+ The purpose of this event is to give the client the ability to
+ query DRM and discover information which may help them pick the
+ appropriate DRM device or select the appropriate connectors therein.
+
+
+
+
+
+
+ The compositor will use this event to advertise connectors available for
+ lease by clients. This object may be passed into a lease request to
+ indicate the client would like to lease that connector, see
+ wp_drm_lease_request_v1.request_connector for details. While the
+ compositor will make a best effort to not send disconnected connectors,
+ no guarantees can be made.
+
+ The compositor must send the drm_fd event before sending connectors.
+ After the drm_fd event it will send all available connectors but may
+ send additional connectors at any time.
+
+
+
+
+
+
+ The compositor will send this event to indicate that it has sent all
+ currently available connectors after the client binds to the global or
+ when it updates the connector list, for example on hotplug, drm master
+ change or when a leased connector becomes available again. It will
+ similarly send this event to group wp_drm_lease_connector_v1.withdrawn
+ events of connectors of this device.
+
+
+
+
+
+ This event is sent in response to the release request and indicates
+ that the compositor is done sending connector events.
+ The compositor will destroy this object immediately after sending the
+ event and it will become invalid. The client should release any
+ resources associated with this device after receiving this event.
+
+
+
+
+
+
+ Represents a DRM connector which is available for lease. These objects are
+ created via wp_drm_lease_device_v1.connector events, and should be passed
+ to lease requests via wp_drm_lease_request_v1.request_connector.
+ Immediately after the wp_drm_lease_connector_v1 object is created the
+ compositor will send a name, a description, a connector_id and a done
+ event. When the description is updated the compositor will send a
+ description event followed by a done event.
+
+
+
+
+ The compositor sends this event once the connector is created to
+ indicate the name of this connector. This will not change for the
+ duration of the Wayland session, but is not guaranteed to be consistent
+ between sessions.
+
+
+
+
+
+
+ The compositor sends this event once the connector is created to provide
+ a human-readable description for this connector, which may be presented
+ to the user. The compositor may send this event multiple times over the
+ lifetime of this object to reflect changes in the description.
+
+
+
+
+
+
+ The compositor sends this event once the connector is created to
+ indicate the DRM object ID which represents the underlying connector
+ that is being offered. Note that the final lease may include additional
+ object IDs, such as CRTCs and planes.
+
+
+
+
+
+
+ This event is sent after all properties of a connector have been sent.
+ This allows changes to the properties to be seen as atomic even if they
+ happen via multiple events.
+
+
+
+
+
+ Sent to indicate that the compositor will no longer honor requests for
+ DRM leases which include this connector. The client may still issue a
+ lease request including this connector, but the compositor will send
+ wp_drm_lease_v1.finished without issuing a lease fd. Compositors are
+ encouraged to send this event when they lose access to connector, for
+ example when the connector is hot-unplugged, when the connector gets
+ leased to a client or when the compositor loses DRM master.
+
+
+
+
+
+ The client may send this request to indicate that it will not use this
+ connector. Clients are encouraged to send this after receiving the
+ "withdrawn" event so that the server can release the resources
+ associated with this connector offer. Neither existing lease requests
+ nor leases will be affected.
+
+
+
+
+
+
+ A client that wishes to lease DRM resources will attach the list of
+ connectors advertised with wp_drm_lease_device_v1.connector that they
+ wish to lease, then use wp_drm_lease_request_v1.submit to submit the
+ request.
+
+
+
+
+
+
+
+
+
+
+ Indicates that the client would like to lease the given connector.
+ This is only used as a suggestion, the compositor may choose to
+ include any resources in the lease it issues, or change the set of
+ leased resources at any time. Compositors are however encouraged to
+ include the requested connector and other resources necessary
+ to drive the connected output in the lease.
+
+ Requesting a connector that was created from a different lease device
+ than this lease request raises the wrong_device error. Requesting a
+ connector twice will raise the duplicate_connector error.
+
+
+
+
+
+
+ Submits the lease request and creates a new wp_drm_lease_v1 object.
+ After calling submit the compositor will immediately destroy this
+ object, issuing any more requests will cause a wl_diplay error.
+ The compositor doesn't make any guarantees about the events of the
+ lease object, clients cannot expect an immediate response.
+ Not requesting any connectors before submitting the lease request
+ will raise the empty_lease error.
+
+
+
+
+
+
+
+ A DRM lease object is used to transfer the DRM file descriptor to the
+ client and manage the lifetime of the lease.
+
+ Some time after the wp_drm_lease_v1 object is created, the compositor
+ will reply with the lease request's result. If the lease request is
+ granted, the compositor will send a lease_fd event. If the lease request
+ is denied, the compositor will send a finished event without a lease_fd
+ event.
+
+
+
+
+ This event returns a file descriptor suitable for use with DRM-related
+ ioctls. The client should use drmModeGetLease to enumerate the DRM
+ objects which have been leased to them. The compositor guarantees it
+ will not use the leased DRM objects itself until it sends the finished
+ event. If the compositor cannot or will not grant a lease for the
+ requested connectors, it will not send this event, instead sending the
+ finished event.
+
+ The compositor will send this event at most once during this objects
+ lifetime.
+
+
+
+
+
+
+ The compositor uses this event to either reject a lease request, or if
+ it previously sent a lease_fd, to notify the client that the lease has
+ been revoked. If the client requires a new lease, they should destroy
+ this object and submit a new lease request. The compositor will send
+ no further events for this object after sending the finish event.
+ Compositors should revoke the lease when any of the leased resources
+ become unavailable, namely when a hot-unplug occurs or when the
+ compositor loses DRM master.
+
+
+
+
+
+ The client should send this to indicate that it no longer wishes to use
+ this lease. The compositor should use drmModeRevokeLease on the
+ appropriate file descriptor, if necessary.
+
+
+
+
diff --git a/drm_lease.c b/drm_lease.c
new file mode 100644
index 0000000..dd9812b
--- /dev/null
+++ b/drm_lease.c
@@ -0,0 +1,1693 @@
+/* Wayland compositor running on top of an X server.
+
+Copyright (C) 2022 to various contributors.
+
+This file is part of 12to11.
+
+12to11 is free software: you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation, either version 3 of the License, or (at your
+option) any later version.
+
+12to11 is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with 12to11. If not, see . */
+
+#include
+#include
+#include
+
+#include
+
+#include
+#include
+
+#include
+
+#include "compositor.h"
+#include "drm-lease-v1.h"
+
+#if defined DEBUG
+#define DebugPrint(format, args...) \
+ fprintf (stderr, "%s: " format "\n", __FUNCTION__, ## args)
+#else
+#define DebugPrint(fmt, ...) ((void) 0)
+#endif
+
+/* DRM Leasing.
+
+ Modern applications can demand direct access to the kernel
+ modesetting resources underlying an output. Wayland exposes this
+ functionality via the wp_drm_lease_device_v1 protocol.
+
+ There is some mismatch between the X server terminology and the
+ kernel speak used in Wayland and in the KMS/DRM APIs themselves.
+ Under X, DRM nodes are named "providers", and DRM connectors are
+ named "outputs". This file uses a mix of both the the X
+ terminology and the KMS/DRM terminology. */
+
+/* TODO: dynamic output updating. */
+
+typedef struct _DrmLeaseDevice DrmLeaseDevice;
+typedef struct _DrmLeaseDeviceRef DrmLeaseDeviceRef;
+typedef struct _DrmLeaseConnector DrmLeaseConnector;
+typedef struct _DrmLeaseConnectorRef DrmLeaseConnectorRef;
+typedef struct _DrmLeaseConnectorList DrmLeaseConnectorList;
+typedef struct _DrmLeaseRequest DrmLeaseRequest;
+typedef struct _DrmLease DrmLease;
+
+typedef struct _ProviderOutputTree ProviderOutputTree;
+
+enum
+ {
+ /* These flags are only used by outputs. */
+ InvalidConnectorID = 1,
+ IsDisconnected = (1 << 2),
+
+ /* These flags are shared by both providers and outputs. */
+ IsMarked = (1 << 3),
+ IsRemoved = (1 << 4),
+
+ /* These flags apply to both outputs and output references. */
+ IsWithdrawn = (1 << 5),
+ };
+
+struct _DrmLeaseConnectorRef
+{
+ /* The next and last references to this connector. */
+ DrmLeaseConnectorRef *next, *last;
+
+ /* The next and last global references to this connector. */
+ DrmLeaseConnectorRef *gcnext, *gclast;
+
+ /* The associated connector. */
+ DrmLeaseConnector *connector;
+
+ /* The resource associated with this connector. */
+ struct wl_resource *resource;
+
+ /* Flags. */
+ int flags;
+};
+
+struct _DrmLeaseConnector
+{
+ /* The output associated with this connector. */
+ RROutput output;
+
+ /* The CRTC associated with this connector. */
+ RRCrtc crtc;
+
+ /* The connector ID and some flags. */
+ int connector_id, flags;
+
+ /* The next and last outputs associated with this device. */
+ DrmLeaseConnector *next, *last;
+
+ /* References to this connector. */
+ DrmLeaseConnectorRef references;
+
+ /* The associated device. */
+ DrmLeaseDevice *device;
+
+ /* The human readable name of this output. */
+ char *name;
+};
+
+struct _DrmLeaseDeviceRef
+{
+ /* The next and last references to this provider. */
+ DrmLeaseDeviceRef *next, *last;
+
+ /* The next and last global references to this provider. */
+ DrmLeaseDeviceRef *gcnext, *gclast;
+
+ /* The referenced device. */
+ DrmLeaseDevice *device;
+
+ /* The wl_resource associated with this reference. */
+ struct wl_resource *resource;
+};
+
+struct _DrmLeaseDevice
+{
+ /* The struct wl_global associated with this provider. */
+ struct wl_global *global;
+
+ /* Any references to this provider. */
+ DrmLeaseDeviceRef references;
+
+ /* The provider associated with this provider. */
+ RRProvider provider;
+
+ /* The next and last devices in this list. */
+ DrmLeaseDevice *next, *last;
+
+ /* The file descriptor of this provider, and some flags. */
+ int fd, flags;
+
+ /* The outputs attached to this provider. */
+ DrmLeaseConnector outputs;
+};
+
+struct _DrmLeaseConnectorList
+{
+ /* The next and last connectors in this list. */
+ DrmLeaseConnectorList *next, *last;
+
+ /* The output. */
+ DrmLeaseConnector *connector;
+};
+
+struct _DrmLeaseRequest
+{
+ /* List of requested outputs. */
+ DrmLeaseConnectorList outputs;
+
+ /* The next and last lease requests. */
+ DrmLeaseRequest *gcnext, *gclast;
+
+ /* The request device. */
+ DrmLeaseDevice *device;
+
+ /* The struct wl_resource associated with this lease request. */
+ struct wl_resource *resource;
+
+ /* The number of outputs requested. */
+ int noutputs;
+};
+
+struct _DrmLease
+{
+ /* The XID of the lease. */
+ xcb_randr_lease_t lease;
+
+ /* The resource of the lease. */
+ struct wl_resource *resource;
+};
+
+struct _ProviderOutputTree
+{
+ /* List of provider IDs. */
+ xcb_randr_provider_t *providers;
+
+ /* List of outputs associated with each of the providers. */
+ xcb_randr_output_t *outputs;
+
+ /* List of output info associated with each of the providers. */
+ xcb_randr_get_output_info_reply_t **output_info;
+
+ /* Number of outputs and crtcs associated with each provider. */
+ int *nconnectors;
+
+ /* Number of providers. */
+ int nproviders;
+
+ /* When the tree data was found. */
+ Time timestamp;
+};
+
+/* List of all providers. */
+static DrmLeaseDevice all_devices;
+
+/* List of all provider references. */
+static DrmLeaseDeviceRef all_device_references;
+
+/* List of all connector references. */
+static DrmLeaseConnectorRef all_connector_references;
+
+/* List of all lease requests. */
+static DrmLeaseRequest all_lease_requests;
+
+/* The last time the provider info was updated. */
+static Time last_change_time;
+
+static void
+DeleteConnector (DrmLeaseConnector *connector)
+{
+ /* There should be no more references at this point. */
+ XLAssert (connector->references.next == &connector->references);
+
+ /* Free the name. */
+ XLFree (connector->name);
+
+ DebugPrint ("destroying connector %p (crtc %lu output %lu)",
+ connector, connector->crtc, connector->output);
+
+ /* Unlink the connector. */
+ connector->next->last = connector->last;
+ connector->last->next = connector->next;
+
+ /* Free the connector. */
+ XLFree (connector);
+ return;
+}
+
+static void
+DeleteDevice (DrmLeaseDevice *device)
+{
+ /* There should be no more connectors at this point. */
+ XLAssert (device->outputs.next == &device->outputs);
+
+ /* device->global must be gone as well. */
+ XLAssert (device->global == NULL);
+
+ DebugPrint ("destroying device %p (%lu) w/ fd %d",
+ device, device->provider, device->fd);
+
+ /* Close the fd. */
+ close (device->fd);
+}
+
+/* Connector and device "garbage collection".
+
+ Managing the complicated reference cycles between connectors
+ resources, outputs, device resources and providers is a tricky
+ business. Every time a resource is destroyed, we mark each
+ provider and output referenced from Wayland resources, and if there
+ are no more references to a dead provider or output, destroy
+ them. */
+
+static void
+CollectDeadResources (void)
+{
+ DrmLeaseDeviceRef *device_ref;
+ DrmLeaseConnectorRef *connector_ref;
+ DrmLeaseRequest *request;
+ DrmLeaseConnectorList *item;
+ DrmLeaseDevice *device, *last_device;
+ DrmLeaseConnector *connector, *last_connector;
+
+ DebugPrint ("collecting dead resources");
+
+ /* Mark all provider references. */
+ device_ref = all_device_references.gcnext;
+ while (device_ref != &all_device_references)
+ {
+ /* Mark the device referenced. */
+ device_ref->device->flags |= IsMarked;
+
+ /* Move to the next device. */
+ device_ref = device_ref->gcnext;
+ }
+
+ /* Mark all connector references. */
+ connector_ref = all_connector_references.gcnext;
+ while (connector_ref != &all_connector_references)
+ {
+ DebugPrint ("marked via connector: connector %p, device %p (%lu)",
+ connector_ref->connector,
+ connector_ref->connector->device,
+ connector_ref->connector->device->provider);
+
+ /* Mark the connector and device referenced. */
+ connector_ref->connector->flags |= IsMarked;
+ connector_ref->connector->device->flags |= IsMarked;
+
+ /* Move to the next connector reference. */
+ connector_ref = connector_ref->gcnext;
+ }
+
+ /* Mark all lease requests. */
+ request = all_lease_requests.gcnext;
+ while (request != &all_lease_requests)
+ {
+ /* Mark each referenced connector. */
+ item = request->outputs.next;
+ while (item != &request->outputs)
+ {
+ DebugPrint ("marked via req: connector %p, device %p (%lu)",
+ item->connector, item->connector->device,
+ item->connector->device->provider);
+
+ item->connector->flags |= IsMarked;
+ item->connector->device->flags |= IsMarked;
+
+ item = item->next;
+ }
+
+ /* Move to the next lease request. */
+ request = request->gcnext;
+ }
+
+ /* Now, judge each device's connectors and then the device
+ itself. */
+ device = all_devices.next;
+ while (device != &all_devices)
+ {
+ DebugPrint ("judging device %p", device);
+
+ /* Do the connectors first. If the device is not marked, then
+ there should be no marked connectors at all, but if it is,
+ then any dead connectors must be removed. */
+ connector = device->outputs.next;
+ while (connector != &device->outputs)
+ {
+ DebugPrint ("judging connector %p of device %p", connector,
+ connector->device);
+
+ XLAssert (connector->device == device);
+
+ if (!(device->flags & IsMarked))
+ XLAssert (!(connector->flags & IsMarked));
+
+ last_connector = connector;
+ connector = connector->next;
+
+ /* If the connector is no longer marked and also dead,
+ remove it. */
+ if (!(last_connector->flags & IsMarked))
+ {
+ DebugPrint ("connector %lu %lu is no longer marked",
+ last_connector->output, last_connector->crtc);
+
+ if (last_connector->flags & IsRemoved)
+ /* Delete the connector. */
+ DeleteConnector (last_connector);
+ else
+ /* The connector is still alive. */
+ DebugPrint ("not removing live connector");
+ }
+ else
+ /* Clear the marked flag. */
+ last_connector->flags &= ~IsMarked;
+ }
+
+ last_device = device;
+ device = device->next;
+
+ /* Now, consider the device. */
+ if (!(device->flags & IsMarked))
+ {
+ DebugPrint ("device %p (%lu) is no longer marked",
+ device, device->provider);
+
+ if (device->flags & IsRemoved)
+ DeleteDevice (device);
+ else
+ DebugPrint ("not removing live device");
+ }
+ else
+ last_device->flags &= ~IsMarked;
+ }
+}
+
+
+
+static void
+Destroy (struct wl_client *client, struct wl_resource *resource)
+{
+ wl_resource_destroy (resource);
+}
+
+static struct wp_drm_lease_connector_v1_interface drm_lease_connector_impl =
+ {
+ .destroy = Destroy,
+ };
+
+static void
+HandleConnectorResourceDestroy (struct wl_resource *resource)
+{
+ DrmLeaseConnectorRef *ref;
+
+ ref = wl_resource_get_user_data (resource);
+ ref->last->next = ref->next;
+ ref->next->last = ref->last;
+ ref->gcnext->gclast = ref->gclast;
+ ref->gclast->gcnext = ref->gcnext;
+
+ XLFree (ref);
+ CollectDeadResources ();
+}
+
+
+
+static void
+RequestConnector (struct wl_client *client, struct wl_resource *resource,
+ struct wl_resource *connector_resource)
+{
+ DrmLeaseRequest *request;
+ DrmLeaseConnectorRef *ref;
+ DrmLeaseConnector *connector;
+ DrmLeaseConnectorList *list;
+
+ request = wl_resource_get_user_data (resource);
+ ref = wl_resource_get_user_data (connector_resource);
+ connector = ref->connector;
+
+ if (connector->device != request->device)
+ {
+ wl_resource_post_error (resource,
+ WP_DRM_LEASE_REQUEST_V1_ERROR_WRONG_DEVICE,
+ "the specified connector is on a different device");
+ return;
+ }
+
+#define DuplicateConnector \
+ WP_DRM_LEASE_REQUEST_V1_ERROR_DUPLICATE_CONNECTOR
+
+ /* See if the connector has already been added. */
+ list = request->outputs.next;
+ while (list != &request->outputs)
+ {
+ if (connector == list->connector)
+ {
+ wl_resource_post_error (resource, DuplicateConnector,
+ "the same connector got attached twice");
+ return;
+ }
+
+ list = list->next;
+ }
+
+#undef DuplicateConnector
+
+ DebugPrint ("requesting connector %p", connector);
+
+ /* Insert the connector into the list. */
+ list = XLCalloc (1, sizeof *list);
+ list->next = request->outputs.next;
+ list->last = &request->outputs;
+ list->connector = connector;
+ request->outputs.next->last = list;
+ request->outputs.next = list;
+ request->noutputs++;
+}
+
+
+
+static void
+DestroyLease (struct wl_client *client, struct wl_resource *resource)
+{
+ wl_resource_destroy (resource);
+}
+
+static const struct wp_drm_lease_v1_interface drm_lease_impl =
+ {
+ .destroy = DestroyLease,
+ };
+
+static void
+HandleLeaseResourceDestroy (struct wl_resource *resource)
+{
+ DrmLease *lease;
+ xcb_void_cookie_t cookie;
+ xcb_generic_error_t *error;
+
+ lease = wl_resource_get_user_data (resource);
+
+ /* Cancel the lease. */
+ if (lease->lease)
+ {
+ cookie = xcb_randr_free_lease_checked (compositor.conn,
+ lease->lease, 1);
+ error = xcb_request_check (compositor.conn, cookie);
+
+ if (error)
+ {
+ DebugPrint ("rid: %"PRIu32", minor: %"PRIu16", major: %"PRIu8", "
+ "error: %"PRIu8, error->resource_id, error->minor_code,
+ error->major_code, error->error_code);
+ free (error);
+ }
+ }
+
+ /* Free the rec. */
+ XLFree (lease);
+ CollectDeadResources ();
+}
+
+static void
+Submit (struct wl_client *client, struct wl_resource *resource, uint32_t id)
+{
+ DrmLeaseRequest *request;
+ xcb_randr_crtc_t *crtcs;
+ xcb_randr_output_t *outputs;
+ xcb_randr_lease_t lease_id;
+ DrmLease *lease;
+ DrmLeaseConnectorList *item;
+ int i, *fds;
+ xcb_randr_create_lease_cookie_t cookie;
+ xcb_randr_create_lease_reply_t *reply;
+ xcb_generic_error_t *error;
+
+ request = wl_resource_get_user_data (resource);
+
+ /* If the lease request is empty, post that error. */
+ if (request->outputs.next == &request->outputs)
+ {
+ wl_resource_post_error (resource,
+ WP_DRM_LEASE_REQUEST_V1_ERROR_EMPTY_LEASE,
+ "trying to lease without specifying connectors");
+ return;
+ }
+
+ lease = XLSafeMalloc (sizeof *lease);
+ error = NULL;
+
+ if (!lease)
+ {
+ wl_resource_post_no_memory (resource);
+ return;
+ }
+
+ /* Create the lease resource. */
+ memset (lease, 0, sizeof *lease);
+ lease->resource = wl_resource_create (client, &wp_drm_lease_v1_interface,
+ wl_resource_get_version (resource),
+ id);
+
+ if (!lease->resource)
+ {
+ XLFree (lease);
+ wl_resource_post_no_memory (resource);
+ return;
+ }
+
+ /* Populate crtcs and outputs. */
+ crtcs = alloca (sizeof *crtcs * request->noutputs);
+ outputs = alloca (sizeof *outputs * request->noutputs);
+ item = request->outputs.next;
+ i = 0;
+ reply = NULL;
+
+ while (item != &request->outputs)
+ {
+ if (item->connector->flags & IsRemoved)
+ {
+ DebugPrint ("removed connector was used in lease request");
+
+ /* The connector was removed. This means it can no longer
+ be leased. */
+ wl_resource_set_implementation (lease->resource, &drm_lease_impl,
+ lease, HandleLeaseResourceDestroy);
+
+ /* Send failure and return. */
+ wp_drm_lease_v1_send_finished (lease->resource);
+ return;
+ }
+
+ crtcs[i++] = item->connector->crtc;
+ outputs[i - 1] = item->connector->output;
+
+ DebugPrint ("adding output: %u crtc: %u",
+ outputs[i - 1], crtcs[i - 1]);
+
+ item = item->next;
+ }
+
+ /* Do the lease. Generate the resource ID for the lease. */
+ lease_id = xcb_generate_id (compositor.conn);
+
+ /* Now, try to create the lease. */
+ cookie = xcb_randr_create_lease (compositor.conn,
+ DefaultRootWindow (compositor.display),
+ lease_id, request->noutputs,
+ request->noutputs, crtcs, outputs);
+ reply = xcb_randr_create_lease_reply (compositor.conn, cookie, &error);
+
+ /* Set the resource implementation now. */
+ wl_resource_set_implementation (lease->resource, &drm_lease_impl,
+ lease, HandleLeaseResourceDestroy);
+
+ if (!reply)
+ {
+ DebugPrint ("lease failure");
+
+ if (error)
+ DebugPrint ("rid: %"PRIu32", minor: %"PRIu16", major: %"PRIu8", "
+ "error: %"PRIu8, error->resource_id, error->minor_code,
+ error->major_code, error->error_code);
+
+ /* Send failure. */
+ wp_drm_lease_v1_send_finished (lease->resource);
+
+ if (error)
+ free (error);
+ }
+ else
+ {
+ fds = xcb_randr_create_lease_reply_fds (compositor.conn, reply);
+
+ if (!fds)
+ /* Obtaining the reply fds failed. */
+ wp_drm_lease_v1_send_finished (lease->resource);
+ else
+ {
+ /* Send the lease file descriptor. */
+ wp_drm_lease_v1_send_lease_fd (lease->resource, fds[0]);
+ close (fds[0]);
+ }
+
+ /* Set the lease resource. */
+ lease->lease = lease_id;
+
+ /* Free the reply. */
+ free (reply);
+ }
+}
+
+static struct wp_drm_lease_request_v1_interface drm_lease_request_impl =
+ {
+ .request_connector = RequestConnector,
+ .submit = Submit,
+ };
+
+static void
+HandleRequestResourceDestroy (struct wl_resource *resource)
+{
+ DrmLeaseRequest *request;
+ DrmLeaseConnectorList *item, *last;
+
+ request = wl_resource_get_user_data (resource);
+
+ /* Free each element of the connector list. */
+ item = request->outputs.next;
+ while (item != &request->outputs)
+ {
+ last = item;
+ item = item->next;
+
+ XLFree (last);
+ }
+
+ /* Remove the request from the live request list. */
+ request->gclast->gcnext = request->gcnext;
+ request->gcnext->gclast = request->gclast;
+
+ /* Free the request itself. */
+ XLFree (request);
+}
+
+
+
+static void
+CreateLeaseRequest (struct wl_client *client, struct wl_resource *resource,
+ uint32_t id)
+{
+ DrmLeaseRequest *request;
+ DrmLeaseDeviceRef *ref;
+ DrmLeaseDevice *device;
+
+ request = XLSafeMalloc (sizeof *request);
+
+ if (!request)
+ {
+ wl_resource_post_no_memory (resource);
+ return;
+ }
+
+ memset (request, 0, sizeof *request);
+ request->resource
+ = wl_resource_create (client, &wp_drm_lease_request_v1_interface,
+ wl_resource_get_version (resource), id);
+
+ if (!request->resource)
+ {
+ XLFree (request);
+ return;
+ }
+
+ ref = wl_resource_get_user_data (resource);
+ device = ref->device;
+
+ /* Initialize the list of DRM connectors. */
+ request->outputs.next = &request->outputs;
+ request->outputs.last = &request->outputs;
+
+ /* Set the device. */
+ request->device = device;
+
+ /* Link the device onto the list of references. */
+ request->gcnext = all_lease_requests.gcnext;
+ request->gclast = &all_lease_requests;
+ all_lease_requests.gcnext->gclast = request;
+ all_lease_requests.gcnext = request;
+
+ /* Set the implementation. */
+ wl_resource_set_implementation (request->resource, &drm_lease_request_impl,
+ request, HandleRequestResourceDestroy);
+}
+
+static void
+Release (struct wl_client *client, struct wl_resource *resource)
+{
+ /* Release the resource, but not before sending a `released'
+ event. */
+ wp_drm_lease_device_v1_send_released (resource);
+ wl_resource_destroy (resource);
+}
+
+static const struct wp_drm_lease_device_v1_interface drm_lease_device_impl =
+ {
+ .release = Release,
+ .create_lease_request = CreateLeaseRequest,
+ };
+
+static void
+HandleResourceDestroy (struct wl_resource *resource)
+{
+ DrmLeaseDeviceRef *ref;
+
+ ref = wl_resource_get_user_data (resource);
+ ref->last->next = ref->next;
+ ref->next->last = ref->last;
+ ref->gcnext->gclast = ref->gclast;
+ ref->gclast->gcnext = ref->gcnext;
+
+ XLFree (ref);
+ CollectDeadResources ();
+}
+
+static DrmLeaseConnectorRef *
+AddConnectorRef (DrmLeaseConnector *connector,
+ DrmLeaseDeviceRef *ref)
+{
+ DrmLeaseConnectorRef *connector_ref;
+ struct wl_client *client;
+
+ client = wl_resource_get_client (ref->resource);
+ connector_ref = XLCalloc (1, sizeof *connector_ref);
+ connector_ref->resource
+ = wl_resource_create (client,
+ &wp_drm_lease_connector_v1_interface,
+ wl_resource_get_version (ref->resource),
+ 0);
+
+ if (!connector_ref->resource)
+ {
+ fprintf (stderr, "failed to allocate output ref\n");
+ abort ();
+ }
+
+ connector_ref->connector = connector;
+ connector_ref->next = connector->references.next;
+ connector_ref->last = &connector->references;
+ connector_ref->gcnext = all_connector_references.gcnext;
+ connector_ref->gclast = &all_connector_references;
+ all_connector_references.gcnext->gclast = connector_ref;
+ all_connector_references.gcnext = connector_ref;
+ connector->references.next->last = connector_ref;
+ connector->references.next = connector_ref;
+ wl_resource_set_implementation (connector_ref->resource,
+ &drm_lease_connector_impl,
+ connector_ref,
+ HandleConnectorResourceDestroy);
+
+ return connector_ref;
+}
+
+static void
+SendOutputs (DrmLeaseDevice *device, DrmLeaseDeviceRef *ref)
+{
+ DrmLeaseConnector *connector;
+ DrmLeaseConnectorRef *connector_ref;
+ char buf[sizeof "xxxxxxxxxx" + 1];
+
+ connector = device->outputs.next;
+ while (connector != &device->outputs)
+ {
+ if (!(connector->flags & IsDisconnected)
+ && !(connector->flags & InvalidConnectorID)
+ && !(connector->flags & IsRemoved))
+ {
+ connector_ref = AddConnectorRef (connector, ref);
+ wp_drm_lease_device_v1_send_connector (ref->resource,
+ connector_ref->resource);
+
+ DebugPrint ("sending connector %lu:%lu to %p",
+ connector->output, connector->crtc, ref);
+
+ /* Send the connector id. */
+ wp_drm_lease_connector_v1_send_connector_id (connector_ref->resource,
+ connector->connector_id);
+
+ /* Send the unique connector name. */
+ sprintf (buf, "%d", connector->connector_id);
+ wp_drm_lease_connector_v1_send_name (connector_ref->resource, buf);
+
+ /* Send the connector description. */
+ wp_drm_lease_connector_v1_send_description (connector_ref->resource,
+ connector->name);
+
+ /* Send done. */
+ wp_drm_lease_connector_v1_send_done (connector_ref->resource);
+ }
+
+ connector = connector->next;
+ }
+}
+
+static void
+HandleBind (struct wl_client *client, void *data, uint32_t version,
+ uint32_t id)
+{
+ DrmLeaseDeviceRef *ref;
+ DrmLeaseDevice *device;
+
+ ref = XLSafeMalloc (sizeof *ref);
+ device = data;
+
+ if (!ref)
+ {
+ wl_client_post_no_memory (client);
+ return;
+ }
+
+ memset (ref, 0, sizeof *ref);
+ ref->resource = wl_resource_create (client,
+ &wp_drm_lease_device_v1_interface,
+ version, id);
+
+ if (!ref->resource)
+ {
+ XLFree (ref);
+ wl_client_post_no_memory (client);
+ return;
+ }
+
+ ref->next = device->references.next;
+ ref->last = &device->references;
+ ref->device = device;
+ device->references.next->last = ref;
+ device->references.next = ref;
+ ref->gcnext = all_device_references.gcnext;
+ ref->gclast = &all_device_references;
+ all_device_references.gcnext->gclast = ref;
+ all_device_references.gcnext = ref;
+
+ wl_resource_set_implementation (ref->resource, &drm_lease_device_impl,
+ ref, HandleResourceDestroy);
+
+ DebugPrint ("sending fd %d to %p", device->fd, ref);
+
+ /* Send the drm_fd to the client. */
+ wp_drm_lease_device_v1_send_drm_fd (ref->resource, device->fd);
+
+ /* Send each output. */
+ SendOutputs (device, ref);
+
+ /* Send done. */
+ wp_drm_lease_device_v1_send_done (ref->resource);
+}
+
+static DrmLeaseDevice *
+AddProvider (RRProvider provider)
+{
+ DrmLeaseDevice *device;
+ xcb_dri3_open_cookie_t cookie;
+ xcb_dri3_open_reply_t *reply;
+ int *fds, fd, new;
+ xcb_generic_error_t *error;
+ char *name;
+
+ error = NULL;
+
+ device = XLCalloc (1, sizeof *device);
+ device->references.next = &device->references;
+ device->references.last = &device->references;
+
+ /* Add the given provider. Obtain the file descriptor it is
+ associated with. */
+ cookie = xcb_dri3_open (compositor.conn,
+ DefaultRootWindow (compositor.display),
+ provider);
+ reply = xcb_dri3_open_reply (compositor.conn, cookie, &error);
+
+ if (!reply)
+ goto error;
+
+ fds = xcb_dri3_open_reply_fds (compositor.conn, reply);
+
+ if (!fds)
+ {
+ free (reply);
+ goto error;
+ }
+
+ fd = fds[0];
+
+ if (drmGetNodeTypeFromFd (fd) != DRM_NODE_RENDER)
+ {
+ name = drmGetDeviceNameFromFd2 (fd);
+
+ if (name)
+ {
+ DebugPrint ("device name is %s", name);
+ new = open (name, O_RDWR);
+
+ if (new >= 0)
+ {
+ if (drmIsMaster (fd))
+ drmDropMaster (fd);
+
+ /* Close the old file descriptor. */
+ close (fd);
+ fd = new;
+ }
+ else
+ DebugPrint ("failed to open device");
+
+ free (name);
+ }
+ }
+
+ device->fd = fd;
+
+ DebugPrint ("obtained provider %lu's fd %d", provider, fd);
+
+ free (reply);
+
+ /* Set the provider. */
+ device->provider = provider;
+
+ /* Create the global. */
+ device->global = wl_global_create (compositor.wl_display,
+ &wp_drm_lease_device_v1_interface,
+ 1, device, HandleBind);
+
+ /* Chain the provider onto the list of all devices. */
+ device->next = all_devices.next;
+ device->last = &all_devices;
+ all_devices.next->last = device;
+ all_devices.next = device;
+
+ /* Initialize the device's connector list. */
+ device->outputs.next = &device->outputs;
+ device->outputs.last = &device->outputs;
+
+ return device;
+
+ error:
+ if (error)
+ free (error);
+
+ XLFree (device);
+ return NULL;
+}
+
+static DrmLeaseConnector *
+AddOutput (DrmLeaseDevice *device, RROutput output, RRCrtc crtc,
+ XRROutputInfo *info)
+{
+ DrmLeaseConnector *connector;
+ int rc, actual_format;
+ Atom actual_type;
+ unsigned long nitems, bytes_after;
+ unsigned char *data;
+
+ connector = XLCalloc (1, sizeof *connector);
+ connector->output = output;
+ connector->crtc = crtc;
+ connector->name = XLStrdup (info->name);
+
+ /* Try to determine the connector ID. */
+ data = NULL;
+
+ CatchXErrors ();
+ rc = XRRGetOutputProperty (compositor.display, output,
+ CONNECTOR_ID, 0, 1, False,
+ False, XA_INTEGER, &actual_type,
+ &actual_format, &nitems, &bytes_after,
+ &data);
+ UncatchXErrors (NULL);
+
+ if (rc != Success || !data || actual_format != 32
+ || nitems < 1 || actual_type != XA_INTEGER)
+ {
+ if (data)
+ XFree (data);
+
+ /* Mark this connector as invalid. */
+ connector->flags |= InvalidConnectorID;
+ DebugPrint ("invalid connector id");
+ }
+ else
+ {
+ /* Set the connector ID. */
+ connector->connector_id = *(unsigned long *) data;
+ DebugPrint ("connector ID is %d", connector->connector_id);
+ }
+
+ if (info->connection == RR_Disconnected)
+ connector->flags |= IsDisconnected;
+
+ connector->references.next = &connector->references;
+ connector->references.last = &connector->references;
+
+ /* Link the output onto the device's output list. */
+ connector->next = device->outputs.next;
+ connector->last = &device->outputs;
+ connector->device = device;
+ device->outputs.next->last = connector;
+ device->outputs.next = connector;
+
+ if (data)
+ XFree (data);
+
+ return connector;
+}
+
+static void
+InitializeProviderOutputs (void)
+{
+ XRRProviderInfo *info;
+ XRRScreenResources *screen_resources;
+ Window root;
+ DrmLeaseDevice *device;
+ int i;
+ XRROutputInfo *output;
+
+ root = DefaultRootWindow (compositor.display);
+ screen_resources = XRRGetScreenResources (compositor.display,
+ root);
+
+ device = all_devices.next;
+ while (device != &all_devices)
+ {
+ CatchXErrors ();
+ info = XRRGetProviderInfo (compositor.display,
+ screen_resources,
+ device->provider);
+ UncatchXErrors (NULL);
+
+ DebugPrint ("provider info: %p", info);
+
+ if (!info)
+ goto next;
+
+ DebugPrint ("obtained provider info %lu; cap: %u"
+ " ncrtcs: %d noutputs %d",
+ device->provider, info->capabilities,
+ info->ncrtcs, info->noutputs);
+
+ /* Now loop through each output. */
+ for (i = 0; i < info->noutputs; ++i)
+ {
+ /* Try to obtain the output info. */
+ CatchXErrors ();
+ output = XRRGetOutputInfo (compositor.display,
+ screen_resources,
+ info->outputs[i]);
+ UncatchXErrors (NULL);
+
+ DebugPrint ("obtained output %i %lu %p", i,
+ info->outputs[i], output);
+
+ if (!output)
+ continue;
+
+ DebugPrint ("output %s crtc is %lu", output->name,
+ output->crtc);
+ AddOutput (device, info->outputs[i], output->crtc,
+ output);
+ XRRFreeOutputInfo (output);
+ }
+
+ XRRFreeProviderInfo (info);
+ next:
+ device = device->next;
+ }
+
+ XRRFreeScreenResources (screen_resources);
+}
+
+static void
+InitializeProviderList (void)
+{
+ Window root;
+ XRRProviderResources *resources;
+ int i;
+
+ root = DefaultRootWindow (compositor.display);
+ resources = XRRGetProviderResources (compositor.display, root);
+
+ DebugPrint ("providers: %d", resources->nproviders);
+
+ for (i = 0; i < resources->nproviders; ++i)
+ AddProvider (resources->providers[i]);
+
+ XRRFreeProviderResources (resources);
+
+ DebugPrint ("initializing outputs");
+ InitializeProviderOutputs ();
+}
+
+static ProviderOutputTree *
+BuildProviderTree (void)
+{
+ ProviderOutputTree *tree;
+ xcb_randr_get_providers_cookie_t cookie;
+ xcb_randr_get_providers_reply_t *reply;
+ xcb_randr_get_provider_info_cookie_t *cookies;
+ xcb_randr_get_provider_info_reply_t **replies;
+ xcb_randr_get_output_info_cookie_t *output_cookies;
+ xcb_randr_get_output_info_reply_t **output_replies;
+ int i, noutputs, j, num_outputs, k;
+ xcb_timestamp_t reply_timestamp;
+ Window root;
+ xcb_randr_output_t *output_ptr, *outputs;
+ xcb_randr_get_output_info_reply_t **output_info_ptr;
+ xcb_generic_error_t *error;
+
+ tree = XLCalloc (1, sizeof *tree);
+ root = DefaultRootWindow (compositor.display);
+
+ /* Now, query for all providers. */
+ cookie = xcb_randr_get_providers (compositor.conn, root);
+ reply = xcb_randr_get_providers_reply (compositor.conn, cookie,
+ NULL);
+
+ if (!reply)
+ abort ();
+
+ /* Obtain the providers. */
+ tree->nproviders = xcb_randr_get_providers_providers_length (reply);
+ tree->providers = XLCalloc (tree->nproviders, sizeof *tree->providers);
+ memcpy (tree->providers, xcb_randr_get_providers_providers (reply),
+ sizeof *tree->providers * tree->nproviders);
+
+ /* Record the timestamp. */
+ reply_timestamp = reply->timestamp;
+ tree->timestamp = reply_timestamp;
+
+ /* Free the reply. */
+ free (reply);
+
+ /* Now that we know how many providers there are, look at all the
+ outputs for each provider. */
+ cookies = alloca (sizeof *cookies * tree->nproviders);
+ replies = alloca (sizeof *replies * tree->nproviders);
+
+ /* Satisfy -Wanalyzer-use-of-uninitialized-value 13 lines below.
+ Where is the uninitialized value? */
+ memset (cookies, 0, sizeof *cookies * tree->nproviders);
+ memset (replies, 0, sizeof *replies * tree->nproviders);
+
+ for (i = 0; i < tree->nproviders; i++)
+ cookies[i] = xcb_randr_get_provider_info (compositor.conn,
+ tree->providers[i],
+ reply_timestamp);
+ noutputs = 0;
+
+ for (i = 0; i < tree->nproviders; i++)
+ {
+ error = NULL;
+ replies[i] = xcb_randr_get_provider_info_reply (compositor.conn,
+ cookies[i], &error);
+ if (error)
+ free (error);
+
+ if (replies[i])
+ /* Set the number of outputs. */
+ noutputs += xcb_randr_get_provider_info_outputs_length (replies[i]);
+ }
+
+ /* Retrieve the output info for each provider. It is too hard to
+ reason about doing this asychronously across providers, so we
+ sync at the end of each processing outputs for each provider
+ despite there being no hard data dependency there. */
+ tree->outputs = XLCalloc (noutputs, sizeof *tree->outputs);
+ tree->output_info = XLCalloc (noutputs, sizeof *tree->output_info);
+ tree->nconnectors = XLCalloc (tree->nproviders,
+ sizeof *tree->nconnectors);
+ output_ptr = tree->outputs;
+ output_info_ptr = tree->output_info;
+
+ for (i = 0, j = 0; i < tree->nproviders; ++i)
+ {
+ if (!replies[i])
+ continue;
+
+ num_outputs = xcb_randr_get_provider_info_outputs_length (replies[i]);
+
+ DebugPrint ("num_outputs: %d", num_outputs);
+
+ outputs = xcb_randr_get_provider_info_outputs (replies[i]);
+ output_cookies = alloca (num_outputs * sizeof *output_cookies);
+ output_replies = alloca (num_outputs * sizeof *output_replies);
+
+ for (k = 0; k < num_outputs; ++k)
+ output_cookies[k] = xcb_randr_get_output_info (compositor.conn,
+ outputs[k],
+ reply_timestamp);
+
+ for (k = 0; k < num_outputs; ++k)
+ {
+ error = NULL;
+ output_replies[k]
+ = xcb_randr_get_output_info_reply (compositor.conn,
+ output_cookies[k],
+ &error);
+
+ if (error)
+ free (error);
+
+ if (!output_replies[k])
+ continue;
+
+ tree->nconnectors[j] += 1;
+ DebugPrint ("nconnectors[%d] became: %d",
+ j, tree->nconnectors[j]);
+
+ /* Record the output and output info. */
+ XLAssert (output_ptr < tree->outputs + noutputs);
+
+ *output_ptr++ = outputs[k];
+ *output_info_ptr++ = output_replies[k];
+ }
+
+ /* Free the provider info. */
+ free (replies[i]);
+
+ j++;
+ }
+
+ /* Return the resulting tree. */
+ return tree;
+}
+
+static void
+FreeProviderTree (ProviderOutputTree *tree)
+{
+ int i, j, k;
+
+ XLFree (tree->providers);
+ XLFree (tree->outputs);
+
+ /* Free all output info. */
+ for (i = 0, j = 0; i < tree->nproviders; ++i)
+ {
+ for (k = 0; k < tree->nconnectors[i]; ++k)
+ free (tree->output_info[j + k]);
+ j += k;
+ }
+
+ XLFree (tree->output_info);
+ XLFree (tree->nconnectors);
+ XLFree (tree);
+}
+
+static DrmLeaseDevice *
+FindProvider (RRProvider id)
+{
+ DrmLeaseDevice *device;
+
+ device = all_devices.next;
+ while (device != &all_devices)
+ {
+ if (!(device->flags & IsRemoved)
+ && device->provider == id)
+ return device;
+ }
+
+ return NULL;
+}
+
+static void
+RemoveDevice (DrmLeaseDevice *device)
+{
+ /* Mark the device as invalid and free its fd and global. The
+ device itself will be destroyed once no more references to it
+ exist from clients. */
+ device->flags |= IsRemoved;
+ close (device->fd);
+ wl_global_destroy (device->global);
+}
+
+static void
+RemoveConnector (DrmLeaseConnector *connector)
+{
+ DrmLeaseConnectorRef *ref;
+
+ /* Mark the output as removed. */
+ connector->flags |= IsRemoved | IsWithdrawn;
+
+ /* Withdraw each of the references. */
+ ref = connector->references.next;
+ while (ref != &connector->references)
+ {
+ if (!(ref->flags & IsWithdrawn))
+ wp_drm_lease_connector_v1_send_withdrawn (ref->resource);
+
+ ref->flags |= IsWithdrawn;
+ ref = ref->next;
+ }
+}
+
+static void
+WithdrawConnector (DrmLeaseConnector *connector)
+{
+ DrmLeaseConnectorRef *ref;
+
+ if (connector->flags & IsWithdrawn)
+ return;
+
+ connector->flags |= IsWithdrawn;
+
+ /* Withdraw each of the references. */
+ ref = connector->references.next;
+ while (ref != &connector->references)
+ {
+ if (!(ref->flags & IsWithdrawn))
+ wp_drm_lease_connector_v1_send_withdrawn (ref->resource);
+
+ ref->flags |= IsWithdrawn;
+ ref = ref->next;
+ }
+}
+
+static void
+SendConnectorToClients (DrmLeaseConnector *connector)
+{
+ DrmLeaseConnectorRef *ref;
+ DrmLeaseDeviceRef *device_ref;
+ char buf[sizeof "xxxxxxxxxx" + 1];
+
+ XLAssert (!(connector->flags & IsRemoved));
+
+ connector->flags &= ~IsWithdrawn;
+
+ device_ref = connector->device->references.next;
+ while (device_ref != &connector->device->references)
+ {
+ ref = AddConnectorRef (connector, device_ref);
+
+ wp_drm_lease_device_v1_send_connector (device_ref->resource,
+ ref->resource);
+
+ /* Send the connector id. */
+ wp_drm_lease_connector_v1_send_connector_id (ref->resource,
+ connector->connector_id);
+
+ /* Send the unique connector name. */
+ sprintf (buf, "%d", connector->connector_id);
+ wp_drm_lease_connector_v1_send_name (ref->resource, buf);
+
+ /* Send the connector description. */
+ wp_drm_lease_connector_v1_send_description (ref->resource,
+ connector->name);
+
+ /* Send done. */
+ wp_drm_lease_connector_v1_send_done (ref->resource);
+
+ device_ref = device_ref->next;
+ }
+}
+
+static DrmLeaseConnector *
+FindOutput (DrmLeaseDevice *device, RROutput id)
+{
+ DrmLeaseConnector *connector;
+
+ connector = device->outputs.next;
+ while (connector != &device->outputs)
+ {
+ if (connector->output == id)
+ return connector;
+
+ connector = connector->next;
+ }
+
+ return NULL;
+}
+
+static void
+HandleSingleProvider (ProviderOutputTree *tree, int index,
+ int connector_offset)
+{
+ RRProvider provider;
+ xcb_randr_output_t *outputs;
+ xcb_randr_get_output_info_reply_t **info;
+ DrmLeaseDevice *device;
+ int i, name_length;
+ XRROutputInfo outputinfo;
+ DrmLeaseConnector *connector;
+ DrmLeaseDeviceRef *ref;
+
+ provider = tree->providers[index];
+ outputs = tree->outputs + connector_offset;
+ info = tree->output_info + connector_offset;
+
+ /* Try to find an existing provider. */
+ device = FindProvider (provider);
+
+ /* If there is no existing provider, then add the new device. */
+ if (!device)
+ {
+ DebugPrint ("adding provider for provider %lu", provider);
+
+ device = AddProvider (provider);
+
+ /* Add all the outputs. */
+ for (i = 0; i < tree->nconnectors[index]; ++i)
+ {
+ name_length = xcb_randr_get_output_info_name_length (*info);
+
+ outputinfo.connection = (*info)->connection;
+ outputinfo.name = XLMalloc (name_length + 1);
+ memcpy (outputinfo.name,
+ xcb_randr_get_output_info_name (*info),
+ name_length);
+ outputinfo.name[name_length] = '\0';
+
+ DebugPrint ("adding output named %s", outputinfo.name);
+
+ /* It seems a little wrong to use a fake output info
+ structure. */
+ AddOutput (device, *outputs, (*info)->crtc, &outputinfo);
+
+ XLFree (outputinfo.name);
+ outputs++;
+ info++;
+ }
+ }
+ else
+ {
+ DebugPrint ("provider %p found", device);
+
+ /* Otherwise, compare the outputs of the provider with what is
+ currently present. First, remove each output that is not
+ still present. */
+
+ connector = device->outputs.next;
+ while (connector != &device->outputs)
+ {
+ if (connector->flags & IsRemoved)
+ continue;
+
+ for (i = 0; i < tree->nconnectors[index]; ++i)
+ {
+ DebugPrint ("consideration: %p %"PRIu32" %lu", connector,
+ outputs[i], connector->output);
+
+ if (outputs[i] == connector->output)
+ goto next;
+ }
+
+ DebugPrint ("removing connector %p", connector);
+
+ /* Remove the connector. */
+ RemoveConnector (connector);
+
+ next:
+ connector = connector->next;
+ }
+
+ /* Next, look through each output. */
+ for (i = 0; i < tree->nconnectors[index]; ++i)
+ {
+ connector = FindOutput (device, outputs[i]);
+
+ if (!connector)
+ {
+ /* If the connector does not exist, add it. */
+ name_length = xcb_randr_get_output_info_name_length (info[i]);
+
+ outputinfo.connection = info[i]->connection;
+ outputinfo.name = XLMalloc (name_length + 1);
+ memcpy (outputinfo.name,
+ xcb_randr_get_output_info_name (info[i]),
+ name_length);
+ outputinfo.name[name_length] = '\0';
+
+ /* It seems a little wrong to use a fake output info
+ structure. */
+ connector = AddOutput (device, outputs[i], info[i]->crtc,
+ &outputinfo);
+ SendConnectorToClients (connector);
+ DebugPrint ("added output named %s", outputinfo.name);
+
+ XLFree (outputinfo.name);
+ continue;
+ }
+
+ DebugPrint ("updating existing connector %p", connector);
+
+ /* Otherwise, the connector already exists. Compare it with
+ the new connector info and see what changed. */
+ if (connector->flags & IsDisconnected
+ && info[i]->connection != XCB_RANDR_CONNECTION_DISCONNECTED)
+ {
+ /* The connector was previously disconnected, but not
+ anymore. Send the connector to clients. */
+ SendConnectorToClients (connector);
+
+ /* Update the flag. */
+ connector->flags &= ~IsDisconnected;
+
+ DebugPrint ("output named %s was connected", connector->name);
+ }
+ else if (!(connector->flags & IsDisconnected)
+ && info[i]->connection == XCB_RANDR_CONNECTION_DISCONNECTED)
+ {
+ /* The connector was just disconnected. Withdraw the
+ connector. */
+ WithdrawConnector (connector);
+
+ /* Update the flag. */
+ connector->flags |= IsDisconnected;
+
+ DebugPrint ("output named %s disconnected", connector->name);
+ }
+
+ /* Set the crtc. */
+ connector->crtc = info[i]->crtc;
+ }
+ }
+
+ /* Now send done to each device. */
+ ref = device->references.next;
+ while (ref != &device->references)
+ {
+ wp_drm_lease_device_v1_send_done (ref->resource);
+ ref = ref->next;
+ }
+}
+
+static void
+HandleOutputOrResourceChange (Time timestamp)
+{
+ ProviderOutputTree *tree;
+ int i, connectors_read;
+ DrmLeaseDevice *device;
+
+ DebugPrint ("timestamp: %lu, last-change-time: %lu", timestamp,
+ last_change_time);
+
+ if (timestamp != CurrentTime
+ && (timestamp - last_change_time) <= 0
+ /* If timestamp is 500 ms later, assume that the time
+ overflowed. */
+ && (timestamp - last_change_time) > -500)
+ {
+ DebugPrint ("rejecting outdated event");
+ return;
+ }
+
+ /* Outputs or resources changed. First, build a "provider-output
+ tree" structure. */
+ tree = BuildProviderTree ();
+
+ DebugPrint ("provider tree obtained with %d providers",
+ tree->nproviders);
+
+ /* Afterwards, mark every provider that is no longer present as
+ removed. */
+ device = all_devices.next;
+ while (device != &all_devices)
+ {
+ if (device->flags & IsRemoved)
+ goto next_device;
+
+ for (i = 0; i < tree->nproviders; ++i)
+ {
+ if (tree->providers[i] == device->provider)
+ goto next_device;
+ }
+
+ DebugPrint ("device %p was not found in tree",
+ device);
+
+ /* Remove the device. */
+ RemoveDevice (device);
+
+ next_device:
+ device = device->next;
+ }
+
+ /* Next, compare each provider in the tree with the currently
+ attached devices. */
+
+ connectors_read = 0;
+ for (i = 0; i < tree->nproviders; ++i)
+ {
+ HandleSingleProvider (tree, i, connectors_read);
+ connectors_read += tree->nconnectors[i];
+ }
+
+ /* Set the last change time. */
+ last_change_time = MAX (tree->timestamp, timestamp);
+
+ /* Finally, free the provider tree. */
+ FreeProviderTree (tree);
+
+ /* And collect dead resources. */
+ CollectDeadResources ();
+}
+
+void
+XLInitDrmLease (void)
+{
+ xcb_randr_query_version_reply_t *reply;
+ xcb_randr_query_version_cookie_t cookie;
+
+ /* This shouldn't be freed. */
+ const xcb_query_extension_reply_t *ext;
+
+ /* Initialize XRandR with XCB as well. Version 1.6 of the extension
+ must be available. */
+ ext = xcb_get_extension_data (compositor.conn, &xcb_randr_id);
+
+ if (!ext || !ext->present)
+ /* DRM leasing will not be supported. */
+ return;
+
+
+ cookie = xcb_randr_query_version (compositor.conn, 1, 6);
+ reply = xcb_randr_query_version_reply (compositor.conn,
+ cookie, NULL);
+
+ if (!reply)
+ return;
+
+ if (reply->major_version < 1
+ || (reply->major_version == 1
+ && reply->minor_version < 6))
+ {
+ free (reply);
+ return;
+ }
+
+ /* Free the reply. */
+ free (reply);
+
+ all_devices.next = &all_devices;
+ all_devices.last = &all_devices;
+ all_device_references.gclast = &all_device_references;
+ all_device_references.gcnext = &all_device_references;
+ all_connector_references.gcnext = &all_connector_references;
+ all_connector_references.gclast = &all_connector_references;
+ all_lease_requests.gcnext = &all_lease_requests;
+ all_lease_requests.gclast = &all_lease_requests;
+
+ /* Initialize the provider list. */
+ InitializeProviderList ();
+
+ /* Add a hook that runs upon notification. */
+ XLOutputSetChangeFunction (HandleOutputOrResourceChange);
+}
diff --git a/libraries.def b/libraries.def
deleted file mode 100644
index 06e153f..0000000
--- a/libraries.def
+++ /dev/null
@@ -1,26 +0,0 @@
-/* Edit this file if any of these libraries are named differently
- on your system. */
-
-XCB = -lxcb
-XCB_SHM = -lxcb-shm
-XCB_DRI3 = -lxcb-dri3
-XCB_SHAPE = -lxcb-shape
-WAYLAND_SERVER = -lwayland-server
-XCBLIB = -lX11-xcb
-PIXMAN = -lpixman-1
-DRMINCLUDES = -I$(INCROOT)/drm
-PIXMANINCLUDES = -I$(INCROOT)/pixman-1
-
-/* And edit this if wayland-scanner is named something else on your
- system. */
-
-WAYLAND_SCANNER = wayland-scanner
-
-/* Uncomment the following code if building with EGL support.
-
-#define HaveEglSupport
-
-EGL = -lEGL
-GLES = -lGLESv2
-
-*/