From d84eb9e9f54462c89891a87bcc234aad07032a0f Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Thu, 27 Jun 2019 15:13:37 -0400 Subject: [PATCH] Introduce wlr_drm_lease_v1 --- include/wlr/types/wlr_drm_lease_v1.h | 145 +++++++ protocol/drm-lease-unstable-v1.xml | 246 +++++++++++ protocol/meson.build | 1 + types/meson.build | 1 + types/wlr_drm_lease_v1.c | 582 +++++++++++++++++++++++++++ 5 files changed, 975 insertions(+) create mode 100644 include/wlr/types/wlr_drm_lease_v1.h create mode 100644 protocol/drm-lease-unstable-v1.xml create mode 100644 types/wlr_drm_lease_v1.c diff --git a/include/wlr/types/wlr_drm_lease_v1.h b/include/wlr/types/wlr_drm_lease_v1.h new file mode 100644 index 00000000..e81abbd9 --- /dev/null +++ b/include/wlr/types/wlr_drm_lease_v1.h @@ -0,0 +1,145 @@ +/* + * 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_DRM_LEASE_V1_H +#define WLR_TYPES_WLR_DRM_LEASE_V1_H + +#include +#include + +struct wlr_drm_backend; + +struct wlr_drm_lease_manager_v1 { + struct wl_list resources; // wl_resource_get_link + struct wl_global *global; + struct wlr_drm_backend *backend; + + struct wl_list connectors; // wlr_drm_lease_connector_v1::link + struct wl_list leases; // wl_resource_get_link + struct wl_list lease_requests; // wl_resource_get_link + + struct { + /** + * Upon receiving this signal, call + * wlr_drm_lease_manager_v1_grant_lease_request to grant a lease of the + * requested DRM resources, or + * wlr_drm_lease_manager_v1_reject_lease_request to reject the request. + */ + struct wl_signal lease_requested; // wlr_drm_lease_request_v1 + } events; + + void *data; +}; + +struct wlr_drm_connector; +struct wlr_drm_lease_v1; + +/** Represents a connector which is available for leasing, and may be leased */ +struct wlr_drm_lease_connector_v1 { + struct wlr_output *output; + struct wlr_drm_connector *drm_connector; + struct wl_list resources; // wl_resource_get_link + + /** NULL if no client is currently leasing this connector */ + struct wlr_drm_lease_v1 *active_lease; + + struct wl_list link; // wlr_drm_lease_manager_v1::connectors +}; + +/** Represents a connector which has been added to a lease or lease request */ +struct wlr_drm_connector_lease_v1 { + struct wlr_drm_lease_manager_v1 *manager; + struct wlr_drm_lease_connector_v1 *connector; + struct wl_list link; // wlr_drm_lease_request_v1::connectors +}; + +/** Represents a pending or submitted lease request */ +struct wlr_drm_lease_request_v1 { + struct wlr_drm_lease_manager_v1 *manager; + struct wl_resource *resource; // wlr_drm_manager_v1::lease_requests + struct wl_list connectors; // wlr_drm_connector_lease_v1::link + bool invalid; + + /** NULL until the lease is submitted */ + struct wlr_drm_lease_v1 *lease; +}; + +/** Represents an active or previously active lease */ +struct wlr_drm_lease_v1 { + struct wlr_drm_lease_manager_v1 *manager; + struct wl_resource *resource; // wlr_drm_manager_v1::leases + struct wl_list connectors; // wlr_drm_connector_lease_v1::link + uint32_t lessee_id; + + struct { + /** + * Upon receiving this signal, it is safe to re-use the leased + * resources. After the signal is processed, the backend will re-signal + * new_output events for each leased output. + * + * The lifetime of the lease reference after the signal handler returns + * is not defined. + */ + struct wl_signal revoked; // wlr_drm_lease_v1 + } events; + + void *data; +}; + +/** + * Creates a DRM lease manager. The backend supplied must be a DRM backend, or a + * multi-backend with a single DRM backend within. If the supplied backend is + * not suitable, NULL is returned. + */ +struct wlr_drm_lease_manager_v1 *wlr_drm_lease_manager_v1_create( + struct wl_display *display, struct wlr_backend *backend); + +/** + * Offers a wlr_output for lease. The offered output must be owned by the DRM + * backend associated with this lease manager. + */ +void wlr_drm_lease_manager_v1_offer_output( + struct wlr_drm_lease_manager_v1 *manager, struct wlr_output *output); + +/** + * Withdraws a previously offered output for lease. It is a programming error to + * call this function when there are any active leases for this output. + */ +void wlr_drm_lease_manager_v1_widthraw_output( + struct wlr_drm_lease_manager_v1 *manager, struct wlr_output *output); + +/** + * Grants a client's lease request. The lease manager will then provision the + * DRM lease and transfer the file descriptor to the client. After calling this, + * each wlr_output leased is destroyed, and will be re-issued through + * wlr_backend.events.new_outputs when the lease is revoked. + * + * This will return NULL without leasing any resources if the lease is invalid; + * this can happen for example if two clients request the same resources and an + * attempt to grant both leases is made. + */ +struct wlr_drm_lease_v1 *wlr_drm_lease_manager_v1_grant_lease_request( + struct wlr_drm_lease_manager_v1 *manager, + struct wlr_drm_lease_request_v1 *request); + +/** + * Rejects a client's lease request. + */ +void wlr_drm_lease_manager_v1_reject_lease_request( + struct wlr_drm_lease_manager_v1 *manager, + struct wlr_drm_lease_request_v1 *request); + +/** + * Revokes a client's lease request. You may resume use of any of the outputs + * leased after making this call. + */ +void wlr_drm_lease_manager_v1_revoke_lease( + struct wlr_drm_lease_manager_v1 *manager, + struct wlr_drm_lease_v1 *lease); + +#endif diff --git a/protocol/drm-lease-unstable-v1.xml b/protocol/drm-lease-unstable-v1.xml new file mode 100644 index 00000000..083d0041 --- /dev/null +++ b/protocol/drm-lease-unstable-v1.xml @@ -0,0 +1,246 @@ + + + + Copyright © 2018 NXP + Copyright © 2019 Status Research & Development GmbH. + + 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. Once leased, the compositor will not use the leased resources + until the lease is revoked or the client closes the file descriptor. + + The lease manager is used to advertise connectors which are available for + leasing, and by the client to negotiate a lease request. + + 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. + + + + + + + + + Creates a lease request object. + + See the documentation for zwp_drm_lease_request_v1 for details. + + + + + + + Indicates the client no longer wishes to receive connector events. The + compositor may still send connector events until it sends the finish + event, however. + + The client must not send any requests after this one. + + + + + + The compositor may choose to advertise 0 or more connectors which may be + leased to clients, and will use this event to do so. This object may be + passed into a lease request to lease that connector. See + zwp_drm_lease_request_v1.add_connector for details. + + When this global is bound, the compositor will send all connectors + available for lease, but may send additional connectors at any time. + + + + + + + This event indicates that the compositor is done sending connector + events. The compositor will destroy this object immediately after + sending this event, and it will become invalid. The client should + release any resources associated with this manager after receiving this + event. + + + + + + + Represents a DRM connector which is available for lease. These objects are + created via zwp_drm_lease_manager_v1.connector, and should be passed into + lease requests via zwp_drm_lease_request_v1.add_connector. + + + + + 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. + + If the compositor also supports zxdg_output_manager_v1 and this + connector corresponds to a zxdg_output_v1, this name will match the + name of this zxdg_output_v1 object. + + + + + + + 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 will send this event to indicate the DRM ID which + represents the underlying connector which is being offered. Note that + the final lease may include additional object IDs, such as CRTCs and + planes. + + + + + + + The compositor may send this event once the connector is created to + provide a file descriptor which may be memory-mapped to read the + connector's EDID, to assist in selecting the correct connectors + for lease. The fd must be mapped with MAP_PRIVATE by the recipient. + + Note that not all displays have an EDID, and this event will not be + sent in such cases. + + + + + + + + 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 + zwp_drm_lease_v1.finished without issuing a lease fd. + + + + + + The client may send this request to indicate that it will not issue a + lease request for this connector. Clients are encouraged to send this + after receiving the "withdrawn" request so that the server can release + the resources associated with this connector offer. + + + + + + + A client that wishes to lease DRM resources will attach the list of + connectors advertised with zwp_drm_lease_manager_v1.connector that they + wish to lease, then use zwp_drm_lease_request_v1.submit to submit the + request. + + + + + + + + + Indicates that the client will no longer use this lease 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. + + + + + + + Submits the lease request and creates a new zwp_drm_lease_v1 object. + After calling submit, issuing any other request than destroy is a + protocol error. + + + + + + + + A DRM lease object is used to transfer the DRM file descriptor to the + client and manage the lifetime of the lease. + + + + + 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. 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 immediately. + + It is a protocol error for the compositor to send this event more than + once for a given lease. + + + + + + + When the compositor revokes the lease, it will issue this event to + notify clients of the change. 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. + + + + + + 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, then release this object. + + + + diff --git a/protocol/meson.build b/protocol/meson.build index b7a0241e..b5f8f2d9 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -25,6 +25,7 @@ protocols = [ [wl_protocol_dir, 'unstable/xdg-decoration/xdg-decoration-unstable-v1.xml'], [wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'], [wl_protocol_dir, 'unstable/xdg-shell/xdg-shell-unstable-v6.xml'], + 'drm-lease-unstable-v1.xml', 'gtk-primary-selection.xml', 'idle.xml', 'input-method-unstable-v2.xml', diff --git a/types/meson.build b/types/meson.build index 94d37873..a658605f 100644 --- a/types/meson.build +++ b/types/meson.build @@ -28,6 +28,7 @@ lib_wlr_types = static_library( 'wlr_compositor.c', 'wlr_cursor.c', 'wlr_data_control_v1.c', + 'wlr_drm_lease_v1.c', 'wlr_export_dmabuf_v1.c', 'wlr_foreign_toplevel_management_v1.c', 'wlr_fullscreen_shell_v1.c', diff --git a/types/wlr_drm_lease_v1.c b/types/wlr_drm_lease_v1.c new file mode 100644 index 00000000..183eff21 --- /dev/null +++ b/types/wlr_drm_lease_v1.c @@ -0,0 +1,582 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "backend/drm/drm.h" +#include "drm-lease-unstable-v1-protocol.h" +#include "util/shm.h" +#include "util/signal.h" + +static struct zwp_drm_lease_manager_v1_interface lease_manager_impl; +static struct zwp_drm_lease_request_v1_interface lease_request_impl; +static struct zwp_drm_lease_connector_v1_interface lease_connector_impl; +static struct zwp_drm_lease_v1_interface lease_impl; + +static void drm_lease_connector_v1_send_to_client( + struct wlr_drm_lease_connector_v1 *connector, + struct wl_client *wl_client, struct wl_resource *manager); + +static struct wlr_drm_lease_manager_v1 *wlr_drm_lease_manager_v1_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwp_drm_lease_manager_v1_interface, &lease_manager_impl)); + return wl_resource_get_user_data(resource); +} + +static struct wlr_drm_lease_request_v1 *wlr_drm_lease_request_v1_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwp_drm_lease_request_v1_interface, &lease_request_impl)); + return wl_resource_get_user_data(resource); +} + +static struct wlr_drm_lease_connector_v1 * +wlr_drm_lease_connector_v1_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwp_drm_lease_connector_v1_interface, &lease_connector_impl)); + return wl_resource_get_user_data(resource); +} + +static struct wlr_drm_lease_v1 *wlr_drm_lease_v1_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwp_drm_lease_v1_interface, &lease_impl)); + return wl_resource_get_user_data(resource); +} + +static bool drm_lease_request_v1_validate( + struct wlr_drm_lease_request_v1 *req) { + if (req->invalid) { + return false; + } + /* Don't lease connectors which are already leased */ + struct wlr_drm_connector_lease_v1 *connector; + wl_list_for_each(connector, &req->connectors, link) { + if (connector->connector->active_lease) { + return false; + } + } + return true; +} + +static void lease_terminated_by_drm( + struct wlr_drm_connector *conn, void *data) { + wlr_log(WLR_DEBUG, "Lease terminated by DRM"); + struct wlr_drm_lease_v1 *lease = data; + lease->lessee_id = 0; + wlr_drm_lease_manager_v1_revoke_lease(lease->manager, lease); +} + +struct wlr_drm_lease_v1 *wlr_drm_lease_manager_v1_grant_lease_request( + struct wlr_drm_lease_manager_v1 *manager, + struct wlr_drm_lease_request_v1 *request) { + assert(manager && request); + assert(request->lease); + + struct wlr_drm_lease_v1 *lease = request->lease; + if (!drm_lease_request_v1_validate(request)) { + zwp_drm_lease_v1_send_finished(lease->resource); + return NULL; + } + + int nconns = 0; + + /** Adopt the connector leases from the lease request */ + struct wlr_drm_connector_lease_v1 *conn, *temp; + wl_list_for_each_safe(conn, temp, &request->connectors, link) { + wl_list_remove(&conn->link); + wl_list_init(&conn->link); + wl_list_insert(&lease->connectors, &conn->link); + ++nconns; + } + + struct wlr_drm_connector *conns[nconns + 1]; + int i = 0; + wl_list_for_each(conn, &lease->connectors, link) { + conns[i] = conn->connector->drm_connector; + ++i; + } + + int fd = drm_create_lease(manager->backend, + conns, nconns, &lease->lessee_id, + lease_terminated_by_drm, lease); + + if (fd < 0) { + wlr_log_errno(WLR_ERROR, "drm_create_lease failed"); + zwp_drm_lease_v1_send_finished(lease->resource); + return NULL; + } + + wl_list_for_each(conn, &lease->connectors, link) { + struct wlr_drm_lease_connector_v1 *conn_lease = + conn->connector; + conn_lease->active_lease = lease; + + struct wl_resource *wl_resource, *temp; + wl_resource_for_each_safe(wl_resource, temp, &conn_lease->resources) { + zwp_drm_lease_connector_v1_send_withdrawn(wl_resource); + wl_resource_set_user_data(wl_resource, NULL); + wl_list_remove(wl_resource_get_link(wl_resource)); + wl_list_init(wl_resource_get_link(wl_resource)); + } + } + + zwp_drm_lease_v1_send_lease_fd(lease->resource, fd); + close(fd); + return lease; +} + +void wlr_drm_lease_manager_v1_reject_lease_request( + struct wlr_drm_lease_manager_v1 *manager, + struct wlr_drm_lease_request_v1 *request) { + assert(manager && request); + assert(request->lease); + zwp_drm_lease_v1_send_finished(request->lease->resource); + request->invalid = true; +} + +void wlr_drm_lease_manager_v1_revoke_lease( + struct wlr_drm_lease_manager_v1 *manager, + struct wlr_drm_lease_v1 *lease) { + assert(manager && lease); + if (lease->resource != NULL) { + zwp_drm_lease_v1_send_finished(lease->resource); + } + if (lease->lessee_id != 0) { + if (drm_terminate_lease(manager->backend, lease->lessee_id) < 0) { + wlr_log_errno(WLR_DEBUG, "drm_terminate_lease"); + } + } + struct wlr_drm_connector_lease_v1 *conn; + wl_list_for_each(conn, &lease->connectors, link) { + struct wlr_drm_lease_connector_v1 *conn_lease = + conn->connector; + conn_lease->active_lease = NULL; + + struct wl_resource *wl_resource; + wl_resource_for_each(wl_resource, &manager->resources) { + struct wl_client *wl_client = wl_resource_get_client(wl_resource); + drm_lease_connector_v1_send_to_client( + conn_lease, wl_client, wl_resource); + } + } + wlr_signal_emit_safe(&lease->events.revoked, lease); +} + +static void drm_lease_v1_destroy(struct wlr_drm_lease_v1 *lease) { + wlr_drm_lease_manager_v1_revoke_lease(lease->manager, lease); + free(lease); +} + +static void drm_lease_v1_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_drm_lease_v1 *lease = wlr_drm_lease_v1_from_resource(resource); + wl_list_remove(wl_resource_get_link(resource)); + wl_list_init(wl_resource_get_link(resource)); + lease->resource = NULL; + drm_lease_v1_destroy(lease); +} + +static void drm_lease_v1_handle_destroy( + struct wl_client *client, struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static struct zwp_drm_lease_v1_interface lease_impl = { + .destroy = drm_lease_v1_handle_destroy, +}; + +static void drm_lease_request_v1_destroy(struct wlr_drm_lease_request_v1 *req) { + if (!req) { + return; + } + struct wlr_drm_connector_lease_v1 *conn, *temp; + wl_list_for_each_safe(conn, temp, &req->connectors, link) { + wl_list_remove(&conn->link); + wl_list_init(&conn->link); + free(conn); + } + free(req); +} + +static void drm_lease_request_v1_handle_resource_destroy( + struct wl_resource *resource) { + struct wlr_drm_lease_request_v1 *req = + wlr_drm_lease_request_v1_from_resource(resource); + drm_lease_request_v1_destroy(req); + wl_list_remove(wl_resource_get_link(resource)); + wl_list_init(wl_resource_get_link(resource)); +} + +static void drm_lease_request_v1_handle_destroy( + struct wl_client *client, struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void drm_lease_request_v1_handle_request_connector( + struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *connector) { + struct wlr_drm_lease_request_v1 *request = + wlr_drm_lease_request_v1_from_resource(resource); + struct wlr_drm_lease_connector_v1 *conn = + wlr_drm_lease_connector_v1_from_resource(connector); + + if (conn == NULL) { + /* This connector offer has been withdrawn */ + request->invalid = true; + return; + } + + struct wlr_drm_connector_lease_v1 *lease = + calloc(1, sizeof(struct wlr_drm_connector_lease_v1)); + if (!lease) { + wl_client_post_no_memory(client); + return; + } + + lease->connector = conn; + wl_list_insert(&request->connectors, &lease->link); +} + +static void drm_lease_request_v1_handle_submit( + struct wl_client *client, struct wl_resource *resource, uint32_t id) { + struct wlr_drm_lease_request_v1 *lease_request = + wlr_drm_lease_request_v1_from_resource(resource); + + struct wlr_drm_lease_v1 *lease = calloc(1, sizeof(struct wlr_drm_lease_v1)); + if (!lease) { + wl_resource_post_no_memory(resource); + return; + } + + struct wl_resource *wl_resource = wl_resource_create( + client, &zwp_drm_lease_v1_interface, 1, id); + if (!wl_resource) { + free(lease); + wl_resource_post_no_memory(resource); + return; + } + + wl_signal_init(&lease->events.revoked); + wl_list_init(&lease->connectors); + lease->manager = lease_request->manager; + lease->resource = wl_resource; + lease_request->lease = lease; + wl_list_insert(&lease->manager->leases, wl_resource_get_link(wl_resource)); + + wl_resource_set_implementation(wl_resource, &lease_impl, + lease, drm_lease_v1_handle_resource_destroy); + + if (!drm_lease_request_v1_validate(lease_request)) { + /* Pre-emptively reject invalid lease requests */ + zwp_drm_lease_v1_send_finished(lease->resource); + } else { + wlr_signal_emit_safe( + &lease_request->manager->events.lease_requested, + lease_request); + } +} + +static struct zwp_drm_lease_request_v1_interface lease_request_impl = { + .destroy = drm_lease_request_v1_handle_destroy, + .request_connector = drm_lease_request_v1_handle_request_connector, + .submit = drm_lease_request_v1_handle_submit, +}; + +static void drm_lease_manager_v1_validate_destroy( + struct wlr_drm_lease_manager_v1 *manager, struct wl_client *client) { + // TODO: send protocol error if there are any bound resources +} + +static void drm_lease_manager_v1_handle_resource_destroy( + struct wl_resource *resource) { + drm_lease_manager_v1_validate_destroy( + wlr_drm_lease_manager_v1_from_resource(resource), + wl_resource_get_client(resource)); + wl_list_remove(wl_resource_get_link(resource)); + wl_list_init(wl_resource_get_link(resource)); +} + +static void drm_lease_manager_v1_handle_stop( + struct wl_client *client, struct wl_resource *resource) { + zwp_drm_lease_manager_v1_send_finished(resource); + wl_resource_destroy(resource); +} + +void drm_lease_manager_v1_handle_create_lease_request( + struct wl_client *client, struct wl_resource *resource, uint32_t id) { + struct wlr_drm_lease_manager_v1 *manager = + wlr_drm_lease_manager_v1_from_resource(resource); + + struct wlr_drm_lease_request_v1 *req = + calloc(1, sizeof(struct wlr_drm_lease_request_v1)); + if (!req) { + wl_resource_post_no_memory(resource); + return; + } + + struct wl_resource *wl_resource = wl_resource_create(client, + &zwp_drm_lease_request_v1_interface, 1, id); + if (!wl_resource) { + wl_resource_post_no_memory(resource); + free(req); + return; + } + + req->manager = manager; + req->resource = wl_resource; + wl_list_init(&req->connectors); + + wl_resource_set_implementation(wl_resource, &lease_request_impl, + req, drm_lease_request_v1_handle_resource_destroy); + + wl_list_insert(&manager->lease_requests, wl_resource_get_link(wl_resource)); +} + +static struct zwp_drm_lease_manager_v1_interface lease_manager_impl = { + .stop = drm_lease_manager_v1_handle_stop, + .create_lease_request = drm_lease_manager_v1_handle_create_lease_request, +}; + +static void drm_connector_v1_handle_resource_destroy( + struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); + wl_list_init(wl_resource_get_link(resource)); +} + +static void drm_connector_v1_handle_destroy( + struct wl_client *client, struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static struct zwp_drm_lease_connector_v1_interface lease_connector_impl = { + .destroy = drm_connector_v1_handle_destroy, +}; + +static void drm_lease_connector_v1_send_to_client( + struct wlr_drm_lease_connector_v1 *connector, + struct wl_client *wl_client, struct wl_resource *manager) { + if (connector->active_lease) { + return; + } + + struct wl_resource *wl_resource = wl_resource_create(wl_client, + &zwp_drm_lease_connector_v1_interface, 1, 0); + wl_resource_set_implementation(wl_resource, &lease_connector_impl, + connector, drm_connector_v1_handle_resource_destroy); + zwp_drm_lease_manager_v1_send_connector(manager, wl_resource); + + struct wlr_output *output = connector->output; + zwp_drm_lease_connector_v1_send_name(wl_resource, output->name); + + char description[128]; + snprintf(description, sizeof(description), "%s %s %s (%s)", + output->make, output->model, output->serial, output->name); + zwp_drm_lease_connector_v1_send_description(wl_resource, description); + + zwp_drm_lease_connector_v1_send_connector_id( + wl_resource, connector->drm_connector->id); + + struct wlr_drm_lease_manager_v1 *lease_manager = + wlr_drm_lease_manager_v1_from_resource(manager); + struct wlr_drm_connector *conn = connector->drm_connector; + size_t edid_len = 0; + uint8_t *edid = get_drm_prop_blob(lease_manager->backend->fd, + conn->id, conn->props.edid, &edid_len); + int edid_fd = allocate_shm_file(edid_len); + void *ptr = mmap(NULL, edid_len, PROT_READ | PROT_WRITE, + MAP_SHARED, edid_fd, 0); + memcpy(ptr, edid, edid_len); + munmap(ptr, edid_len); + + zwp_drm_lease_connector_v1_send_edid(wl_resource, edid_fd, edid_len); + free(edid); + close(edid_fd); + + wl_list_insert(&connector->resources, wl_resource_get_link(wl_resource)); +} + +static void lease_manager_bind(struct wl_client *wl_client, void *data, + uint32_t version, uint32_t id) { + struct wlr_drm_lease_manager_v1 *lease_manager = data; + + struct wl_resource *wl_resource = wl_resource_create(wl_client, + &zwp_drm_lease_manager_v1_interface, version, id); + + if (!wl_resource) { + wl_client_post_no_memory(wl_client); + return; + } + + wl_list_insert(&lease_manager->resources, + wl_resource_get_link(wl_resource)); + + wl_resource_set_implementation(wl_resource, &lease_manager_impl, + lease_manager, drm_lease_manager_v1_handle_resource_destroy); + + struct wlr_drm_lease_connector_v1 *connector; + wl_list_for_each(connector, &lease_manager->connectors, link) { + drm_lease_connector_v1_send_to_client( + connector, wl_client, wl_resource); + } +} + +void wlr_drm_lease_manager_v1_offer_output( + struct wlr_drm_lease_manager_v1 *manager, struct wlr_output *output) { + assert(manager && output); + assert(wlr_output_is_drm(output)); + struct wlr_drm_connector *drm_connector = + (struct wlr_drm_connector *)output; + /* + * When the compositor grants a lease, we "destroy" all of the outputs on + * that lease. When the lease ends, the outputs re-appear. However, the + * underlying DRM connector remains the same. If the compositor offers + * outputs based on some criteria, then sees the output re-appear with the + * same critera, this code allows it to safely re-offer outputs which are + * backed by DRM connectors it has leased in the past. + */ + struct wlr_drm_lease_connector_v1 *connector; + wl_list_for_each(connector, &manager->connectors, link) { + if (connector->drm_connector == drm_connector) { + return; + } + } + + connector = calloc(1, sizeof(struct wlr_drm_lease_connector_v1)); + connector->drm_connector = drm_connector; + connector->output = &drm_connector->output; + wl_list_init(&connector->resources); + wl_list_insert(&manager->connectors, &connector->link); + + struct wl_resource *resource; + wl_resource_for_each(resource, &manager->resources) { + drm_lease_connector_v1_send_to_client( + connector, wl_resource_get_client(resource), resource); + } +} + +void wlr_drm_lease_manager_v1_widthraw_output( + struct wlr_drm_lease_manager_v1 *manager, struct wlr_output *output) { + struct wlr_drm_lease_connector_v1 *connector = NULL, *_connector; + wl_list_for_each(_connector, &manager->connectors, link) { + if (_connector->output == output) { + connector = _connector; + break; + } + } + if (!connector) { + return; + } + assert(connector->active_lease == NULL && "Cannot withdraw a leased output"); + + struct wl_resource *wl_resource, *temp; + wl_resource_for_each_safe(wl_resource, temp, &connector->resources) { + zwp_drm_lease_connector_v1_send_withdrawn(wl_resource); + wl_resource_set_user_data(wl_resource, NULL); + wl_list_remove(wl_resource_get_link(wl_resource)); + wl_list_init(wl_resource_get_link(wl_resource)); + } + + wl_resource_for_each(wl_resource, &manager->lease_requests) { + struct wlr_drm_lease_request_v1 *request = + wlr_drm_lease_request_v1_from_resource(wl_resource); + request->invalid = true; + } + + wl_list_remove(&connector->link); + wl_list_init(&connector->link); + free(connector); +} + +static void multi_backend_cb(struct wlr_backend *backend, void *data) { + struct wlr_backend **ptr = data; + if (wlr_backend_is_drm(backend)) { + *ptr = backend; + } +} + +struct wlr_drm_lease_manager_v1 *wlr_drm_lease_manager_v1_create( + struct wl_display *display, struct wlr_backend *backend) { + assert(display); + + if (!wlr_backend_is_drm(backend) && wlr_backend_is_multi(backend)) { + wlr_multi_for_each_backend(backend, multi_backend_cb, &backend); + if (!wlr_backend_is_drm(backend)) { + return NULL; + } + } else { + return NULL; + } + + struct wlr_drm_lease_manager_v1 *lease_manager = + calloc(1, sizeof(struct wlr_drm_lease_manager_v1)); + + if (!lease_manager) { + return NULL; + } + + lease_manager->backend = get_drm_backend_from_backend(backend); + wl_list_init(&lease_manager->resources); + wl_list_init(&lease_manager->connectors); + wl_list_init(&lease_manager->lease_requests); + wl_list_init(&lease_manager->leases); + + wl_signal_init(&lease_manager->events.lease_requested); + + lease_manager->global = wl_global_create(display, + &zwp_drm_lease_manager_v1_interface, 1, + lease_manager, lease_manager_bind); + + if (!lease_manager->global) { + free(lease_manager); + return NULL; + } + + return lease_manager; +} + +void wlr_drm_lease_manager_v1_destroy( + struct wlr_drm_lease_manager_v1 *manager) { + if (!manager) { + return; + } + + struct wl_resource *resource; + struct wl_resource *tmp_resource; + wl_resource_for_each_safe(resource, tmp_resource, &manager->resources) { + wl_resource_destroy(resource); + } + + wl_resource_for_each_safe(resource, tmp_resource, + &manager->lease_requests) { + wl_resource_destroy(resource); + } + + wl_resource_for_each_safe(resource, tmp_resource, &manager->leases) { + struct wlr_drm_lease_v1 *lease = + wlr_drm_lease_v1_from_resource(resource); + wlr_drm_lease_manager_v1_revoke_lease(manager, lease); + } + + struct wlr_drm_lease_connector_v1 *connector, *tmp_connector; + wl_list_for_each_safe(connector, tmp_connector, + &manager->connectors, link) { + wl_list_remove(&connector->link); + wl_list_init(&connector->link); + free(connector); + } + + free(manager); +}