diff --git a/src/wayland/CMakeLists.txt b/src/wayland/CMakeLists.txt index ddd8cef3dc..934b6d5a69 100644 --- a/src/wayland/CMakeLists.txt +++ b/src/wayland/CMakeLists.txt @@ -19,6 +19,7 @@ set(SERVER_LIB_SRCS display.cpp dpms_interface.cpp drmclientbuffer.cpp + drmleasedevice_v1_interface.cpp eglstream_controller_interface.cpp fakeinput_interface.cpp filtered_display.cpp @@ -278,6 +279,11 @@ ecm_add_qtwayland_server_protocol_kde(SERVER_LIB_SRCS BASENAME xdg-activation-v1 ) +ecm_add_qtwayland_server_protocol_kde(SERVER_LIB_SRCS + PROTOCOL ${WaylandProtocols_DATADIR}/staging/drm-lease/drm-lease-v1.xml + BASENAME drm-lease-v1 +) + add_library(KWaylandServer ${SERVER_LIB_SRCS}) add_library(Plasma::KWaylandServer ALIAS KWaylandServer) ecm_generate_export_header(KWaylandServer @@ -339,6 +345,7 @@ set(SERVER_LIB_HEADERS display.h dpms_interface.h drmclientbuffer.h + drmleasedevice_v1_interface.h eglstream_controller_interface.h fakeinput_interface.h filtered_display.h diff --git a/src/wayland/drmleasedevice_v1_interface.cpp b/src/wayland/drmleasedevice_v1_interface.cpp new file mode 100644 index 0000000000..bffd22cdb4 --- /dev/null +++ b/src/wayland/drmleasedevice_v1_interface.cpp @@ -0,0 +1,389 @@ +/* + SPDX-FileCopyrightText: 2021 Xaver Hugl + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#include "drmleasedevice_v1_interface.h" +#include "drmleasedevice_v1_interface_p.h" +#include "display.h" +#include "logging.h" +#include "utils.h" + +#include +#include + +namespace KWaylandServer +{ + +static const quint32 s_version = 1; + +DrmLeaseDeviceV1Interface::DrmLeaseDeviceV1Interface(Display *display, std::function createNonMasterFd) + : d(new DrmLeaseDeviceV1InterfacePrivate(display, this, createNonMasterFd)) +{ +} + +DrmLeaseDeviceV1Interface::~DrmLeaseDeviceV1Interface() +{ + d->remove(); +} + +void DrmLeaseDeviceV1Interface::setDrmMaster(bool hasDrmMaster) +{ + if (hasDrmMaster && !d->hasDrmMaster) { + // withdraw all connectors + for (const auto &connector : qAsConst(d->connectors)) { + DrmLeaseConnectorV1InterfacePrivate::get(connector)->withdraw(); + } + // and revoke all leases + for (const auto &lease : qAsConst(d->leases)) { + lease->deny(); + } + } else if (!hasDrmMaster && d->hasDrmMaster) { + // send pending drm fds + while (!d->pendingFds.isEmpty()) { + int fd = d->createNonMasterFd(); + d->send_drm_fd(d->pendingFds.dequeue(), fd); + close(fd); + } + // offer all connectors again + for (const auto &connector : qAsConst(d->connectors)) { + auto connectorPrivate = DrmLeaseConnectorV1InterfacePrivate::get(connector); + connectorPrivate->withdrawn = false; + for (const auto &resource : d->resourceMap()) { + auto connectorResource = connectorPrivate->add(resource->client(), 0, s_version); + d->send_connector(resource->handle, connectorResource->handle); + connectorPrivate->send(connectorResource->handle); + } + } + } + d->hasDrmMaster = hasDrmMaster; +} + + +DrmLeaseDeviceV1InterfacePrivate::DrmLeaseDeviceV1InterfacePrivate(Display *display, DrmLeaseDeviceV1Interface *device, std::function createNonMasterFd) + : QtWaylandServer::wp_drm_lease_device_v1(*display, s_version) + , q(device) + , createNonMasterFd(createNonMasterFd) +{ +} + +DrmLeaseDeviceV1InterfacePrivate::~DrmLeaseDeviceV1InterfacePrivate() +{ +} + +void DrmLeaseDeviceV1InterfacePrivate::remove() +{ + for (const auto &lease : qAsConst(leases)) { + lease->deny(); + } + for (const auto &connector : qAsConst(connectors)) { + DrmLeaseConnectorV1InterfacePrivate::get(connector)->withdraw(); + } + for (const auto &request : qAsConst(leaseRequests)) { + request->connectors.clear(); + } + globalRemove(); + removed = true; + if (resourceMap().isEmpty()) { + delete this; + } +} + +void DrmLeaseDeviceV1InterfacePrivate::registerConnector(DrmLeaseConnectorV1Interface *connector) +{ + connectors << connector; + if (!hasDrmMaster) { + return; + } + for (const auto &resource : resourceMap()) { + auto connectorPrivate = DrmLeaseConnectorV1InterfacePrivate::get(connector); + auto connectorResource = connectorPrivate->add(resource->client(), 0, resource->version()); + send_connector(resource->handle, connectorResource->handle); + connectorPrivate->send(connectorResource->handle); + } +} + +void DrmLeaseDeviceV1InterfacePrivate::unregisterConnector(DrmLeaseConnectorV1Interface *connector) +{ + connectors.removeOne(connector); + for (const auto &lease : qAsConst(leases)) { + if (lease->d->connectors.contains(connector)) { + lease->d->connectors.removeOne(connector); + lease->deny(); + } + } + for (const auto &leaseRequest : qAsConst(leaseRequests)) { + if (leaseRequest->connectors.removeOne(connector)) { + leaseRequest->invalid = true; + } + } +} + +DrmLeaseDeviceV1InterfacePrivate *DrmLeaseDeviceV1InterfacePrivate::get(DrmLeaseDeviceV1Interface *device) +{ + return device->d; +} + +void DrmLeaseDeviceV1InterfacePrivate::wp_drm_lease_device_v1_create_lease_request(Resource *resource, uint32_t id) +{ + wl_resource *requestResource = wl_resource_create(resource->client(), &wp_drm_lease_request_v1_interface, + resource->version(), id); + if (!requestResource) { + wl_resource_post_no_memory(resource->handle); + return; + } + leaseRequests << new DrmLeaseRequestV1Interface(this, requestResource); +} + +void DrmLeaseDeviceV1InterfacePrivate::wp_drm_lease_device_v1_release(Resource *resource) +{ + send_released(resource->handle); + wl_resource_destroy(resource->handle); +} + +void DrmLeaseDeviceV1InterfacePrivate::wp_drm_lease_device_v1_bind_resource(Resource *resource) +{ + if (!hasDrmMaster) { + pendingFds << resource->handle; + return; + } + int fd = createNonMasterFd(); + send_drm_fd(resource->handle, fd); + close(fd); + for (const auto &connector : qAsConst(connectors)) { + auto connectorPrivate = DrmLeaseConnectorV1InterfacePrivate::get(connector); + if (!connectorPrivate->withdrawn) { + auto connectorResource = connectorPrivate->add(resource->client(), 0, s_version); + send_connector(resource->handle, connectorResource->handle); + connectorPrivate->send(connectorResource->handle); + } + } +} + +void DrmLeaseDeviceV1InterfacePrivate::wp_drm_lease_device_v1_destroy_resource(Resource *resource) +{ + Q_UNUSED(resource) + if (removed && resourceMap().isEmpty()) { + delete this; + } +} + + +DrmLeaseConnectorV1Interface::DrmLeaseConnectorV1Interface(DrmLeaseDeviceV1Interface *leaseDevice, + uint32_t id, + const QString &name, + const QString &description) + : d(new DrmLeaseConnectorV1InterfacePrivate(leaseDevice, this, id, name, description)) +{ + DrmLeaseDeviceV1InterfacePrivate::get(leaseDevice)->registerConnector(this); +} + +DrmLeaseConnectorV1Interface::~DrmLeaseConnectorV1Interface() +{ + d->withdraw(); + if (d->device) { + auto devicePrivate = DrmLeaseDeviceV1InterfacePrivate::get(d->device); + devicePrivate->unregisterConnector(this); + } +} + +DrmLeaseConnectorV1Interface *DrmLeaseConnectorV1Interface::get(wl_resource *resource) +{ + if (auto connectorPrivate = resource_cast(resource)) { + return connectorPrivate->q; + } + return nullptr; +} + +DrmLeaseConnectorV1InterfacePrivate::DrmLeaseConnectorV1InterfacePrivate(DrmLeaseDeviceV1Interface *device, + DrmLeaseConnectorV1Interface *connector, + uint32_t connectorId, + const QString &name, + const QString &description) + : wp_drm_lease_connector_v1() + , q(connector) + , device(device) + , connectorId(connectorId) + , name(name) + , description(description) +{ +} + +DrmLeaseConnectorV1InterfacePrivate::~DrmLeaseConnectorV1InterfacePrivate() +{ +} + +void DrmLeaseConnectorV1InterfacePrivate::send(wl_resource *resource) +{ + send_connector_id(resource, connectorId); + send_name(resource, name); + send_description(resource, description); + send_done(resource); +} + +void DrmLeaseConnectorV1InterfacePrivate::withdraw() +{ + if (!withdrawn) { + withdrawn = true; + for (const auto &resource : resourceMap()) { + send_withdrawn(resource->handle); + DrmLeaseDeviceV1InterfacePrivate::get(device)->send_done(resource->handle); + } + } +} + +DrmLeaseConnectorV1InterfacePrivate *DrmLeaseConnectorV1InterfacePrivate::get(DrmLeaseConnectorV1Interface *connector) +{ + return connector->d.get(); +} + +void DrmLeaseConnectorV1InterfacePrivate::wp_drm_lease_connector_v1_destroy(Resource *resource) +{ + wl_resource_destroy(resource->handle); +} + + +DrmLeaseRequestV1Interface::DrmLeaseRequestV1Interface(DrmLeaseDeviceV1InterfacePrivate *device, wl_resource *resource) + : wp_drm_lease_request_v1(resource) + , device(device) +{ +} + +DrmLeaseRequestV1Interface::~DrmLeaseRequestV1Interface() +{ + device->leaseRequests.removeOne(this); +} + +void DrmLeaseRequestV1Interface::wp_drm_lease_request_v1_request_connector(Resource *resource, struct ::wl_resource *connector_handle) +{ + Q_UNUSED(resource); + if (auto connector = DrmLeaseConnectorV1Interface::get(connector_handle)) { + auto connectorPrivate = DrmLeaseConnectorV1InterfacePrivate::get(connector); + if (connectorPrivate->device != device->q) { + wl_resource_post_error(resource->handle, WP_DRM_LEASE_REQUEST_V1_ERROR_WRONG_DEVICE, "Requested connector from invalid lease device"); + } else if (connectorPrivate->withdrawn) { + qCWarning(KWAYLAND_SERVER) << "DrmLease: withdrawn connector requested"; + } else if (connectors.contains(connector)) { + wl_resource_post_error(resource->handle, WP_DRM_LEASE_REQUEST_V1_ERROR_DUPLICATE_CONNECTOR, "Requested connector twice"); + } else { + connectors << connector; + } + } else { + qCWarning(KWAYLAND_SERVER, "DrmLease: Invalid connector requested"); + } +} + +void DrmLeaseRequestV1Interface::wp_drm_lease_request_v1_submit(Resource *resource, uint32_t id) +{ + wl_resource *leaseResource = wl_resource_create(resource->client(), &wp_drm_lease_v1_interface, s_version, id); + if (!leaseResource) { + wl_resource_post_no_memory(resource->handle); + return; + } + DrmLeaseV1Interface *lease = new DrmLeaseV1Interface(device, leaseResource); + device->leases << lease; + if (!device->hasDrmMaster) { + qCWarning(KWAYLAND_SERVER) << "DrmLease: rejecting lease request without drm master"; + lease->deny(); + } else if (invalid) { + qCWarning(KWAYLAND_SERVER()) << "DrmLease: rejecting lease request with a withdrawn connector"; + lease->deny(); + } else if (connectors.isEmpty()) { + wl_resource_post_error(resource->handle, WP_DRM_LEASE_REQUEST_V1_ERROR_EMPTY_LEASE, "Requested lease without connectors"); + } else { + lease->d->connectors = connectors; + emit device->q->leaseRequested(lease); + } + wl_resource_destroy(resource->handle); +} + +void DrmLeaseRequestV1Interface::wp_drm_lease_request_v1_destroy_resource(Resource *resource) +{ + Q_UNUSED(resource) + delete this; +} + +DrmLeaseV1Interface::DrmLeaseV1Interface(DrmLeaseDeviceV1InterfacePrivate *device, wl_resource *resource) + : d(new DrmLeaseV1InterfacePrivate(device, this, resource)) +{ +} + +DrmLeaseV1Interface::~DrmLeaseV1Interface() +{ + deny(); + d->device->leases.removeOne(this); +} + +void DrmLeaseV1Interface::grant(int leaseFd, uint32_t lesseeId) +{ + d->send_lease_fd(leaseFd); + close(leaseFd); + d->lesseeId = lesseeId; + for (const auto &connector : qAsConst(d->connectors)) { + DrmLeaseConnectorV1InterfacePrivate::get(connector)->withdraw(); + } +} + +void DrmLeaseV1Interface::deny() +{ + if (!d->finished) { + d->finished = true; + d->send_finished(); + } + if (!d->lesseeId) { + return; + } + Q_EMIT d->device->q->leaseRevoked(this); + // check if we should offer connectors again + if (d->device->hasDrmMaster) { + bool sent = false; + for (const auto &connector : qAsConst(d->connectors)) { + auto connectorPrivate = DrmLeaseConnectorV1InterfacePrivate::get(connector); + connectorPrivate->withdrawn = false; + for (const auto &resource : d->device->resourceMap()) { + auto connectorResource = connectorPrivate->add(resource->client(), 0, s_version); + d->device->send_connector(resource->handle, connectorResource->handle); + connectorPrivate->send(connectorResource->handle); + sent = true; + } + } + if (sent) { + for (const auto &resource : d->device->resourceMap()) { + d->device->send_done(resource->handle); + } + } + } + d->lesseeId = 0; +} + +uint32_t DrmLeaseV1Interface::lesseeId() const +{ + return d->lesseeId; +} + +QVector DrmLeaseV1Interface::connectors() const +{ + return d->connectors; +} + + +DrmLeaseV1InterfacePrivate::DrmLeaseV1InterfacePrivate(DrmLeaseDeviceV1InterfacePrivate *device, DrmLeaseV1Interface *q, wl_resource *resource) + : wp_drm_lease_v1(resource) + , device(device) + , q(q) +{ +} + +void DrmLeaseV1InterfacePrivate::wp_drm_lease_v1_destroy(Resource *resource) +{ + wl_resource_destroy(resource->handle); +} + +void DrmLeaseV1InterfacePrivate::wp_drm_lease_v1_destroy_resource(Resource *resource) +{ + Q_UNUSED(resource) + delete q; +} + +} diff --git a/src/wayland/drmleasedevice_v1_interface.h b/src/wayland/drmleasedevice_v1_interface.h new file mode 100644 index 0000000000..8ab3b8a871 --- /dev/null +++ b/src/wayland/drmleasedevice_v1_interface.h @@ -0,0 +1,122 @@ +/* + SPDX-FileCopyrightText: 2021 Xaver Hugl + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#pragma once + +#include + +#include +#include + +struct wl_resource; + +namespace KWaylandServer +{ + +class Display; +class DrmLeaseDeviceV1InterfacePrivate; +class DrmLeaseV1Interface; +class DrmLeaseV1InterfacePrivate; +class DrmLeaseRequestV1Interface; +class DrmLeaseConnectorV1InterfacePrivate; + +/** + * The DrmLeaseV1DeviceInterface allows the wayland compositor to offer unused + * drm connectors for lease by clients. The main use for this is VR headsets + */ +class KWAYLANDSERVER_EXPORT DrmLeaseDeviceV1Interface : public QObject +{ + Q_OBJECT +public: + /** + * @param createNonMasterFd a function that creates non-master drm file descriptors for + * this device that clients can use to enumerate connectors and their properties + */ + explicit DrmLeaseDeviceV1Interface(Display *display, std::function createNonMasterFd); + ~DrmLeaseDeviceV1Interface() override; + + /** + * Must be called by the compositor when it loses or gains drm master + */ + void setDrmMaster(bool hasDrmMaster); + +Q_SIGNALS: + /** + * Emitted when a lease is requested. The compositor needs to either + * grant or deny the lease when receiving this signal + */ + void leaseRequested(DrmLeaseV1Interface *leaseRequest); + + /** + * Emitted when a granted lease gets revoked + */ + void leaseRevoked(DrmLeaseV1Interface *lease); + +private: + friend class DrmLeaseDeviceV1InterfacePrivate; + DrmLeaseDeviceV1InterfacePrivate *d; +}; + +/** + * Represents a lease offer from the compositor. Creating the DrmLeaseConnectorV1Interface + * will allow clients to requests a lease for the connector, deleting it will result in the + * offer and possibly an active lease being revoked + */ +class KWAYLANDSERVER_EXPORT DrmLeaseConnectorV1Interface : public QObject +{ + Q_OBJECT +public: + explicit DrmLeaseConnectorV1Interface(DrmLeaseDeviceV1Interface *leaseDevice, uint32_t id, const QString &name, const QString &description); + ~DrmLeaseConnectorV1Interface() override; + + static DrmLeaseConnectorV1Interface *get(wl_resource *resource); + +private: + friend class DrmLeaseConnectorV1InterfacePrivate; + QScopedPointer d; +}; + +/** + * Represents a lease request or active lease + */ +class KWAYLANDSERVER_EXPORT DrmLeaseV1Interface : public QObject +{ + Q_OBJECT +public: + /** + * Grant the client requesting the lease access to DRM resources needed to + * drive the outputs corresponding to the requested connectors. + * Must only be called once in response to DrmLeaseDeviceV1Interface::leaseRequested + */ + void grant(int leaseFd, uint32_t lesseeId); + + /** + * Deny the lease request. The compositor may call this in response to + * DrmLeaseDeviceV1Interface::leaseRequested or when it detects a lease being ended with libdrm + */ + void deny(); + + /** + * The connectors this lease (request) encompasses + */ + QVector connectors() const; + + /** + * The lesseeId passed to DrmLeaseV1Interface::grant, or 0 if this lease was not granted + */ + uint32_t lesseeId() const; + +private: + DrmLeaseV1Interface(DrmLeaseDeviceV1InterfacePrivate *device, wl_resource *resource); + ~DrmLeaseV1Interface(); + + friend class DrmLeaseDeviceV1InterfacePrivate; + friend class DrmLeaseRequestV1Interface; + friend class DrmLeaseV1InterfacePrivate; + QScopedPointer d; +}; + +} diff --git a/src/wayland/drmleasedevice_v1_interface_p.h b/src/wayland/drmleasedevice_v1_interface_p.h new file mode 100644 index 0000000000..4bef36e401 --- /dev/null +++ b/src/wayland/drmleasedevice_v1_interface_p.h @@ -0,0 +1,104 @@ +/* + SPDX-FileCopyrightText: 2021 Xaver Hugl + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#pragma once + +#include + +#include +#include +#include + +namespace KWaylandServer +{ + +class Display; +class DrmLeaseConnectorV1Interface; +class DrmLeaseRequestV1Interface; +class DrmLeaseV1Interface; + +class DrmLeaseDeviceV1InterfacePrivate : public QtWaylandServer::wp_drm_lease_device_v1 +{ +public: + DrmLeaseDeviceV1InterfacePrivate(Display *display, DrmLeaseDeviceV1Interface *device, std::function createNonMasterFd); + ~DrmLeaseDeviceV1InterfacePrivate(); + void remove(); + + void registerConnector(DrmLeaseConnectorV1Interface *connector); + void unregisterConnector(DrmLeaseConnectorV1Interface *connector); + + static DrmLeaseDeviceV1InterfacePrivate *get(DrmLeaseDeviceV1Interface *device); + + DrmLeaseDeviceV1Interface *q; + QVector connectors; + QVector leaseRequests; + QVector leases; + QQueue pendingFds; + std::function createNonMasterFd; + bool hasDrmMaster = true; + bool removed = false; +protected: + void wp_drm_lease_device_v1_create_lease_request(Resource *resource, uint32_t id) override; + void wp_drm_lease_device_v1_release(Resource *resource) override; + void wp_drm_lease_device_v1_bind_resource(Resource *resource) override; + void wp_drm_lease_device_v1_destroy_resource(Resource *resource) override; +}; + +class DrmLeaseConnectorV1InterfacePrivate : public QObject, public QtWaylandServer::wp_drm_lease_connector_v1 +{ + Q_OBJECT +public: + DrmLeaseConnectorV1InterfacePrivate(DrmLeaseDeviceV1Interface *device, DrmLeaseConnectorV1Interface *connector, + uint32_t connectorId, const QString &name, const QString &description); + ~DrmLeaseConnectorV1InterfacePrivate(); + + void send(wl_resource *resource); + void withdraw(); + + static DrmLeaseConnectorV1InterfacePrivate *get(DrmLeaseConnectorV1Interface *connector); + + DrmLeaseConnectorV1Interface *q; + QPointer device; + uint32_t connectorId; + QString name; + QString description; + bool withdrawn = false; +protected: + void wp_drm_lease_connector_v1_destroy(Resource *resource) override; +}; + +class DrmLeaseRequestV1Interface : public QtWaylandServer::wp_drm_lease_request_v1 +{ +public: + DrmLeaseRequestV1Interface(DrmLeaseDeviceV1InterfacePrivate *device, wl_resource *resource); + ~DrmLeaseRequestV1Interface(); + + DrmLeaseDeviceV1InterfacePrivate *device; + QVector connectors; + bool invalid = false; +protected: + void wp_drm_lease_request_v1_request_connector(Resource *resource, struct ::wl_resource *connector) override; + void wp_drm_lease_request_v1_submit(Resource *resource, uint32_t id) override; + void wp_drm_lease_request_v1_destroy_resource(Resource *resource) override; +}; + +class DrmLeaseV1InterfacePrivate : public QtWaylandServer::wp_drm_lease_v1 +{ +public: + DrmLeaseV1InterfacePrivate(DrmLeaseDeviceV1InterfacePrivate *device, DrmLeaseV1Interface *q, wl_resource *resource); + + DrmLeaseDeviceV1InterfacePrivate *device; + DrmLeaseV1Interface *q; + QVector connectors; + uint32_t lesseeId = 0; + bool finished = false; +protected: + void wp_drm_lease_v1_destroy(Resource *resource) override; + void wp_drm_lease_v1_destroy_resource(Resource *resource) override; +}; + +} +