diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -335,6 +335,7 @@ add_subdirectory( scripts ) add_subdirectory( tabbox ) add_subdirectory(scripting) +add_subdirectory(helpers) ########### next target ############### diff --git a/autotests/wayland/CMakeLists.txt b/autotests/wayland/CMakeLists.txt --- a/autotests/wayland/CMakeLists.txt +++ b/autotests/wayland/CMakeLists.txt @@ -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 @@ 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) diff --git a/autotests/wayland/helper/CMakeLists.txt b/autotests/wayland/helper/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/autotests/wayland/helper/CMakeLists.txt @@ -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) diff --git a/autotests/wayland/helper/copy.cpp b/autotests/wayland/helper/copy.cpp new file mode 100644 --- /dev/null +++ b/autotests/wayland/helper/copy.cpp @@ -0,0 +1,71 @@ +/******************************************************************** +KWin - the KDE window manager +This file is part of the KDE project. + +Copyright (C) 2016 Martin Gräßlin + +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 . +*********************************************************************/ +#include +#include +#include +#include +#include + +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 w(new Window); + w->setGeometry(QRect(0, 0, 100, 200)); + w->show(); + + return app.exec(); +} + +#include "copy.moc" diff --git a/autotests/wayland/helper/paste.cpp b/autotests/wayland/helper/paste.cpp new file mode 100644 --- /dev/null +++ b/autotests/wayland/helper/paste.cpp @@ -0,0 +1,68 @@ +/******************************************************************** +KWin - the KDE window manager +This file is part of the KDE project. + +Copyright (C) 2016 Martin Gräßlin + +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 . +*********************************************************************/ +#include +#include +#include +#include +#include + +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 w(new Window); + w->setGeometry(QRect(0, 0, 100, 200)); + w->show(); + + return app.exec(); +} + +#include "paste.moc" diff --git a/autotests/wayland/xclipboardsync_test.cpp b/autotests/wayland/xclipboardsync_test.cpp new file mode 100644 --- /dev/null +++ b/autotests/wayland/xclipboardsync_test.cpp @@ -0,0 +1,181 @@ +/******************************************************************** +KWin - the KDE window manager +This file is part of the KDE project. + +Copyright (C) 2016 Martin Gräßlin + +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 . +*********************************************************************/ +#include "kwin_wayland_test.h" +#include "platform.h" +#include "shell_client.h" +#include "screens.h" +#include "wayland_server.h" +#include "workspace.h" + +#include + +#include +#include + +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(); + qRegisterMetaType(); + qRegisterMetaType(); + 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("copyPlatform"); + QTest::addColumn("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(); + } else { + QVERIFY(shellClientAddedSpy.wait()); + copyClient = shellClientAddedSpy.first().first().value(); + } + 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(&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(); + } else { + QVERIFY(shellClientAddedSpy.wait()); + pasteClient = shellClientAddedSpy.last().first().value(); + } + 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" diff --git a/config-kwin.h.cmake b/config-kwin.h.cmake --- a/config-kwin.h.cmake +++ b/config-kwin.h.cmake @@ -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 diff --git a/helpers/CMakeLists.txt b/helpers/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/helpers/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(xclipboardsync) diff --git a/helpers/xclipboardsync/CMakeLists.txt b/helpers/xclipboardsync/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/helpers/xclipboardsync/CMakeLists.txt @@ -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} ) diff --git a/helpers/xclipboardsync/main.cpp b/helpers/xclipboardsync/main.cpp new file mode 100644 --- /dev/null +++ b/helpers/xclipboardsync/main.cpp @@ -0,0 +1,36 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2016 Martin Gräßlin + +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 . +*********************************************************************/ +#include "waylandclipboard.h" + +#include + +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(); +} diff --git a/helpers/xclipboardsync/waylandclipboard.h b/helpers/xclipboardsync/waylandclipboard.h new file mode 100644 --- /dev/null +++ b/helpers/xclipboardsync/waylandclipboard.h @@ -0,0 +1,56 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2016 Martin Gräßlin + +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 . +*********************************************************************/ +#ifndef WAYLANDCLIPBOARD_H +#define WAYLANDCLIPBOARD_H + +#include + +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 diff --git a/helpers/xclipboardsync/waylandclipboard.cpp b/helpers/xclipboardsync/waylandclipboard.cpp new file mode 100644 --- /dev/null +++ b/helpers/xclipboardsync/waylandclipboard.cpp @@ -0,0 +1,174 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2016 Martin Gräßlin + +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 . +*********************************************************************/ +#include "waylandclipboard.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +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(); +} diff --git a/keyboard_input.cpp b/keyboard_input.cpp --- a/keyboard_input.cpp +++ b/keyboard_input.cpp @@ -26,6 +26,7 @@ #include "wayland_server.h" #include "workspace.h" // KWayland +#include #include //screenlocker #include @@ -452,6 +453,19 @@ 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); diff --git a/wayland_server.h b/wayland_server.h --- a/wayland_server.h +++ b/wayland_server.h @@ -23,8 +23,10 @@ #include #include +#include class QThread; +class QProcess; class QWindow; namespace KWayland @@ -41,6 +43,7 @@ class ClientConnection; class CompositorInterface; class Display; +class DataDeviceInterface; class ShellInterface; class SeatInterface; class ServerSideDecorationManagerInterface; @@ -118,6 +121,8 @@ int createInputMethodConnection(); void destroyInputMethodConnection(); + int createXclipboardSyncConnection(); + /** * @returns true if screen is locked. **/ @@ -142,6 +147,9 @@ KWayland::Server::ClientConnection *screenLockerClientConnection() const { return m_screenLockerClientConnection; } + QPointer xclipboardSyncDataDevice() const { + return m_xclipbaordSync.ddi; + } KWayland::Client::ShmPool *internalShmPool() { return m_internalConnection.shm; } @@ -160,6 +168,7 @@ void terminatingInternalClientConnection(); private: + void setupX11ClipboardSync(); void shellClientShown(Toplevel *t); void initOutputs(); quint16 createClientId(KWayland::Server::ClientConnection *c); @@ -188,6 +197,11 @@ KWayland::Client::ShmPool *shm = nullptr; } m_internalConnection; + struct { + QProcess *process = nullptr; + KWayland::Server::ClientConnection *client = nullptr; + QPointer ddi; + } m_xclipbaordSync; QList m_clients; QList m_internalClients; QHash m_clientIds; diff --git a/wayland_server.cpp b/wayland_server.cpp --- a/wayland_server.cpp +++ b/wayland_server.cpp @@ -57,6 +57,7 @@ // system #include #include +#include //screenlocker #include @@ -75,6 +76,7 @@ qRegisterMetaType(); connect(kwinApp(), &Application::screensCreated, this, &WaylandServer::initOutputs); + connect(kwinApp(), &Application::x11ConnectionChanged, this, &WaylandServer::setupX11ClipboardSync); } WaylandServer::~WaylandServer() @@ -175,7 +177,25 @@ 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(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 @@ 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 @@ 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(&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];