diff --git a/src/plugins/qpa/CMakeLists.txt b/src/plugins/qpa/CMakeLists.txt index e1f5dc2793..62b7a12a38 100644 --- a/src/plugins/qpa/CMakeLists.txt +++ b/src/plugins/qpa/CMakeLists.txt @@ -1,6 +1,7 @@ add_library(KWinQpaPlugin OBJECT) target_sources(KWinQpaPlugin PRIVATE backingstore.cpp + clipboard.cpp eglhelpers.cpp eglplatformcontext.cpp integration.cpp diff --git a/src/plugins/qpa/clipboard.cpp b/src/plugins/qpa/clipboard.cpp new file mode 100644 index 0000000000..37bd0f98ba --- /dev/null +++ b/src/plugins/qpa/clipboard.cpp @@ -0,0 +1,207 @@ +/* + SPDX-FileCopyrightText: 2022 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "plugins/qpa/clipboard.h" +#include "utils/filedescriptor.h" +#include "wayland/display.h" +#include "wayland/seat.h" +#include "wayland_server.h" + +#include + +#include +#include +#include + +namespace KWin::QPA +{ + +ClipboardDataSource::ClipboardDataSource(QMimeData *mimeData, QObject *parent) + : AbstractDataSource(parent) + , m_mimeData(mimeData) +{ +} + +QMimeData *ClipboardDataSource::mimeData() const +{ + return m_mimeData; +} + +static void writeData(FileDescriptor fd, const QByteArray &buffer) +{ + size_t remainingSize = buffer.size(); + + pollfd pfds[1]; + pfds[0].fd = fd.get(); + pfds[0].events = POLLOUT; + + while (true) { + const int ready = poll(pfds, 1, 5000); + if (ready < 0) { + if (errno != EINTR) { + return; + } + } else if (ready == 0) { + return; + } else if (!(pfds[0].revents & POLLOUT)) { + return; + } else { + const char *chunk = buffer.constData() + (buffer.size() - remainingSize); + const ssize_t n = write(fd.get(), chunk, remainingSize); + + if (n < 0) { + return; + } else if (n == 0) { + return; + } else { + remainingSize -= n; + if (remainingSize == 0) { + return; + } + } + } + } +} + +void ClipboardDataSource::requestData(const QString &mimeType, qint32 fd) +{ + const QByteArray data = m_mimeData->data(mimeType); + QThreadPool::globalInstance()->start([data, fd]() { + writeData(FileDescriptor(fd), data); + }); +} + +void ClipboardDataSource::cancel() +{ +} + +QStringList ClipboardDataSource::mimeTypes() const +{ + return m_mimeData->formats(); +} + +ClipboardMimeData::ClipboardMimeData(AbstractDataSource *dataSource) + : m_dataSource(dataSource) +{ +} + +static QVariant readData(FileDescriptor fd) +{ + pollfd pfd; + pfd.fd = fd.get(); + pfd.events = POLLIN; + + QByteArray buffer; + while (true) { + const int ready = poll(&pfd, 1, 1000); + if (ready < 0) { + if (errno != EINTR) { + return QVariant(); + } + } else if (ready == 0) { + return QVariant(); + } else { + char chunk[4096]; + const ssize_t n = read(fd.get(), chunk, sizeof chunk); + + if (n < 0) { + return QVariant(); + } else if (n == 0) { + return buffer; + } else if (n > 0) { + buffer.append(chunk, n); + } + } + } +} + +QVariant ClipboardMimeData::retrieveData(const QString &mimeType, QMetaType preferredType) const +{ + int pipeFds[2]; + if (pipe2(pipeFds, O_CLOEXEC) != 0) { + return QVariant(); + } + + m_dataSource->requestData(mimeType, pipeFds[1]); + + waylandServer()->display()->flush(); + return readData(FileDescriptor(pipeFds[0])); +} + +Clipboard::Clipboard() +{ +} + +void Clipboard::initialize() +{ + connect(waylandServer()->seat(), &SeatInterface::selectionChanged, this, [this](AbstractDataSource *selection) { + if (selection && m_ownSelection.get() != selection) { + m_externalMimeData = std::make_unique(selection); + } else { + m_externalMimeData.reset(); + } + emitChanged(QClipboard::Clipboard); + }); +} + +QMimeData *Clipboard::mimeData(QClipboard::Mode mode) +{ + switch (mode) { + case QClipboard::Clipboard: + if (m_ownSelection) { + if (waylandServer()->seat()->selection() == m_ownSelection.get()) { + return m_ownSelection->mimeData(); + } + } + return m_externalMimeData ? m_externalMimeData.get() : &m_emptyData; + default: + return &m_emptyData; + } +} + +void Clipboard::setMimeData(QMimeData *data, QClipboard::Mode mode) +{ + static const QString plain = QStringLiteral("text/plain"); + static const QString utf8 = QStringLiteral("text/plain;charset=utf-8"); + + if (data && data->hasFormat(plain) && !data->hasFormat(utf8)) { + data->setData(utf8, data->data(plain)); + } + + switch (mode) { + case QClipboard::Clipboard: + if (data) { + auto oldSelection = std::move(m_ownSelection); + m_ownSelection = std::make_unique(data); + waylandServer()->seat()->setSelection(m_ownSelection.get(), waylandServer()->display()->nextSerial()); + } else { + if (waylandServer()->seat()->selection() == m_ownSelection.get()) { + waylandServer()->seat()->setSelection(nullptr, waylandServer()->display()->nextSerial()); + } + m_ownSelection.reset(); + } + break; + default: + break; + } +} + +bool Clipboard::supportsMode(QClipboard::Mode mode) const +{ + return mode == QClipboard::Clipboard; +} + +bool Clipboard::ownsMode(QClipboard::Mode mode) const +{ + switch (mode) { + case QClipboard::Clipboard: + return m_ownSelection && waylandServer()->seat()->selection() == m_ownSelection.get(); + default: + return false; + } +} + +} diff --git a/src/plugins/qpa/clipboard.h b/src/plugins/qpa/clipboard.h new file mode 100644 index 0000000000..a85746d0e5 --- /dev/null +++ b/src/plugins/qpa/clipboard.h @@ -0,0 +1,69 @@ +/* + SPDX-FileCopyrightText: 2022 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#pragma once + +#include "wayland/abstract_data_source.h" + +#include + +#include + +namespace KWin::QPA +{ + +class ClipboardDataSource : public AbstractDataSource +{ + Q_OBJECT + +public: + explicit ClipboardDataSource(QMimeData *mimeData, QObject *parent = nullptr); + + QMimeData *mimeData() const; + + void requestData(const QString &mimeType, qint32 fd) override; + void cancel() override; + QStringList mimeTypes() const override; + +private: + QMimeData *m_mimeData; +}; + +class ClipboardMimeData : public QMimeData +{ + Q_OBJECT + +public: + explicit ClipboardMimeData(AbstractDataSource *dataSource); + +protected: + QVariant retrieveData(const QString &mimetype, QMetaType preferredType) const override; + +private: + AbstractDataSource *m_dataSource; +}; + +class Clipboard : public QObject, public QPlatformClipboard +{ + Q_OBJECT + +public: + Clipboard(); + + void initialize(); + + QMimeData *mimeData(QClipboard::Mode mode = QClipboard::Clipboard) override; + void setMimeData(QMimeData *data, QClipboard::Mode mode = QClipboard::Clipboard) override; + bool supportsMode(QClipboard::Mode mode) const override; + bool ownsMode(QClipboard::Mode mode) const override; + +private: + QMimeData m_emptyData; + std::unique_ptr m_externalMimeData; + std::unique_ptr m_ownSelection; +}; + +} diff --git a/src/plugins/qpa/integration.cpp b/src/plugins/qpa/integration.cpp index af5c517922..be55379c0c 100644 --- a/src/plugins/qpa/integration.cpp +++ b/src/plugins/qpa/integration.cpp @@ -10,6 +10,7 @@ #include "integration.h" #include "backingstore.h" +#include "clipboard.h" #include "eglplatformcontext.h" #include "logging.h" #include "offscreensurface.h" @@ -51,6 +52,7 @@ Integration::Integration() , m_fontDb(new QGenericUnixFontDatabase()) , m_nativeInterface(new QPlatformNativeInterface()) , m_services(new QGenericUnixServices()) + , m_clipboard(new Clipboard()) { } @@ -184,6 +186,8 @@ void Integration::handleWorkspaceCreated() for (Output *output : outputs) { handleOutputEnabled(output); } + + m_clipboard->initialize(); } void Integration::handleOutputEnabled(Output *output) @@ -224,6 +228,11 @@ QPlatformServices *Integration::services() const return m_services.get(); } +QPlatformClipboard *Integration::clipboard() const +{ + return m_clipboard.get(); +} + } } diff --git a/src/plugins/qpa/integration.h b/src/plugins/qpa/integration.h index 318709f297..78a85c8eb0 100644 --- a/src/plugins/qpa/integration.h +++ b/src/plugins/qpa/integration.h @@ -23,6 +23,7 @@ class Output; namespace QPA { +class Clipboard; class Screen; class Integration : public QObject, public QPlatformIntegration @@ -44,6 +45,7 @@ public: QPlatformAccessibility *accessibility() const override; QPlatformNativeInterface *nativeInterface() const override; QPlatformServices *services() const override; + QPlatformClipboard *clipboard() const override; void initialize() override; QHash screens() const; @@ -60,6 +62,7 @@ private: QPlatformPlaceholderScreen *m_dummyScreen = nullptr; QHash m_screens; std::unique_ptr m_services; + std::unique_ptr m_clipboard; }; } diff --git a/src/wayland/display.h b/src/wayland/display.h index c6bd8e407c..d27949475c 100644 --- a/src/wayland/display.h +++ b/src/wayland/display.h @@ -122,7 +122,7 @@ public: */ void setDefaultMaxBufferSize(size_t max); -private Q_SLOTS: +public Q_SLOTS: void flush(); Q_SIGNALS: