You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
176 lines
6.3 KiB
176 lines
6.3 KiB
/******************************************************************** |
|
KWin - the KDE window manager |
|
This file is part of the KDE project. |
|
|
|
Copyright (C) 2016 Martin Gräßlin <mgraesslin@kde.org> |
|
|
|
This program is free software; you can redistribute it and/or modify |
|
it under the terms of the GNU General Public License as published by |
|
the Free Software Foundation; either version 2 of the License, or |
|
(at your option) any later version. |
|
|
|
This program is distributed in the hope that it will be useful, |
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
GNU General Public License for more details. |
|
|
|
You should have received a copy of the GNU General Public License |
|
along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
*********************************************************************/ |
|
#include "waylandclipboard.h" |
|
|
|
#include <KWayland/Client/connection_thread.h> |
|
#include <KWayland/Client/datadevicemanager.h> |
|
#include <KWayland/Client/datadevice.h> |
|
#include <KWayland/Client/datasource.h> |
|
#include <KWayland/Client/event_queue.h> |
|
#include <KWayland/Client/seat.h> |
|
#include <KWayland/Client/registry.h> |
|
|
|
#include <QClipboard> |
|
#include <QFile> |
|
#include <QGuiApplication> |
|
#include <QThread> |
|
#include <QMimeData> |
|
#include <QMimeType> |
|
#include <qplatformdefs.h> |
|
|
|
#include <errno.h> |
|
#include <unistd.h> |
|
|
|
using namespace KWayland::Client; |
|
|
|
WaylandClipboard::WaylandClipboard(QObject *parent) |
|
: QObject(parent) |
|
, m_thread(new QThread) |
|
, m_connectionThread(new ConnectionThread) |
|
{ |
|
m_connectionThread->setSocketFd(qgetenv("WAYLAND_SOCKET").toInt()); |
|
m_connectionThread->moveToThread(m_thread); |
|
m_thread->start(); |
|
|
|
connect(m_connectionThread, &ConnectionThread::connected, this, &WaylandClipboard::setup, Qt::QueuedConnection); |
|
|
|
m_connectionThread->initConnection(); |
|
|
|
connect(qApp->clipboard(), &QClipboard::changed, this, |
|
[this] (QClipboard::Mode mode) { |
|
if (mode != QClipboard::Clipboard) { |
|
return; |
|
} |
|
// TODO: do we need to take a copy of the clipboard in order to keep it after the X application quit? |
|
if (!m_dataDeviceManager || !m_dataDevice) { |
|
return; |
|
} |
|
auto source = m_dataDeviceManager->createDataSource(this); |
|
auto mimeData = qApp->clipboard()->mimeData(); |
|
const auto formats = mimeData->formats(); |
|
for (const auto &format : formats) { |
|
source->offer(format); |
|
} |
|
connect(source, &DataSource::sendDataRequested, this, |
|
[] (const QString &type, qint32 fd) { |
|
auto mimeData = qApp->clipboard()->mimeData(); |
|
if (!mimeData->hasFormat(type)) { |
|
close(fd); |
|
return; |
|
} |
|
const auto data = mimeData->data(type); |
|
QFile writePipe; |
|
if (writePipe.open(fd, QIODevice::WriteOnly, QFile::AutoCloseHandle)) { |
|
writePipe.write(data); |
|
writePipe.close(); |
|
} else { |
|
close(fd); |
|
} |
|
} |
|
); |
|
m_dataDevice->setSelection(0, source); |
|
delete m_dataSource; |
|
m_dataSource = source; |
|
m_connectionThread->flush(); |
|
} |
|
); |
|
} |
|
|
|
WaylandClipboard::~WaylandClipboard() |
|
{ |
|
m_connectionThread->deleteLater(); |
|
m_thread->quit(); |
|
m_thread->wait(); |
|
} |
|
|
|
static int readData(int fd, QByteArray &data) |
|
{ |
|
// implementation based on QtWayland file qwaylanddataoffer.cpp |
|
char buf[4096]; |
|
int retryCount = 0; |
|
int n; |
|
while (true) { |
|
n = QT_READ(fd, buf, sizeof buf); |
|
if (n == -1 && (errno == EAGAIN || errno == EWOULDBLOCK) && ++retryCount < 1000) { |
|
usleep(1000); |
|
} else { |
|
break; |
|
} |
|
} |
|
if (n > 0) { |
|
data.append(buf, n); |
|
n = readData(fd, data); |
|
} |
|
return n; |
|
} |
|
|
|
void WaylandClipboard::setup() |
|
{ |
|
EventQueue *queue = new EventQueue(this); |
|
queue->setup(m_connectionThread); |
|
|
|
Registry *registry = new Registry(this); |
|
registry->setEventQueue(queue); |
|
registry->create(m_connectionThread); |
|
connect(registry, &Registry::interfacesAnnounced, this, |
|
[this, registry] { |
|
const auto seatInterface = registry->interface(Registry::Interface::Seat); |
|
if (seatInterface.name != 0) { |
|
m_seat = registry->createSeat(seatInterface.name, seatInterface.version, this); |
|
} |
|
const auto ddmInterface = registry->interface(Registry::Interface::DataDeviceManager); |
|
if (ddmInterface.name != 0) { |
|
m_dataDeviceManager = registry->createDataDeviceManager(ddmInterface.name, ddmInterface.version, this); |
|
} |
|
if (m_seat && m_dataDeviceManager) { |
|
m_dataDevice = m_dataDeviceManager->getDataDevice(m_seat, this); |
|
connect(m_dataDevice, &DataDevice::selectionOffered, this, |
|
[this] (DataOffer *offer) { |
|
if (offer->offeredMimeTypes().isEmpty()) { |
|
return; |
|
} |
|
int pipeFds[2]; |
|
if (pipe(pipeFds) != 0) { |
|
return; |
|
} |
|
const auto mimeType = offer->offeredMimeTypes().first(); |
|
offer->receive(mimeType, pipeFds[1]); |
|
m_connectionThread->flush(); |
|
close(pipeFds[1]); |
|
QByteArray content; |
|
if (readData(pipeFds[0], content) != 0) { |
|
content = QByteArray(); |
|
} |
|
close(pipeFds[0]); |
|
QMimeData *mimeData = new QMimeData(); |
|
mimeData->setData(mimeType.name(), content); |
|
qApp->clipboard()->setMimeData(mimeData); |
|
} |
|
); |
|
connect(m_dataDevice, &DataDevice::selectionCleared, this, |
|
[this] { |
|
qApp->clipboard()->clear(); |
|
} |
|
); |
|
} |
|
} |
|
); |
|
registry->setup(); |
|
}
|
|
|