Support for syncing the clipboard from X11 to Wayland and vice versa
Summary: The clipboard sync is done by a dedicated helper binary launched by KWin. This helper binary is forced to xcb platform to piggy-back on Qt's implementation of the X11 clipboard. In addition it implements the Wayland clipboard - which is much simpler. Reading the Wayland clipboard is based on the implementation in QtWayland. KWin internally knows the DataDeviceInterface belonging to the helper application. Whenever an xwayland client is focussed, this DataDevice is allowed to set the selection and KWin manually updates the current selection in the SeatInterface. By that the sync from X11 to Wayland is implemented. When afterwards a Wayland client is selected, it's sent the current selection which references the X clipboard and a data transfer can be initiated in the normal Wayland way. For the other direction KWin sends the current selection to the helper's DataDevice whenever an xwayland window is focused. The helper application reads the Wayland clipboard and sets it on the X11 clipboard. Thus the Wayland clipboard is synced to X11. The approach used here will also be useful for implementing a clipboard manager (aka klipper). Currently the implementation is not yet fully completed. We need to make sure that the helper application gets restarted in case of a crash. Test Plan: See added test case Reviewers: #plasma_on_wayland, #kwin Subscribers: plasma-devel, kwin Tags: #plasma_on_wayland, #kwin Differential Revision: https://phabricator.kde.org/D1973
This commit is contained in:
parent
4bc0c75df2
commit
3493e97655
15 changed files with 710 additions and 1 deletions
|
@ -335,6 +335,7 @@ add_subdirectory( effects )
|
|||
add_subdirectory( scripts )
|
||||
add_subdirectory( tabbox )
|
||||
add_subdirectory(scripting)
|
||||
add_subdirectory(helpers)
|
||||
|
||||
########### next target ###############
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
add_definitions(-DKWINBACKENDPATH="${CMAKE_BINARY_DIR}/plugins/platforms/virtual/KWinWaylandVirtualBackend.so")
|
||||
add_definitions(-DKWINQPAPATH="${CMAKE_BINARY_DIR}/plugins/qpa/")
|
||||
add_subdirectory(helper)
|
||||
########################################################
|
||||
# Test Start
|
||||
########################################################
|
||||
|
@ -225,3 +226,12 @@ add_executable(testDontCrashNoBorder ${testDontCrashNoBorder_SRCS})
|
|||
target_link_libraries( testDontCrashNoBorder kwin Qt5::Test)
|
||||
add_test(kwin-testDontCrashNoBorder testDontCrashNoBorder)
|
||||
ecm_mark_as_test(testDontCrashNoBorder)
|
||||
|
||||
########################################################
|
||||
# XClipboardSync Test
|
||||
########################################################
|
||||
set( testXClipboardSync_SRCS xclipboardsync_test.cpp kwin_wayland_test.cpp )
|
||||
add_executable(testXClipboardSync ${testXClipboardSync_SRCS})
|
||||
target_link_libraries( testXClipboardSync kwin Qt5::Test)
|
||||
add_test(kwin-testXClipboardSync testXClipboardSync)
|
||||
ecm_mark_as_test(testXClipboardSync)
|
||||
|
|
7
autotests/wayland/helper/CMakeLists.txt
Normal file
7
autotests/wayland/helper/CMakeLists.txt
Normal file
|
@ -0,0 +1,7 @@
|
|||
add_executable(copy copy.cpp)
|
||||
target_link_libraries(copy Qt5::Gui)
|
||||
ecm_mark_as_test(copy)
|
||||
######################
|
||||
add_executable(paste paste.cpp)
|
||||
target_link_libraries(paste Qt5::Gui)
|
||||
ecm_mark_as_test(paste)
|
71
autotests/wayland/helper/copy.cpp
Normal file
71
autotests/wayland/helper/copy.cpp
Normal file
|
@ -0,0 +1,71 @@
|
|||
/********************************************************************
|
||||
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 <QClipboard>
|
||||
#include <QGuiApplication>
|
||||
#include <QPainter>
|
||||
#include <QRasterWindow>
|
||||
#include <QTimer>
|
||||
|
||||
class Window : public QRasterWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit Window();
|
||||
virtual ~Window();
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void focusInEvent(QFocusEvent *event) override;
|
||||
};
|
||||
|
||||
Window::Window()
|
||||
: QRasterWindow()
|
||||
{
|
||||
}
|
||||
|
||||
Window::~Window() = default;
|
||||
|
||||
void Window::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
Q_UNUSED(event)
|
||||
QPainter p(this);
|
||||
p.fillRect(0, 0, width(), height(), Qt::red);
|
||||
}
|
||||
|
||||
void Window::focusInEvent(QFocusEvent *event)
|
||||
{
|
||||
QRasterWindow::focusInEvent(event);
|
||||
// TODO: make it work without singleshot
|
||||
QTimer::singleShot(100,[] {
|
||||
qApp->clipboard()->setText(QStringLiteral("test"));
|
||||
});
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QGuiApplication app(argc, argv);
|
||||
QScopedPointer<Window> w(new Window);
|
||||
w->setGeometry(QRect(0, 0, 100, 200));
|
||||
w->show();
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
#include "copy.moc"
|
68
autotests/wayland/helper/paste.cpp
Normal file
68
autotests/wayland/helper/paste.cpp
Normal file
|
@ -0,0 +1,68 @@
|
|||
/********************************************************************
|
||||
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 <QClipboard>
|
||||
#include <QGuiApplication>
|
||||
#include <QPainter>
|
||||
#include <QRasterWindow>
|
||||
#include <QTimer>
|
||||
|
||||
class Window : public QRasterWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit Window();
|
||||
virtual ~Window();
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
};
|
||||
|
||||
Window::Window()
|
||||
: QRasterWindow()
|
||||
{
|
||||
}
|
||||
|
||||
Window::~Window() = default;
|
||||
|
||||
void Window::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
Q_UNUSED(event)
|
||||
QPainter p(this);
|
||||
p.fillRect(0, 0, width(), height(), Qt::blue);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QGuiApplication app(argc, argv);
|
||||
QObject::connect(app.clipboard(), &QClipboard::changed, &app,
|
||||
[] {
|
||||
if (qApp->clipboard()->text() == QLatin1String("test")) {
|
||||
QTimer::singleShot(100, qApp, &QCoreApplication::quit);
|
||||
}
|
||||
}
|
||||
);
|
||||
QScopedPointer<Window> w(new Window);
|
||||
w->setGeometry(QRect(0, 0, 100, 200));
|
||||
w->show();
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
#include "paste.moc"
|
181
autotests/wayland/xclipboardsync_test.cpp
Normal file
181
autotests/wayland/xclipboardsync_test.cpp
Normal file
|
@ -0,0 +1,181 @@
|
|||
/********************************************************************
|
||||
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 "kwin_wayland_test.h"
|
||||
#include "platform.h"
|
||||
#include "shell_client.h"
|
||||
#include "screens.h"
|
||||
#include "wayland_server.h"
|
||||
#include "workspace.h"
|
||||
|
||||
#include <KWayland/Server/datadevice_interface.h>
|
||||
|
||||
#include <QProcess>
|
||||
#include <QProcessEnvironment>
|
||||
|
||||
using namespace KWin;
|
||||
|
||||
static const QString s_socketName = QStringLiteral("wayland_test_kwin_xclipboard_sync-0");
|
||||
|
||||
class XClipboardSyncTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
private Q_SLOTS:
|
||||
void initTestCase();
|
||||
void cleanup();
|
||||
void testSync_data();
|
||||
void testSync();
|
||||
|
||||
private:
|
||||
QProcess *m_copyProcess = nullptr;
|
||||
QProcess *m_pasteProcess = nullptr;
|
||||
};
|
||||
|
||||
void XClipboardSyncTest::initTestCase()
|
||||
{
|
||||
qRegisterMetaType<KWin::ShellClient*>();
|
||||
qRegisterMetaType<KWin::AbstractClient*>();
|
||||
qRegisterMetaType<QProcess::ExitStatus>();
|
||||
QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated);
|
||||
QVERIFY(workspaceCreatedSpy.isValid());
|
||||
kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024));
|
||||
QMetaObject::invokeMethod(kwinApp()->platform(), "setOutputCount", Qt::DirectConnection, Q_ARG(int, 2));
|
||||
waylandServer()->init(s_socketName.toLocal8Bit());
|
||||
|
||||
kwinApp()->start();
|
||||
QVERIFY(workspaceCreatedSpy.wait());
|
||||
QCOMPARE(screens()->count(), 2);
|
||||
QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024));
|
||||
QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024));
|
||||
waylandServer()->initWorkspace();
|
||||
// wait till the xclipboard sync data device is created
|
||||
while (waylandServer()->xclipboardSyncDataDevice().isNull()) {
|
||||
QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents);
|
||||
}
|
||||
QVERIFY(!waylandServer()->xclipboardSyncDataDevice().isNull());
|
||||
}
|
||||
|
||||
void XClipboardSyncTest::cleanup()
|
||||
{
|
||||
if (m_copyProcess) {
|
||||
m_copyProcess->terminate();
|
||||
QVERIFY(m_copyProcess->waitForFinished());
|
||||
m_copyProcess = nullptr;
|
||||
}
|
||||
if (m_pasteProcess) {
|
||||
m_pasteProcess->terminate();
|
||||
QVERIFY(m_pasteProcess->waitForFinished());
|
||||
m_pasteProcess = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void XClipboardSyncTest::testSync_data()
|
||||
{
|
||||
QTest::addColumn<QString>("copyPlatform");
|
||||
QTest::addColumn<QString>("pastePlatform");
|
||||
|
||||
QTest::newRow("x11-wayland") << QStringLiteral("xcb") << QStringLiteral("wayland");
|
||||
QTest::newRow("wayland-x11") << QStringLiteral("wayland") << QStringLiteral("xcb");
|
||||
}
|
||||
|
||||
void XClipboardSyncTest::testSync()
|
||||
{
|
||||
// this test verifies the syncing of X11 to Wayland clipboard
|
||||
const QString copy = QFINDTESTDATA(QStringLiteral("helper/copy"));
|
||||
QVERIFY(!copy.isEmpty());
|
||||
const QString paste = QFINDTESTDATA(QStringLiteral("helper/paste"));
|
||||
QVERIFY(!paste.isEmpty());
|
||||
|
||||
QSignalSpy clientAddedSpy(workspace(), &Workspace::clientAdded);
|
||||
QVERIFY(clientAddedSpy.isValid());
|
||||
QSignalSpy shellClientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded);
|
||||
QVERIFY(shellClientAddedSpy.isValid());
|
||||
QSignalSpy clipboardChangedSpy(waylandServer()->xclipboardSyncDataDevice().data(), &KWayland::Server::DataDeviceInterface::selectionChanged);
|
||||
QVERIFY(clipboardChangedSpy.isValid());
|
||||
|
||||
QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
|
||||
QFETCH(QString, copyPlatform);
|
||||
environment.insert(QStringLiteral("QT_QPA_PLATFORM"), copyPlatform);
|
||||
environment.insert(QStringLiteral("WAYLAND_DISPLAY"), s_socketName);
|
||||
m_copyProcess = new QProcess();
|
||||
m_copyProcess->setProcessEnvironment(environment);
|
||||
m_copyProcess->setProcessChannelMode(QProcess::ForwardedChannels);
|
||||
m_copyProcess->setProgram(copy);
|
||||
m_copyProcess->start();
|
||||
QVERIFY(m_copyProcess->waitForStarted());
|
||||
|
||||
AbstractClient *copyClient = nullptr;
|
||||
if (copyPlatform == QLatin1String("xcb")) {
|
||||
QVERIFY(clientAddedSpy.wait());
|
||||
copyClient = clientAddedSpy.first().first().value<AbstractClient*>();
|
||||
} else {
|
||||
QVERIFY(shellClientAddedSpy.wait());
|
||||
copyClient = shellClientAddedSpy.first().first().value<AbstractClient*>();
|
||||
}
|
||||
QVERIFY(copyClient);
|
||||
if (workspace()->activeClient() != copyClient) {
|
||||
workspace()->activateClient(copyClient);
|
||||
}
|
||||
QCOMPARE(workspace()->activeClient(), copyClient);
|
||||
if (copyPlatform == QLatin1String("xcb")) {
|
||||
QVERIFY(clipboardChangedSpy.isEmpty());
|
||||
QVERIFY(clipboardChangedSpy.wait());
|
||||
} else {
|
||||
// TODO: it would be better to be able to connect to a signal, instead of waiting
|
||||
// the idea is to make sure that the clipboard is updated, thus we need to give it
|
||||
// enough time before starting the paste process which creates another window
|
||||
QTest::qWait(250);
|
||||
}
|
||||
|
||||
// start the paste process
|
||||
m_pasteProcess = new QProcess();
|
||||
QSignalSpy finishedSpy(m_pasteProcess, static_cast<void(QProcess::*)(int,QProcess::ExitStatus)>(&QProcess::finished));
|
||||
QVERIFY(finishedSpy.isValid());
|
||||
QFETCH(QString, pastePlatform);
|
||||
environment.insert(QStringLiteral("QT_QPA_PLATFORM"), pastePlatform);
|
||||
m_pasteProcess->setProcessEnvironment(environment);
|
||||
m_pasteProcess->setProcessChannelMode(QProcess::ForwardedChannels);
|
||||
m_pasteProcess->setProgram(paste);
|
||||
m_pasteProcess->start();
|
||||
QVERIFY(m_pasteProcess->waitForStarted());
|
||||
|
||||
AbstractClient *pasteClient = nullptr;
|
||||
if (pastePlatform == QLatin1String("xcb")) {
|
||||
QVERIFY(clientAddedSpy.wait());
|
||||
pasteClient = clientAddedSpy.last().first().value<AbstractClient*>();
|
||||
} else {
|
||||
QVERIFY(shellClientAddedSpy.wait());
|
||||
pasteClient = shellClientAddedSpy.last().first().value<AbstractClient*>();
|
||||
}
|
||||
QCOMPARE(clientAddedSpy.count(), 1);
|
||||
QCOMPARE(shellClientAddedSpy.count(), 1);
|
||||
QVERIFY(pasteClient);
|
||||
qDebug() << pasteClient;
|
||||
if (workspace()->activeClient() != pasteClient) {
|
||||
workspace()->activateClient(pasteClient);
|
||||
}
|
||||
QTRY_COMPARE(workspace()->activeClient(), pasteClient);
|
||||
QVERIFY(finishedSpy.wait());
|
||||
QCOMPARE(finishedSpy.first().first().toInt(), 0);
|
||||
delete m_pasteProcess;
|
||||
m_pasteProcess = nullptr;
|
||||
}
|
||||
|
||||
WAYLANDTEST_MAIN(XClipboardSyncTest)
|
||||
#include "xclipboardsync_test.moc"
|
|
@ -8,6 +8,7 @@
|
|||
#define XCB_VERSION_STRING "${XCB_VERSION}"
|
||||
#define KWIN_KILLER_BIN "${CMAKE_INSTALL_FULL_LIBEXECDIR}/kwin_killer_helper"
|
||||
#define KWIN_RULES_DIALOG_BIN "${CMAKE_INSTALL_FULL_LIBEXECDIR}/kwin_rules_dialog"
|
||||
#define KWIN_XCLIPBOARD_SYNC_BIN "${CMAKE_INSTALL_FULL_LIBEXECDIR}/org_kde_kwin_xclipboard_syncer"
|
||||
#cmakedefine01 HAVE_INPUT
|
||||
#cmakedefine01 HAVE_X11_XCB
|
||||
#cmakedefine01 HAVE_X11_XINPUT
|
||||
|
|
1
helpers/CMakeLists.txt
Normal file
1
helpers/CMakeLists.txt
Normal file
|
@ -0,0 +1 @@
|
|||
add_subdirectory(xclipboardsync)
|
5
helpers/xclipboardsync/CMakeLists.txt
Normal file
5
helpers/xclipboardsync/CMakeLists.txt
Normal file
|
@ -0,0 +1,5 @@
|
|||
set(xclipboard_SRCS main.cpp waylandclipboard.cpp)
|
||||
add_executable(org_kde_kwin_xclipboard_syncer ${xclipboard_SRCS})
|
||||
target_link_libraries(org_kde_kwin_xclipboard_syncer Qt5::Gui KF5::WaylandClient)
|
||||
|
||||
install(TARGETS org_kde_kwin_xclipboard_syncer DESTINATION ${LIBEXEC_INSTALL_DIR} )
|
36
helpers/xclipboardsync/main.cpp
Normal file
36
helpers/xclipboardsync/main.cpp
Normal file
|
@ -0,0 +1,36 @@
|
|||
/********************************************************************
|
||||
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 <QGuiApplication>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
qputenv("QT_QPA_PLATFORM", "xcb");
|
||||
QGuiApplication app(argc, argv);
|
||||
// perform sanity checks
|
||||
if (app.platformName().toLower() != QStringLiteral("xcb")) {
|
||||
fprintf(stderr, "%s: FATAL ERROR expecting platform xcb but got platform %s\n",
|
||||
argv[0], qPrintable(app.platformName()));
|
||||
return 1;
|
||||
}
|
||||
new WaylandClipboard(&app);
|
||||
return app.exec();
|
||||
}
|
174
helpers/xclipboardsync/waylandclipboard.cpp
Normal file
174
helpers/xclipboardsync/waylandclipboard.cpp
Normal file
|
@ -0,0 +1,174 @@
|
|||
/********************************************************************
|
||||
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 <unistd.h>
|
||||
|
||||
using namespace KWayland::Client;
|
||||
|
||||
WaylandClipboard::WaylandClipboard(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_thread(new QThread)
|
||||
, m_connectionThread(new ConnectionThread)
|
||||
{
|
||||
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();
|
||||
}
|
56
helpers/xclipboardsync/waylandclipboard.h
Normal file
56
helpers/xclipboardsync/waylandclipboard.h
Normal file
|
@ -0,0 +1,56 @@
|
|||
/********************************************************************
|
||||
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/>.
|
||||
*********************************************************************/
|
||||
#ifndef WAYLANDCLIPBOARD_H
|
||||
#define WAYLANDCLIPBOARD_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class QThread;
|
||||
|
||||
namespace KWayland
|
||||
{
|
||||
namespace Client
|
||||
{
|
||||
class ConnectionThread;
|
||||
class Seat;
|
||||
class DataDeviceManager;
|
||||
class DataDevice;
|
||||
class DataSource;
|
||||
}
|
||||
}
|
||||
|
||||
class WaylandClipboard : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit WaylandClipboard(QObject *parent);
|
||||
~WaylandClipboard();
|
||||
|
||||
private:
|
||||
void setup();
|
||||
QThread *m_thread;
|
||||
KWayland::Client::ConnectionThread *m_connectionThread;
|
||||
KWayland::Client::Seat *m_seat = nullptr;
|
||||
KWayland::Client::DataDeviceManager *m_dataDeviceManager = nullptr;
|
||||
KWayland::Client::DataDevice *m_dataDevice = nullptr;
|
||||
KWayland::Client::DataSource *m_dataSource = nullptr;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -26,6 +26,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
#include "wayland_server.h"
|
||||
#include "workspace.h"
|
||||
// KWayland
|
||||
#include <KWayland/Server/datadevice_interface.h>
|
||||
#include <KWayland/Server/seat_interface.h>
|
||||
//screenlocker
|
||||
#include <KScreenLocker/KsldApp>
|
||||
|
@ -452,6 +453,19 @@ void KeyboardInputRedirection::update()
|
|||
if (found && found->surface()) {
|
||||
if (found->surface() != seat->focusedKeyboardSurface()) {
|
||||
seat->setFocusedKeyboardSurface(found->surface());
|
||||
auto newKeyboard = seat->focusedKeyboard();
|
||||
if (newKeyboard && newKeyboard->client() == waylandServer()->xWaylandConnection()) {
|
||||
// focus passed to an XWayland surface
|
||||
const auto selection = seat->selection();
|
||||
auto xclipboard = waylandServer()->xclipboardSyncDataDevice();
|
||||
if (xclipboard && selection != xclipboard.data()) {
|
||||
if (selection) {
|
||||
xclipboard->sendSelection(selection);
|
||||
} else {
|
||||
xclipboard->sendClearSelection();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
seat->setFocusedKeyboardSurface(nullptr);
|
||||
|
|
|
@ -57,6 +57,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
// system
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
//screenlocker
|
||||
#include <KScreenLocker/KsldApp>
|
||||
|
@ -75,6 +76,7 @@ WaylandServer::WaylandServer(QObject *parent)
|
|||
qRegisterMetaType<KWayland::Server::OutputInterface::DpmsMode>();
|
||||
|
||||
connect(kwinApp(), &Application::screensCreated, this, &WaylandServer::initOutputs);
|
||||
connect(kwinApp(), &Application::x11ConnectionChanged, this, &WaylandServer::setupX11ClipboardSync);
|
||||
}
|
||||
|
||||
WaylandServer::~WaylandServer()
|
||||
|
@ -175,7 +177,25 @@ void WaylandServer::init(const QByteArray &socketName, InitalizationFlags flags)
|
|||
m_display->createShm();
|
||||
m_seat = m_display->createSeat(m_display);
|
||||
m_seat->create();
|
||||
m_display->createDataDeviceManager(m_display)->create();
|
||||
auto ddm = m_display->createDataDeviceManager(m_display);
|
||||
ddm->create();
|
||||
connect(ddm, &DataDeviceManagerInterface::dataDeviceCreated, this,
|
||||
[this] (DataDeviceInterface *ddi) {
|
||||
if (ddi->client() == m_xclipbaordSync.client && m_xclipbaordSync.client != nullptr) {
|
||||
m_xclipbaordSync.ddi = QPointer<DataDeviceInterface>(ddi);
|
||||
connect(m_xclipbaordSync.ddi.data(), &DataDeviceInterface::selectionChanged, this,
|
||||
[this] {
|
||||
// testing whether the active client inherits Client
|
||||
// it would be better to test for the keyboard focus, but we might get a clipboard update
|
||||
// when the Client is already active, but no Surface is created yet.
|
||||
if (workspace()->activeClient() && workspace()->activeClient()->inherits("KWin::Client")) {
|
||||
m_seat->setSelection(m_xclipbaordSync.ddi.data());
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
m_display->createIdle(m_display)->create();
|
||||
m_plasmaShell = m_display->createPlasmaShell(m_display);
|
||||
m_plasmaShell->create();
|
||||
|
@ -333,6 +353,10 @@ void WaylandServer::destroyXWaylandConnection()
|
|||
if (!m_xwayland.client) {
|
||||
return;
|
||||
}
|
||||
// first terminate the clipboard sync
|
||||
if (m_xclipbaordSync.process) {
|
||||
m_xclipbaordSync.process->terminate();
|
||||
}
|
||||
disconnect(m_xwayland.destroyConnection);
|
||||
m_xwayland.client->destroy();
|
||||
m_xwayland.client = nullptr;
|
||||
|
@ -358,6 +382,52 @@ void WaylandServer::destroyInputMethodConnection()
|
|||
m_inputMethodServerConnection = nullptr;
|
||||
}
|
||||
|
||||
int WaylandServer::createXclipboardSyncConnection()
|
||||
{
|
||||
int sx[2];
|
||||
if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) < 0) {
|
||||
qCWarning(KWIN_CORE) << "Could not create socket";
|
||||
return -1;
|
||||
}
|
||||
m_xclipbaordSync.client = m_display->createClient(sx[0]);
|
||||
return sx[1];
|
||||
}
|
||||
|
||||
void WaylandServer::setupX11ClipboardSync()
|
||||
{
|
||||
if (m_xclipbaordSync.process) {
|
||||
return;
|
||||
}
|
||||
|
||||
int socket = dup(createXclipboardSyncConnection());
|
||||
if (socket == -1) {
|
||||
delete m_xclipbaordSync.client;
|
||||
m_xclipbaordSync.client = nullptr;
|
||||
return;
|
||||
}
|
||||
if (socket >= 0) {
|
||||
QProcessEnvironment environment = kwinApp()->processStartupEnvironment();
|
||||
environment.insert(QStringLiteral("WAYLAND_SOCKET"), QByteArray::number(socket));
|
||||
environment.insert(QStringLiteral("DISPLAY"), QString::fromUtf8(qgetenv("DISPLAY")));
|
||||
environment.remove("WAYLAND_DISPLAY");
|
||||
m_xclipbaordSync.process = new Process(this);
|
||||
m_xclipbaordSync.process->setProcessChannelMode(QProcess::ForwardedErrorChannel);
|
||||
auto finishedSignal = static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished);
|
||||
connect(m_xclipbaordSync.process, finishedSignal, this,
|
||||
[this] {
|
||||
m_xclipbaordSync.process->deleteLater();
|
||||
m_xclipbaordSync.process = nullptr;
|
||||
m_xclipbaordSync.ddi.clear();
|
||||
m_xclipbaordSync.client->destroy();
|
||||
m_xclipbaordSync.client = nullptr;
|
||||
// TODO: restart
|
||||
}
|
||||
);
|
||||
m_xclipbaordSync.process->setProcessEnvironment(environment);
|
||||
m_xclipbaordSync.process->start(QStringLiteral(KWIN_XCLIPBOARD_SYNC_BIN));
|
||||
}
|
||||
}
|
||||
|
||||
void WaylandServer::createInternalConnection()
|
||||
{
|
||||
int sx[2];
|
||||
|
|
|
@ -23,8 +23,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
#include <kwinglobals.h>
|
||||
|
||||
#include <QObject>
|
||||
#include <QPointer>
|
||||
|
||||
class QThread;
|
||||
class QProcess;
|
||||
class QWindow;
|
||||
|
||||
namespace KWayland
|
||||
|
@ -41,6 +43,7 @@ namespace Server
|
|||
class ClientConnection;
|
||||
class CompositorInterface;
|
||||
class Display;
|
||||
class DataDeviceInterface;
|
||||
class ShellInterface;
|
||||
class SeatInterface;
|
||||
class ServerSideDecorationManagerInterface;
|
||||
|
@ -118,6 +121,8 @@ public:
|
|||
int createInputMethodConnection();
|
||||
void destroyInputMethodConnection();
|
||||
|
||||
int createXclipboardSyncConnection();
|
||||
|
||||
/**
|
||||
* @returns true if screen is locked.
|
||||
**/
|
||||
|
@ -142,6 +147,9 @@ public:
|
|||
KWayland::Server::ClientConnection *screenLockerClientConnection() const {
|
||||
return m_screenLockerClientConnection;
|
||||
}
|
||||
QPointer<KWayland::Server::DataDeviceInterface> xclipboardSyncDataDevice() const {
|
||||
return m_xclipbaordSync.ddi;
|
||||
}
|
||||
KWayland::Client::ShmPool *internalShmPool() {
|
||||
return m_internalConnection.shm;
|
||||
}
|
||||
|
@ -160,6 +168,7 @@ Q_SIGNALS:
|
|||
void terminatingInternalClientConnection();
|
||||
|
||||
private:
|
||||
void setupX11ClipboardSync();
|
||||
void shellClientShown(Toplevel *t);
|
||||
void initOutputs();
|
||||
quint16 createClientId(KWayland::Server::ClientConnection *c);
|
||||
|
@ -188,6 +197,11 @@ private:
|
|||
KWayland::Client::ShmPool *shm = nullptr;
|
||||
|
||||
} m_internalConnection;
|
||||
struct {
|
||||
QProcess *process = nullptr;
|
||||
KWayland::Server::ClientConnection *client = nullptr;
|
||||
QPointer<KWayland::Server::DataDeviceInterface> ddi;
|
||||
} m_xclipbaordSync;
|
||||
QList<ShellClient*> m_clients;
|
||||
QList<ShellClient*> m_internalClients;
|
||||
QHash<KWayland::Server::ClientConnection*, quint16> m_clientIds;
|
||||
|
|
Loading…
Reference in a new issue