From 92ec75ba8bfc06896cb50e6484486dc2b8d37f95 Mon Sep 17 00:00:00 2001 From: Jie Liu Date: Tue, 13 Aug 2024 13:39:53 +0800 Subject: [PATCH] Add support for wlr-data-control-unstable-v1 protocol [ChangeLog][Third-Party Code] New protocol synced from wlroots. Change-Id: Ia5f43e62f98e9c95ebd02077ec6dc132e13cf5e0 Reviewed-by: David Edmundson --- .../wlr-data-control/qt_attribution.json | 18 ++ .../wlr-data-control-unstable-v1.xml | 278 ++++++++++++++++++ src/plugins/platforms/wayland/CMakeLists.txt | 3 + .../platforms/wayland/qwaylandclipboard.cpp | 44 ++- .../wayland/qwaylanddatacontrolv1.cpp | 161 ++++++++++ .../wayland/qwaylanddatacontrolv1_p.h | 119 ++++++++ .../platforms/wayland/qwaylanddisplay.cpp | 18 ++ .../platforms/wayland/qwaylanddisplay_p.h | 14 + .../platforms/wayland/qwaylandinputdevice.cpp | 23 ++ .../platforms/wayland/qwaylandinputdevice_p.h | 13 + 10 files changed, 684 insertions(+), 7 deletions(-) create mode 100644 src/3rdparty/wayland/protocols/wlr-data-control/qt_attribution.json create mode 100644 src/3rdparty/wayland/protocols/wlr-data-control/wlr-data-control-unstable-v1.xml create mode 100644 src/plugins/platforms/wayland/qwaylanddatacontrolv1.cpp create mode 100644 src/plugins/platforms/wayland/qwaylanddatacontrolv1_p.h diff --git a/src/3rdparty/wayland/protocols/wlr-data-control/qt_attribution.json b/src/3rdparty/wayland/protocols/wlr-data-control/qt_attribution.json new file mode 100644 index 00000000000..eea2dde6ef6 --- /dev/null +++ b/src/3rdparty/wayland/protocols/wlr-data-control/qt_attribution.json @@ -0,0 +1,18 @@ +[ + { + "Id": "wlr-data-control-unstable-v1-protocol", + "Name": "Wlr Data Control Unstable V1 Protocol", + "QDocModule": "qtwaylandcompositor", + "QtUsage": "Used in the Qt Wayland platform plugin", + "Files": "wlr-data-control-unstable-v1.xml", + + "Description": "This protocol allows a privileged client to control data devices.", + "Homepage": "https://gitlab.freedesktop.org/wlroots/wlr-protocols/", + "Version": "2", + "DownloadLocation": "https://gitlab.freedesktop.org/wlroots/wlr-protocols/-/raw/master/unstable/wlr-data-control-unstable-v1.xml", + "LicenseId": "MIT", + "License": "MIT License", + "LicenseFile": "../MIT_LICENSE.txt", + "Copyright": "Copyright © 2018 Simon Ser\nCopyright © 2019 Ivan Molodetskikht" + } +] \ No newline at end of file diff --git a/src/3rdparty/wayland/protocols/wlr-data-control/wlr-data-control-unstable-v1.xml b/src/3rdparty/wayland/protocols/wlr-data-control/wlr-data-control-unstable-v1.xml new file mode 100644 index 00000000000..75e8671b0de --- /dev/null +++ b/src/3rdparty/wayland/protocols/wlr-data-control/wlr-data-control-unstable-v1.xml @@ -0,0 +1,278 @@ + + + + Copyright © 2018 Simon Ser + Copyright © 2019 Ivan Molodetskikh + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + This protocol allows a privileged client to control data devices. In + particular, the client will be able to manage the current selection and take + the role of a clipboard manager. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + This interface is a manager that allows creating per-seat data device + controls. + + + + + Create a new data source. + + + + + + + Create a data device that can be used to manage a seat's selection. + + + + + + + + All objects created by the manager will still remain valid, until their + appropriate destroy request has been called. + + + + + + + This interface allows a client to manage a seat's selection. + + When the seat is destroyed, this object becomes inert. + + + + + This request asks the compositor to set the selection to the data from + the source on behalf of the client. + + The given source may not be used in any further set_selection or + set_primary_selection requests. Attempting to use a previously used + source is a protocol error. + + To unset the selection, set the source to NULL. + + + + + + + Destroys the data device object. + + + + + + The data_offer event introduces a new wlr_data_control_offer object, + which will subsequently be used in either the + wlr_data_control_device.selection event (for the regular clipboard + selections) or the wlr_data_control_device.primary_selection event (for + the primary clipboard selections). Immediately following the + wlr_data_control_device.data_offer event, the new data_offer object + will send out wlr_data_control_offer.offer events to describe the MIME + types it offers. + + + + + + + The selection event is sent out to notify the client of a new + wlr_data_control_offer for the selection for this device. The + wlr_data_control_device.data_offer and the wlr_data_control_offer.offer + events are sent out immediately before this event to introduce the data + offer object. The selection event is sent to a client when a new + selection is set. The wlr_data_control_offer is valid until a new + wlr_data_control_offer or NULL is received. The client must destroy the + previous selection wlr_data_control_offer, if any, upon receiving this + event. + + The first selection event is sent upon binding the + wlr_data_control_device object. + + + + + + + This data control object is no longer valid and should be destroyed by + the client. + + + + + + + + The primary_selection event is sent out to notify the client of a new + wlr_data_control_offer for the primary selection for this device. The + wlr_data_control_device.data_offer and the wlr_data_control_offer.offer + events are sent out immediately before this event to introduce the data + offer object. The primary_selection event is sent to a client when a + new primary selection is set. The wlr_data_control_offer is valid until + a new wlr_data_control_offer or NULL is received. The client must + destroy the previous primary selection wlr_data_control_offer, if any, + upon receiving this event. + + If the compositor supports primary selection, the first + primary_selection event is sent upon binding the + wlr_data_control_device object. + + + + + + + This request asks the compositor to set the primary selection to the + data from the source on behalf of the client. + + The given source may not be used in any further set_selection or + set_primary_selection requests. Attempting to use a previously used + source is a protocol error. + + To unset the primary selection, set the source to NULL. + + The compositor will ignore this request if it does not support primary + selection. + + + + + + + + + + + + The wlr_data_control_source object is the source side of a + wlr_data_control_offer. It is created by the source client in a data + transfer and provides a way to describe the offered data and a way to + respond to requests to transfer the data. + + + + + + + + + This request adds a MIME type to the set of MIME types advertised to + targets. Can be called several times to offer multiple types. + + Calling this after wlr_data_control_device.set_selection is a protocol + error. + + + + + + + Destroys the data source object. + + + + + + Request for data from the client. Send the data as the specified MIME + type over the passed file descriptor, then close it. + + + + + + + + This data source is no longer valid. The data source has been replaced + by another data source. + + The client should clean up and destroy this data source. + + + + + + + A wlr_data_control_offer represents a piece of data offered for transfer + by another client (the source client). The offer describes the different + MIME types that the data can be converted to and provides the mechanism + for transferring the data directly from the source client. + + + + + To transfer the offered data, the client issues this request and + indicates the MIME type it wants to receive. The transfer happens + through the passed file descriptor (typically created with the pipe + system call). The source client writes the data in the MIME type + representation requested and then closes the file descriptor. + + The receiving client reads from the read end of the pipe until EOF and + then closes its end, at which point the transfer is complete. + + This request may happen multiple times for different MIME types. + + + + + + + + Destroys the data offer object. + + + + + + Sent immediately after creating the wlr_data_control_offer object. + One event per offered MIME type. + + + + + diff --git a/src/plugins/platforms/wayland/CMakeLists.txt b/src/plugins/platforms/wayland/CMakeLists.txt index 4dfc54bfcb5..90cc673cb3e 100644 --- a/src/plugins/platforms/wayland/CMakeLists.txt +++ b/src/plugins/platforms/wayland/CMakeLists.txt @@ -28,6 +28,7 @@ qt_internal_add_module(WaylandClient qwaylandabstractdecoration.cpp qwaylandabstractdecoration_p.h qwaylandappmenu.cpp qwaylandappmenu_p.h qwaylandbuffer.cpp qwaylandbuffer_p.h + qwaylanddatacontrolv1.cpp qwaylanddatacontrolv1_p.h qwaylanddecorationfactory.cpp qwaylanddecorationfactory_p.h qwaylanddecorationplugin.cpp qwaylanddecorationplugin_p.h qwaylanddisplay.cpp qwaylanddisplay_p.h @@ -92,6 +93,7 @@ qt_internal_add_module(WaylandClient ../3rdparty/protocol/fractional-scale ../3rdparty/protocol/viewporter ../3rdparty/protocol/xdg-shell + ../3rdparty/protocol/wlr-data-control ) qt6_generate_wayland_protocol_client_sources(WaylandClient @@ -109,6 +111,7 @@ qt6_generate_wayland_protocol_client_sources(WaylandClient ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/xdg-output/xdg-output-unstable-v1.xml ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/fractional-scale/fractional-scale-v1.xml ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/viewporter/viewporter.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/wlr-data-control/wlr-data-control-unstable-v1.xml ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/xdg-shell/xdg-shell.xml ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/xdg-system-bell/xdg-system-bell-v1.xml ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/xdg-toplevel-drag/xdg-toplevel-drag-v1.xml diff --git a/src/plugins/platforms/wayland/qwaylandclipboard.cpp b/src/plugins/platforms/wayland/qwaylandclipboard.cpp index a1730c77fd7..a1737ef69eb 100644 --- a/src/plugins/platforms/wayland/qwaylandclipboard.cpp +++ b/src/plugins/platforms/wayland/qwaylandclipboard.cpp @@ -1,9 +1,11 @@ // Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2024 Jie Liu // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qwaylandclipboard_p.h" #include "qwaylanddisplay_p.h" #include "qwaylandinputdevice_p.h" +#include "qwaylanddatacontrolv1_p.h" #include "qwaylanddataoffer_p.h" #include "qwaylanddatasource_p.h" #include "qwaylanddatadevice_p.h" @@ -37,6 +39,12 @@ QMimeData *QWaylandClipboard::mimeData(QClipboard::Mode mode) switch (mode) { case QClipboard::Clipboard: + if (auto *dataControlDevice = seat->dataControlDevice()) { + if (dataControlDevice->selectionSource()) + return m_clientClipboard[QClipboard::Clipboard]; + if (auto *offer = dataControlDevice->selectionOffer()) + return offer->mimeData(); + } if (auto *dataDevice = seat->dataDevice()) { if (dataDevice->selectionSource()) return m_clientClipboard[QClipboard::Clipboard]; @@ -45,6 +53,12 @@ QMimeData *QWaylandClipboard::mimeData(QClipboard::Mode mode) } return &m_emptyData; case QClipboard::Selection: + if (auto *dataControlDevice = seat->dataControlDevice()) { + if (dataControlDevice->primarySelectionSource()) + return m_clientClipboard[QClipboard::Selection]; + if (auto *offer = dataControlDevice->primarySelectionOffer()) + return offer->mimeData(); + } #if QT_CONFIG(wayland_client_primary_selection) if (auto *selectionDevice = seat->primarySelectionDevice()) { if (selectionDevice->selectionSource()) @@ -84,20 +98,28 @@ void QWaylandClipboard::setMimeData(QMimeData *data, QClipboard::Mode mode) switch (mode) { case QClipboard::Clipboard: - if (auto *dataDevice = seat->dataDevice()) { + if (auto *dataControlDevice = seat->dataControlDevice()) { + dataControlDevice->setSelectionSource(data ? new QWaylandDataControlSourceV1(mDisplay->dataControlManager(), + m_clientClipboard[QClipboard::Clipboard]) : nullptr); + emitChanged(mode); + } else if (auto *dataDevice = seat->dataDevice()) { dataDevice->setSelectionSource(data ? new QWaylandDataSource(mDisplay->dndSelectionHandler(), m_clientClipboard[QClipboard::Clipboard]) : nullptr); emitChanged(mode); } break; case QClipboard::Selection: + if (auto *dataControlDevice = seat->dataControlDevice()) { + dataControlDevice->setPrimarySelectionSource(data ? new QWaylandDataControlSourceV1(mDisplay->dataControlManager(), + m_clientClipboard[QClipboard::Selection]) : nullptr); + emitChanged(mode); #if QT_CONFIG(wayland_client_primary_selection) - if (auto *selectionDevice = seat->primarySelectionDevice()) { + } else if (auto *selectionDevice = seat->primarySelectionDevice()) { selectionDevice->setSelectionSource(data ? new QWaylandPrimarySelectionSourceV1(mDisplay->primarySelectionManager(), m_clientClipboard[QClipboard::Selection]) : nullptr); emitChanged(mode); - } #endif + } break; default: break; @@ -106,12 +128,18 @@ void QWaylandClipboard::setMimeData(QMimeData *data, QClipboard::Mode mode) bool QWaylandClipboard::supportsMode(QClipboard::Mode mode) const { -#if QT_CONFIG(wayland_client_primary_selection) if (mode == QClipboard::Selection) { auto *seat = mDisplay->currentInputDevice(); - return seat && seat->primarySelectionDevice(); - } + if (!seat) + return false; + if (seat->dataControlDevice()) + return true; +#if QT_CONFIG(wayland_client_primary_selection) + if (seat->primarySelectionDevice()) + return true; #endif + return false; + } return mode == QClipboard::Clipboard; } @@ -124,8 +152,10 @@ bool QWaylandClipboard::ownsMode(QClipboard::Mode mode) const switch (mode) { case QClipboard::Clipboard: return seat->dataDevice() && seat->dataDevice()->selectionSource() != nullptr; -#if QT_CONFIG(wayland_client_primary_selection) case QClipboard::Selection: + if (seat->dataControlDevice() && seat->dataControlDevice()->primarySelectionSource() != nullptr) + return true; +#if QT_CONFIG(wayland_client_primary_selection) return seat->primarySelectionDevice() && seat->primarySelectionDevice()->selectionSource() != nullptr; #endif default: diff --git a/src/plugins/platforms/wayland/qwaylanddatacontrolv1.cpp b/src/plugins/platforms/wayland/qwaylanddatacontrolv1.cpp new file mode 100644 index 00000000000..6d2b60b2db8 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylanddatacontrolv1.cpp @@ -0,0 +1,161 @@ +// Copyright (C) 2024 Jie Liu +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qwaylanddatacontrolv1_p.h" +#include "qwaylandinputdevice_p.h" +#include "qwaylanddisplay_p.h" +#include "qwaylandmimehelper_p.h" + +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandDataControlManagerV1::QWaylandDataControlManagerV1(QWaylandDisplay *display, uint id, uint version) + : zwlr_data_control_manager_v1(display->wl_registry(), id, qMin(version, uint(2))) + , m_display(display) +{ +} + +QWaylandDataControlDeviceV1 *QWaylandDataControlManagerV1::createDevice(QWaylandInputDevice *seat) +{ + return new QWaylandDataControlDeviceV1(this, seat); +} + +QWaylandDataControlOfferV1::QWaylandDataControlOfferV1(QWaylandDisplay *display, ::zwlr_data_control_offer_v1 *offer) + : zwlr_data_control_offer_v1(offer) + , m_display(display) + , m_mimeData(new QWaylandMimeData(this)) +{} + +void QWaylandDataControlOfferV1::startReceiving(const QString &mimeType, int fd) +{ + receive(mimeType, fd); + wl_display_flush(m_display->wl_display()); +} + +void QWaylandDataControlOfferV1::zwlr_data_control_offer_v1_offer(const QString &mime_type) +{ + m_mimeData->appendFormat(mime_type); +} + +QWaylandDataControlDeviceV1::QWaylandDataControlDeviceV1( + QWaylandDataControlManagerV1 *manager, QWaylandInputDevice *seat) + : QtWayland::zwlr_data_control_device_v1(manager->get_data_device(seat->wl_seat())) + , m_display(manager->display()) + , m_seat(seat) +{ +} + +QWaylandDataControlDeviceV1::~QWaylandDataControlDeviceV1() +{ + destroy(); +} + +void QWaylandDataControlDeviceV1::invalidateSelectionOffer() +{ + if (!m_selectionOffer) + return; + + m_selectionOffer.reset(); + QGuiApplicationPrivate::platformIntegration()->clipboard()->emitChanged(QClipboard::Selection); +} + +void QWaylandDataControlDeviceV1::setSelectionSource(QWaylandDataControlSourceV1 *source) +{ + if (source) { + connect(source, &QWaylandDataControlSourceV1::cancelled, this, [this]() { + m_selectionSource.reset(); + QGuiApplicationPrivate::platformIntegration()->clipboard()->emitChanged(QClipboard::Clipboard); + }); + } + set_selection(source ? source->object() : nullptr); + m_selectionSource.reset(source); +} + +void QWaylandDataControlDeviceV1::setPrimarySelectionSource(QWaylandDataControlSourceV1 *source) +{ + if (source) { + connect(source, &QWaylandDataControlSourceV1::cancelled, this, [this]() { + m_primarySelectionSource.reset(); + QGuiApplicationPrivate::platformIntegration()->clipboard()->emitChanged(QClipboard::Selection); + }); + } + set_primary_selection(source ? source->object() : nullptr); + m_primarySelectionSource.reset(source); +} + +void QWaylandDataControlDeviceV1::zwlr_data_control_device_v1_data_offer(zwlr_data_control_offer_v1 *offer) +{ + new QWaylandDataControlOfferV1(m_display, offer); +} + +void QWaylandDataControlDeviceV1::zwlr_data_control_device_v1_selection(zwlr_data_control_offer_v1 *id) +{ + if (!id) + return; + m_selectionOffer.reset(static_cast(zwlr_data_control_offer_v1_get_user_data(id))); + + // The selection event may be sent before platfrmIntegration is set. + if (auto* integration = QGuiApplicationPrivate::platformIntegration()) + integration->clipboard()->emitChanged(QClipboard::Selection); +} + +void QWaylandDataControlDeviceV1::zwlr_data_control_device_v1_finished() +{ + m_selectionOffer.reset(); + m_primarySelectionOffer.reset(); + QGuiApplicationPrivate::platformIntegration()->clipboard()->emitChanged(QClipboard::Selection); +} + +void QWaylandDataControlDeviceV1::zwlr_data_control_device_v1_primary_selection(struct ::zwlr_data_control_offer_v1 *id) +{ + if (!id) + return; + m_primarySelectionOffer.reset(static_cast(zwlr_data_control_offer_v1_get_user_data(id))); + + // The selection event may be sent before platfrmIntegration is set. + if (auto* integration = QGuiApplicationPrivate::platformIntegration()) + integration->clipboard()->emitChanged(QClipboard::Selection); +} + +QWaylandDataControlSourceV1::QWaylandDataControlSourceV1(QWaylandDataControlManagerV1 *manager, QMimeData *mimeData) + : QtWayland::zwlr_data_control_source_v1(manager->create_data_source()) + , m_mimeData(mimeData) +{ + if (!mimeData) + return; + for (auto &format : mimeData->formats()) + offer(format); +} + +QWaylandDataControlSourceV1::~QWaylandDataControlSourceV1() +{ + destroy(); +} + +void QWaylandDataControlSourceV1::zwlr_data_control_source_v1_send(const QString &mime_type, int32_t fd) +{ + QByteArray content = QWaylandMimeHelper::getByteArray(m_mimeData, mime_type); + if (!content.isEmpty()) { + // Create a sigpipe handler that does nothing, or clients may be forced to terminate + // if the pipe is closed in the other end. + struct sigaction action, oldAction; + action.sa_handler = SIG_IGN; + sigemptyset (&action.sa_mask); + action.sa_flags = 0; + + sigaction(SIGPIPE, &action, &oldAction); + ssize_t unused = write(fd, content.constData(), size_t(content.size())); + Q_UNUSED(unused); + sigaction(SIGPIPE, &oldAction, nullptr); + } + close(fd); +} + +} // namespace QtWaylandClient + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/qwaylanddatacontrolv1_p.h b/src/plugins/platforms/wayland/qwaylanddatacontrolv1_p.h new file mode 100644 index 00000000000..79f6378b3f9 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylanddatacontrolv1_p.h @@ -0,0 +1,119 @@ +// Copyright (C) 2024 Jie Liu +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QWAYLANDDATACONTROLV1_H +#define QWAYLANDDATACONTROLV1_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#include +#include + +#include + +QT_REQUIRE_CONFIG(clipboard); + +QT_BEGIN_NAMESPACE + +class QMimeData; + +namespace QtWaylandClient { + +class QWaylandInputDevice; +class QWaylandDataControlDeviceV1; + +class QWaylandDataControlManagerV1 : public QtWayland::zwlr_data_control_manager_v1 +{ +public: + explicit QWaylandDataControlManagerV1(QWaylandDisplay *display, uint id, uint version); + QWaylandDataControlDeviceV1 *createDevice(QWaylandInputDevice *seat); + QWaylandDisplay *display() const { return m_display; } + +private: + QWaylandDisplay *m_display = nullptr; +}; + +class QWaylandDataControlOfferV1 : public QtWayland::zwlr_data_control_offer_v1, public QWaylandAbstractDataOffer +{ +public: + explicit QWaylandDataControlOfferV1(QWaylandDisplay *display, ::zwlr_data_control_offer_v1 *offer); + ~QWaylandDataControlOfferV1() override { destroy(); } + void startReceiving(const QString &mimeType, int fd) override; + QMimeData *mimeData() override { return m_mimeData.data(); } + +protected: + void zwlr_data_control_offer_v1_offer(const QString &mime_type) override; + +private: + QWaylandDisplay *m_display = nullptr; + QScopedPointer m_mimeData; +}; + +class Q_WAYLANDCLIENT_EXPORT QWaylandDataControlSourceV1 : public QObject, public QtWayland::zwlr_data_control_source_v1 +{ + Q_OBJECT +public: + explicit QWaylandDataControlSourceV1(QWaylandDataControlManagerV1 *manager, QMimeData *mimeData); + ~QWaylandDataControlSourceV1() override; + + QMimeData *mimeData() const { return m_mimeData; } + +Q_SIGNALS: + void cancelled(); + +protected: + void zwlr_data_control_source_v1_send(const QString &mime_type, int32_t fd) override; + void zwlr_data_control_source_v1_cancelled() override { Q_EMIT cancelled(); } + +private: + QWaylandDisplay *m_display = nullptr; + QMimeData *m_mimeData = nullptr; +}; + +class QWaylandDataControlDeviceV1 : public QObject, public QtWayland::zwlr_data_control_device_v1 +{ + Q_OBJECT + QWaylandDataControlDeviceV1(QWaylandDataControlManagerV1 *manager, QWaylandInputDevice *seat); + +public: + ~QWaylandDataControlDeviceV1() override; + QWaylandDataControlOfferV1 *primarySelectionOffer() const { return m_primarySelectionOffer.data(); } + QWaylandDataControlOfferV1 *selectionOffer() const { return m_selectionOffer.data(); } + void invalidateSelectionOffer(); + QWaylandDataControlSourceV1 *selectionSource() const { return m_selectionSource.data(); } + QWaylandDataControlSourceV1 *primarySelectionSource() const { return m_primarySelectionSource.data(); } + void setSelectionSource(QWaylandDataControlSourceV1 *source); + void setPrimarySelectionSource(QWaylandDataControlSourceV1 *source); + +protected: + void zwlr_data_control_device_v1_data_offer(struct ::zwlr_data_control_offer_v1 *id) override; + void zwlr_data_control_device_v1_selection(struct ::zwlr_data_control_offer_v1 *id) override; + void zwlr_data_control_device_v1_finished() override; + void zwlr_data_control_device_v1_primary_selection(struct ::zwlr_data_control_offer_v1 *id) override; + +private: + QWaylandDisplay *m_display = nullptr; + QWaylandInputDevice *m_seat = nullptr; + QScopedPointer m_selectionOffer; + QScopedPointer m_primarySelectionOffer; + QScopedPointer m_selectionSource; + QScopedPointer m_primarySelectionSource; + friend class QWaylandDataControlManagerV1; +}; + +} // namespace QtWaylandClient + +QT_END_NAMESPACE + +#endif // QWAYLANDDATACONTROLV1_H diff --git a/src/plugins/platforms/wayland/qwaylanddisplay.cpp b/src/plugins/platforms/wayland/qwaylanddisplay.cpp index 572275057d8..dfdf88c0b4a 100644 --- a/src/plugins/platforms/wayland/qwaylanddisplay.cpp +++ b/src/plugins/platforms/wayland/qwaylanddisplay.cpp @@ -1,4 +1,5 @@ // Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2024 Jie Liu // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qwaylanddisplay_p.h" @@ -13,6 +14,7 @@ #include "qwaylandinputdevice_p.h" #if QT_CONFIG(clipboard) #include "qwaylandclipboard_p.h" +#include "qwaylanddatacontrolv1_p.h" #endif #if QT_CONFIG(wayland_datadevice) #include "qwaylanddatadevicemanager_p.h" @@ -53,6 +55,7 @@ #include #include #include +#include #include @@ -329,6 +332,7 @@ QWaylandDisplay::QWaylandDisplay(QWaylandIntegration *waylandIntegration) } mWaylandTryReconnect = qEnvironmentVariableIsSet("QT_WAYLAND_RECONNECT"); + mPreferWlrDataControl = qEnvironmentVariableIntValue("QT_WAYLAND_USE_DATA_CONTROL") > 0; } void QWaylandDisplay::setupConnection() @@ -787,6 +791,13 @@ void QWaylandDisplay::registry_global(uint32_t id, const QString &interface, uin } else if ( interface == QLatin1String(QtWayland::org_kde_kwin_appmenu_manager::interface()->name)) { mGlobals.appMenuManager.reset(new QWaylandAppMenuManager(registry, id, 1)); +#if QT_CONFIG(clipboard) + } else if (mPreferWlrDataControl && interface == QLatin1String(QWaylandDataControlManagerV1::interface()->name)) { + mGlobals.dataControlManager.reset(new QWaylandDataControlManagerV1(this, id, 2)); + for (QWaylandInputDevice *inputDevice : std::as_const(mInputDevices)) { + inputDevice->setDataControlDevice(mGlobals.dataControlManager->createDevice(inputDevice)); + } +#endif } mRegistryGlobals.append(RegistryGlobal(id, interface, version, registry)); @@ -851,6 +862,13 @@ void QWaylandDisplay::registry_global_remove(uint32_t id) for (QWaylandInputDevice *inputDevice : std::as_const(mInputDevices)) inputDevice->setPrimarySelectionDevice(nullptr); } +#endif +#if QT_CONFIG(clipboard) + if (global.interface == QLatin1String(QtWayland::zwlr_data_control_manager_v1::interface()->name)) { + mGlobals.dataControlManager.reset(); + for (QWaylandInputDevice *inputDevice : std::as_const(mInputDevices)) + inputDevice->setDataControlDevice(nullptr); + } #endif emit globalRemoved(mRegistryGlobals.takeAt(i)); break; diff --git a/src/plugins/platforms/wayland/qwaylanddisplay_p.h b/src/plugins/platforms/wayland/qwaylanddisplay_p.h index 074117a38f2..1a6a5ba8e91 100644 --- a/src/plugins/platforms/wayland/qwaylanddisplay_p.h +++ b/src/plugins/platforms/wayland/qwaylanddisplay_p.h @@ -1,4 +1,5 @@ // Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2024 Jie Liu // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QWAYLANDDISPLAY_H @@ -71,6 +72,9 @@ class QWaylandXdgOutputManagerV1; class QWaylandClientBufferIntegration; class QWaylandWindowManagerIntegration; class QWaylandDataDeviceManager; +#if QT_CONFIG(clipboard) +class QWaylandDataControlManagerV1; +#endif #if QT_CONFIG(wayland_client_primary_selection) class QWaylandPrimarySelectionDeviceManagerV1; #endif @@ -148,6 +152,12 @@ public: return mGlobals.dndSelectionHandler.get(); } #endif +#if QT_CONFIG(clipboard) + QWaylandDataControlManagerV1 *dataControlManager() const + { + return mGlobals.dataControlManager.get(); + } +#endif #if QT_CONFIG(wayland_client_primary_selection) QWaylandPrimarySelectionDeviceManagerV1 *primarySelectionManager() const { @@ -334,6 +344,9 @@ private: std::unique_ptr tabletManager; #endif std::unique_ptr pointerGestures; +#if QT_CONFIG(clipboard) + std::unique_ptr dataControlManager; +#endif #if QT_CONFIG(wayland_client_primary_selection) std::unique_ptr primarySelectionManager; #endif @@ -363,6 +376,7 @@ private: struct wl_callback *mSyncCallback = nullptr; static const wl_callback_listener syncCallbackListener; bool mWaylandTryReconnect = false; + bool mPreferWlrDataControl = false; bool mWaylandInputContextRequested = [] () { const auto requested = QPlatformInputContextFactory::requested(); diff --git a/src/plugins/platforms/wayland/qwaylandinputdevice.cpp b/src/plugins/platforms/wayland/qwaylandinputdevice.cpp index afbb3879b6d..0c4da6fef4a 100644 --- a/src/plugins/platforms/wayland/qwaylandinputdevice.cpp +++ b/src/plugins/platforms/wayland/qwaylandinputdevice.cpp @@ -1,4 +1,5 @@ // Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2024 Jie Liu // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qwaylandinputdevice_p.h" @@ -12,6 +13,9 @@ #include "qwaylanddatadevice_p.h" #include "qwaylanddatadevicemanager_p.h" #endif +#if QT_CONFIG(clipboard) +#include "qwaylanddatacontrolv1_p.h" +#endif #if QT_CONFIG(wayland_client_primary_selection) #include "qwaylandprimaryselectionv1_p.h" #endif @@ -316,6 +320,13 @@ QWaylandInputDevice::QWaylandInputDevice(QWaylandDisplay *display, int version, , mDisplay(display->wl_display()) , mId(id) { + +#if QT_CONFIG(clipboard) + if (auto *dataControlManager = mQDisplay->dataControlManager()) { + setDataControlDevice(dataControlManager->createDevice(this)); + } +#endif + #if QT_CONFIG(wayland_datadevice) if (mQDisplay->dndSelectionHandler()) { mDataDevice = mQDisplay->dndSelectionHandler()->getDataDevice(this); @@ -485,6 +496,18 @@ QWaylandDataDevice *QWaylandInputDevice::dataDevice() const } #endif +#if QT_CONFIG(clipboard) +void QWaylandInputDevice::setDataControlDevice(QWaylandDataControlDeviceV1 *dataControlDevice) +{ + mDataControlDevice.reset(dataControlDevice); +} + +QWaylandDataControlDeviceV1 *QWaylandInputDevice::dataControlDevice() const +{ + return mDataControlDevice.data(); +} +#endif + #if QT_CONFIG(wayland_client_primary_selection) void QWaylandInputDevice::setPrimarySelectionDevice(QWaylandPrimarySelectionDeviceV1 *primarySelectionDevice) { diff --git a/src/plugins/platforms/wayland/qwaylandinputdevice_p.h b/src/plugins/platforms/wayland/qwaylandinputdevice_p.h index 0d79ef93376..bcaf025840d 100644 --- a/src/plugins/platforms/wayland/qwaylandinputdevice_p.h +++ b/src/plugins/platforms/wayland/qwaylandinputdevice_p.h @@ -1,4 +1,5 @@ // Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2024 Jie Liu // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QWAYLANDINPUTDEVICE_H @@ -51,6 +52,9 @@ namespace QtWaylandClient { class QWaylandDataDevice; class QWaylandDisplay; +#if QT_CONFIG(clipboard) +class QWaylandDataControlDeviceV1; +#endif #if QT_CONFIG(wayland_client_primary_selection) class QWaylandPrimarySelectionDeviceV1; #endif @@ -102,6 +106,11 @@ public: QWaylandDataDevice *dataDevice() const; #endif +#if QT_CONFIG(clipboard) + void setDataControlDevice(QWaylandDataControlDeviceV1 *dataControlDevice); + QWaylandDataControlDeviceV1 *dataControlDevice() const; +#endif + #if QT_CONFIG(wayland_client_primary_selection) void setPrimarySelectionDevice(QWaylandPrimarySelectionDeviceV1 *primarySelectionDevice); QWaylandPrimarySelectionDeviceV1 *primarySelectionDevice() const; @@ -166,6 +175,10 @@ protected: QWaylandDataDevice *mDataDevice = nullptr; #endif +#if QT_CONFIG(clipboard) + QScopedPointer mDataControlDevice; +#endif + #if QT_CONFIG(wayland_client_primary_selection) QScopedPointer mPrimarySelectionDevice; #endif