qpa: Implement clipboard
This allows copy pasting text between the overview and regular clients. QMimeData::data() has a synchronous API. It is a problem for us, the compositor, because it means we need to block the main thread to read the mime data. This change adds a one second timeout. If no data arrives within the next 1 second, the qpa will give up in order to avoid freezing the screen further. Not sure how this can be handled better without changing the API of the QMimeData to add async overloads. BUG: 445751
This commit is contained in:
parent
7d4e993155
commit
95a8163c5b
6 changed files with 290 additions and 1 deletions
|
@ -1,6 +1,7 @@
|
||||||
add_library(KWinQpaPlugin OBJECT)
|
add_library(KWinQpaPlugin OBJECT)
|
||||||
target_sources(KWinQpaPlugin PRIVATE
|
target_sources(KWinQpaPlugin PRIVATE
|
||||||
backingstore.cpp
|
backingstore.cpp
|
||||||
|
clipboard.cpp
|
||||||
eglhelpers.cpp
|
eglhelpers.cpp
|
||||||
eglplatformcontext.cpp
|
eglplatformcontext.cpp
|
||||||
integration.cpp
|
integration.cpp
|
||||||
|
|
207
src/plugins/qpa/clipboard.cpp
Normal file
207
src/plugins/qpa/clipboard.cpp
Normal file
|
@ -0,0 +1,207 @@
|
||||||
|
/*
|
||||||
|
SPDX-FileCopyrightText: 2022 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
|
||||||
|
|
||||||
|
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 <QThreadPool>
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
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<ClipboardMimeData>(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<ClipboardDataSource>(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
69
src/plugins/qpa/clipboard.h
Normal file
69
src/plugins/qpa/clipboard.h
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
/*
|
||||||
|
SPDX-FileCopyrightText: 2022 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
|
||||||
|
|
||||||
|
SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "wayland/abstract_data_source.h"
|
||||||
|
|
||||||
|
#include <QMimeData>
|
||||||
|
|
||||||
|
#include <qpa/qplatformclipboard.h>
|
||||||
|
|
||||||
|
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<ClipboardMimeData> m_externalMimeData;
|
||||||
|
std::unique_ptr<ClipboardDataSource> m_ownSelection;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -10,6 +10,7 @@
|
||||||
|
|
||||||
#include "integration.h"
|
#include "integration.h"
|
||||||
#include "backingstore.h"
|
#include "backingstore.h"
|
||||||
|
#include "clipboard.h"
|
||||||
#include "eglplatformcontext.h"
|
#include "eglplatformcontext.h"
|
||||||
#include "logging.h"
|
#include "logging.h"
|
||||||
#include "offscreensurface.h"
|
#include "offscreensurface.h"
|
||||||
|
@ -51,6 +52,7 @@ Integration::Integration()
|
||||||
, m_fontDb(new QGenericUnixFontDatabase())
|
, m_fontDb(new QGenericUnixFontDatabase())
|
||||||
, m_nativeInterface(new QPlatformNativeInterface())
|
, m_nativeInterface(new QPlatformNativeInterface())
|
||||||
, m_services(new QGenericUnixServices())
|
, m_services(new QGenericUnixServices())
|
||||||
|
, m_clipboard(new Clipboard())
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,6 +186,8 @@ void Integration::handleWorkspaceCreated()
|
||||||
for (Output *output : outputs) {
|
for (Output *output : outputs) {
|
||||||
handleOutputEnabled(output);
|
handleOutputEnabled(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_clipboard->initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Integration::handleOutputEnabled(Output *output)
|
void Integration::handleOutputEnabled(Output *output)
|
||||||
|
@ -224,6 +228,11 @@ QPlatformServices *Integration::services() const
|
||||||
return m_services.get();
|
return m_services.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QPlatformClipboard *Integration::clipboard() const
|
||||||
|
{
|
||||||
|
return m_clipboard.get();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ class Output;
|
||||||
namespace QPA
|
namespace QPA
|
||||||
{
|
{
|
||||||
|
|
||||||
|
class Clipboard;
|
||||||
class Screen;
|
class Screen;
|
||||||
|
|
||||||
class Integration : public QObject, public QPlatformIntegration
|
class Integration : public QObject, public QPlatformIntegration
|
||||||
|
@ -44,6 +45,7 @@ public:
|
||||||
QPlatformAccessibility *accessibility() const override;
|
QPlatformAccessibility *accessibility() const override;
|
||||||
QPlatformNativeInterface *nativeInterface() const override;
|
QPlatformNativeInterface *nativeInterface() const override;
|
||||||
QPlatformServices *services() const override;
|
QPlatformServices *services() const override;
|
||||||
|
QPlatformClipboard *clipboard() const override;
|
||||||
void initialize() override;
|
void initialize() override;
|
||||||
|
|
||||||
QHash<Output *, Screen *> screens() const;
|
QHash<Output *, Screen *> screens() const;
|
||||||
|
@ -60,6 +62,7 @@ private:
|
||||||
QPlatformPlaceholderScreen *m_dummyScreen = nullptr;
|
QPlatformPlaceholderScreen *m_dummyScreen = nullptr;
|
||||||
QHash<Output *, Screen *> m_screens;
|
QHash<Output *, Screen *> m_screens;
|
||||||
std::unique_ptr<QGenericUnixServices> m_services;
|
std::unique_ptr<QGenericUnixServices> m_services;
|
||||||
|
std::unique_ptr<Clipboard> m_clipboard;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,7 +122,7 @@ public:
|
||||||
*/
|
*/
|
||||||
void setDefaultMaxBufferSize(size_t max);
|
void setDefaultMaxBufferSize(size_t max);
|
||||||
|
|
||||||
private Q_SLOTS:
|
public Q_SLOTS:
|
||||||
void flush();
|
void flush();
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
|
|
Loading…
Reference in a new issue