diff --git a/CMakeLists.txt b/CMakeLists.txt index 9d16a22b3dafec8c2fa0605b7e855ee44748dada..17a923d1c2d820694cdde5f21954b9cf6726409f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,9 @@ find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Svg Widgets Quick find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings NO_POLICY_SCOPE) diff --git a/klipper/CMakeLists.txt b/klipper/CMakeLists.txt index 5d400c61c4e64452bb2b30365658922bdc8eab48..25bbcdb4dcf8ece3877d8bb624cfb6916f2e9d89 100644 --- a/klipper/CMakeLists.txt +++ b/klipper/CMakeLists.txt @@ -3,6 +3,9 @@ add_definitions(-DTRANSLATION_DOMAIN=\"klipper\") add_definitions("-DQT_NO_CAST_FROM_ASCII -DQT_NO_CAST_TO_ASCII") add_definitions(-DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT) add_definitions(-DQT_NO_URL_CAST_FROM_STRING) + +add_subdirectory(systemclipboard) + set(libklipper_common_SRCS klipper.cpp urlgrabber.cpp @@ -37,7 +40,6 @@ kconfig_add_kcfg_files(libklipper_common_SRCS klippersettings.kcfgc) set(klipper_KDEINIT_SRCS ${libklipper_common_SRCS} main.cpp tray.cpp) - kf5_add_kdeinit_executable(klipper ${klipper_KDEINIT_SRCS}) target_link_libraries(kdeinit_klipper @@ -54,6 +56,7 @@ target_link_libraries(kdeinit_klipper KF5::WidgetsAddons KF5::XmlGui ${ZLIB_LIBRARY} + systemclipboard ) if (X11_FOUND) target_link_libraries(kdeinit_klipper XCB::XCB Qt5::X11Extras) @@ -88,6 +91,7 @@ target_link_libraries(plasma_engine_clipboard KF5::WindowSystem KF5::XmlGui # KActionCollection ${ZLIB_LIBRARY} + systemclipboard ) if (X11_FOUND) target_link_libraries(plasma_engine_clipboard XCB::XCB Qt5::X11Extras) diff --git a/klipper/klipper.cpp b/klipper/klipper.cpp index d022c4a43f869e61252defc0caa379af72b773fb..7fd75894541897b5e2776d0bd3500036040e8d20 100644 --- a/klipper/klipper.cpp +++ b/klipper/klipper.cpp @@ -52,6 +52,8 @@ #include "historystringitem.h" #include "klipperpopup.h" +#include "systemclipboard.h" + #ifdef HAVE_PRISON #include #endif @@ -102,10 +104,9 @@ Klipper::Klipper(QObject* parent, const KSharedConfigPtr& config, KlipperMode mo QDBusConnection::sessionBus().registerObject(QStringLiteral("/klipper"), this, QDBusConnection::ExportScriptableSlots); updateTimestamp(); // read initial X user time - m_clip = qApp->clipboard(); + m_clip = SystemClipboard::instance(); - connect( m_clip, &QClipboard::changed, - this, &Klipper::newClipData ); + connect( m_clip, &SystemClipboard::changed, this, &Klipper::newClipData ); connect( &m_overflowClearTimer, &QTimer::timeout, this, &Klipper::slotClearOverflow); @@ -717,17 +718,18 @@ void Klipper::checkClipData( bool selectionMode ) qCDebug(KLIPPER_LOG) << "Checking clip data"; const QMimeData* data = m_clip->mimeData( selectionMode ? QClipboard::Selection : QClipboard::Clipboard ); - if ( !data ) { - qCWarning(KLIPPER_LOG) << "No data in clipboard. This not not supposed to happen."; - return; - } + bool clipEmpty = false; bool changed = true; // ### FIXME (only relevant under polling, might be better to simply remove polling and rely on XFixes) - bool clipEmpty = data->formats().isEmpty(); - if (clipEmpty) { - // Might be a timeout. Try again + if ( !data ) { + clipEmpty = true; + } else { clipEmpty = data->formats().isEmpty(); - qCDebug(KLIPPER_LOG) << "was empty. Retried, now " << (clipEmpty?" still empty":" no longer empty"); + if (clipEmpty) { + // Might be a timeout. Try again + clipEmpty = data->formats().isEmpty(); + qCDebug(KLIPPER_LOG) << "was empty. Retried, now " << (clipEmpty?" still empty":" no longer empty"); + } } if ( changed && clipEmpty && m_bNoNullClipboard ) { diff --git a/klipper/klipper.h b/klipper/klipper.h index 2651b0fd7882967690a04e56cbc79bdb34214c26..44ac5c2930f57949278138dc046eba332722d99a 100644 --- a/klipper/klipper.h +++ b/klipper/klipper.h @@ -41,6 +41,7 @@ class QMenu; class QMimeData; class HistoryItem; class KNotification; +class SystemClipboard; enum class KlipperMode { Standalone, @@ -159,7 +160,7 @@ private: static void updateTimestamp(); - QClipboard* m_clip; + SystemClipboard* m_clip; QElapsedTimer m_showTimer; diff --git a/klipper/systemclipboard/CMakeLists.txt b/klipper/systemclipboard/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..bb90fa4dca908249f26c3d0587c753e2443cd910 --- /dev/null +++ b/klipper/systemclipboard/CMakeLists.txt @@ -0,0 +1,27 @@ +find_package(QtWaylandScanner REQUIRED) +include_directories(SYSTEM ${Qt5Gui_PRIVATE_INCLUDE_DIRS}) # for native interface to get wl_seat +find_package(Wayland 1.15 COMPONENTS Client) +find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS WaylandClient) + +set(systemclipboard_SRCS + systemclipboard.cpp + qtclipboard.cpp + waylandclipboard.cpp +) + +ecm_add_qtwayland_client_protocol(systemclipboard_SRCS + PROTOCOL wlr-data-control-unstable-v1.xml + BASENAME wlr-data-control-unstable-v1 +) + +add_library(systemclipboard STATIC ${systemclipboard_SRCS}) +target_link_libraries(systemclipboard + Qt5::Gui + Qt5::WaylandClient + Wayland::Client + KF5::WindowSystem +) + +if(BUILD_TESTING) + add_subdirectory(tests) +endif() diff --git a/klipper/systemclipboard/qtclipboard.cpp b/klipper/systemclipboard/qtclipboard.cpp new file mode 100644 index 0000000000000000000000000000000000000000..884458e9377175e494adb081f13890d8ca18f2ba --- /dev/null +++ b/klipper/systemclipboard/qtclipboard.cpp @@ -0,0 +1,44 @@ +/* + Copyright (C) 2020 David Edmundson + + This program is free software; you can redistribute it and/or + modify it under the terms of the Lesser 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 Lesser GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "qtclipboard.h" + +#include +#include + +QtClipboard::QtClipboard(QObject *parent) + : SystemClipboard(parent) +{ + connect(qApp->clipboard(), &QClipboard::changed, this, &QtClipboard::changed); +} + +void QtClipboard::setMimeData(QMimeData *mime, QClipboard::Mode mode) +{ + qApp->clipboard()->setMimeData(mime, mode); +} + +void QtClipboard::clear(QClipboard::Mode mode) +{ + qApp->clipboard()->clear(mode); +} + +const QMimeData *QtClipboard::mimeData(QClipboard::Mode mode) const +{ + return qApp->clipboard()->mimeData(mode); +} diff --git a/klipper/systemclipboard/qtclipboard.h b/klipper/systemclipboard/qtclipboard.h new file mode 100644 index 0000000000000000000000000000000000000000..2a29121878e7ff95cf6b00f2a195783158a2534e --- /dev/null +++ b/klipper/systemclipboard/qtclipboard.h @@ -0,0 +1,31 @@ +/* + Copyright (C) 2020 David Edmundson + + This program is free software; you can redistribute it and/or + modify it under the terms of the Lesser 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 Lesser GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#pragma once +#include "systemclipboard.h" + +class QtClipboard : public SystemClipboard +{ +public: + explicit QtClipboard(QObject *parent); + void setMimeData(QMimeData *mime, QClipboard::Mode mode) override; + void clear(QClipboard::Mode mode) override; + const QMimeData *mimeData(QClipboard::Mode mode) const override; +}; + diff --git a/klipper/systemclipboard/systemclipboard.cpp b/klipper/systemclipboard/systemclipboard.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d40a2330778f83d130cceac6d0a92dd9054e010e --- /dev/null +++ b/klipper/systemclipboard/systemclipboard.cpp @@ -0,0 +1,48 @@ +/* + Copyright (C) 2020 David Edmundson + + This program is free software; you can redistribute it and/or + modify it under the terms of the Lesser 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 Lesser GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "systemclipboard.h" + +#include "qtclipboard.h" +#include "waylandclipboard.h" + +#include +#include + +SystemClipboard *SystemClipboard::instance() +{ + if (!qApp || qApp->closingDown()) { + return nullptr; + } + static SystemClipboard *systemClipboard = nullptr; + if (!systemClipboard) { + if (KWindowSystem::isPlatformWayland()) { + systemClipboard = new WaylandClipboard(qApp); + } else { + systemClipboard = new QtClipboard(qApp); + } + } + return systemClipboard; +} + +SystemClipboard::SystemClipboard(QObject *parent) + : QObject(parent) +{ +} + diff --git a/klipper/systemclipboard/systemclipboard.h b/klipper/systemclipboard/systemclipboard.h new file mode 100644 index 0000000000000000000000000000000000000000..4ce169e23eedd93eca751b66b019f93b937af812 --- /dev/null +++ b/klipper/systemclipboard/systemclipboard.h @@ -0,0 +1,60 @@ +/* + Copyright (C) 2020 David Edmundson + + This program is free software; you can redistribute it and/or + modify it under the terms of the Lesser 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 Lesser GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#pragma once + +#include +#include +#include + +/** + * This class mimics QClipboard but unlike QClipboard it will continue + * to get updates even when our window does not have focus. + * + * This may require extra access permissions + */ +class SystemClipboard : public QObject +{ + Q_OBJECT +public: + /** + * Returns a shared global SystemClipboard instance + */ + static SystemClipboard *instance(); + + /** + * Sets the clipboard to the new contents + * The clpboard takes ownership of mime + */ + //maybe I should unique_ptr it to be expressive, but then I don't match QClipboard? + virtual void setMimeData(QMimeData *mime, QClipboard::Mode mode) = 0; + /** + * Clears the current clipboard + */ + virtual void clear(QClipboard::Mode mode) = 0; + /** + * Returns the current mime data received by the clipboard + */ + virtual const QMimeData *mimeData(QClipboard::Mode mode) const = 0; +Q_SIGNALS: + void changed(QClipboard::Mode mode); + +protected: + SystemClipboard(QObject *parent); +}; diff --git a/klipper/systemclipboard/tests/CMakeLists.txt b/klipper/systemclipboard/tests/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..da0566691f2e437c8bca0ca1e6ae87b60a4b51bc --- /dev/null +++ b/klipper/systemclipboard/tests/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(pasteclient paste.cpp) + +target_link_libraries(pasteclient + systemclipboard +) diff --git a/klipper/systemclipboard/tests/paste.cpp b/klipper/systemclipboard/tests/paste.cpp new file mode 100644 index 0000000000000000000000000000000000000000..844d5a91f304d24dbf1240f33cf4841191d34f07 --- /dev/null +++ b/klipper/systemclipboard/tests/paste.cpp @@ -0,0 +1,45 @@ +/* + * Copyright 2020 David Edmundson + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include + +#include "../systemclipboard/systemclipboard.h" + +int main(int argc, char ** argv) +{ + QGuiApplication app(argc, argv); + auto clip = SystemClipboard::instance(); + QObject::connect(clip, &SystemClipboard::changed, &app, [clip](QClipboard::Mode mode) { + if (mode != QClipboard::Clipboard) { + return; + } + auto dbg = qDebug(); + dbg << "New clipboard content: "; + + if (clip->mimeData(QClipboard::Clipboard)) { + dbg << clip->mimeData(QClipboard::Clipboard)->text(); + } else { + dbg << "[empty]"; + } + }); + + qDebug() << "Watching for new clipboard content..."; + + app.exec(); +} diff --git a/klipper/systemclipboard/waylandclipboard.cpp b/klipper/systemclipboard/waylandclipboard.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d32a9321e20b425b4dd910bb289158e09835ef2b --- /dev/null +++ b/klipper/systemclipboard/waylandclipboard.cpp @@ -0,0 +1,329 @@ +/* + Copyright (C) 2020 David Edmundson + + This program is free software; you can redistribute it and/or + modify it under the terms of the Lesser 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 Lesser GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "waylandclipboard.h" + +#include +#include +#include +#include +#include + +#include + +#include + +#include + +#include "qwayland-wlr-data-control-unstable-v1.h" + +class DataControlDeviceManager : public QWaylandClientExtensionTemplate + , public QtWayland::zwlr_data_control_manager_v1 +{ + Q_OBJECT +public: + DataControlDeviceManager() + : QWaylandClientExtensionTemplate(1) + { + } + + ~DataControlDeviceManager() { + destroy(); + } +}; + +class DataControlOffer : public QMimeData, public QtWayland::zwlr_data_control_offer_v1 +{ + Q_OBJECT +public: + DataControlOffer(struct ::zwlr_data_control_offer_v1 *id): + QtWayland::zwlr_data_control_offer_v1(id) + { + } + + ~DataControlOffer() { + destroy(); + } + + QStringList formats() const override + { + return m_receivedFormats; + } + + bool hasFormat(const QString &format) const override { + return m_receivedFormats.contains(format); + } +protected: + void zwlr_data_control_offer_v1_offer(const QString &mime_type) override { + m_receivedFormats << mime_type; + } + + QVariant retrieveData(const QString &mimeType, QVariant::Type type) const override; +private: + static bool readData(int fd, QByteArray &data); + QStringList m_receivedFormats; +}; + + +QVariant DataControlOffer::retrieveData(const QString &mimeType, QVariant::Type type) const +{ + if (!hasFormat(mimeType)) { + return QVariant(); + } + Q_UNUSED(type); + + int pipeFds[2]; + if (pipe(pipeFds) != 0){ + return QVariant(); + } + + auto t = const_cast(this); + t->receive(mimeType, pipeFds[1]); + + close(pipeFds[1]); + + /* + * Ideally we need to introduce a non-blocking QMimeData object + * Or a non-blocking constructor to QMimeData with the mimetypes that are relevant + * + * However this isn't actually any worse than X. + */ + + QPlatformNativeInterface *native = qApp->platformNativeInterface(); + auto display = static_cast(native->nativeResourceForIntegration("wl_display")); + wl_display_flush(display); + + QFile readPipe; + if (readPipe.open(pipeFds[0], QIODevice::ReadOnly)) { + QByteArray data; + if (readData(pipeFds[0], data)) { + return data; + } + close(pipeFds[0]); + } + return QVariant(); +} + +// reads data from a file descriptor with a timeout of 1 second +// true if data is read successfully +bool DataControlOffer::readData(int fd, QByteArray &data) +{ + fd_set readset; + FD_ZERO(&readset); + FD_SET(fd, &readset); + struct timeval timeout; + timeout.tv_sec = 1; + timeout.tv_usec = 0; + + Q_FOREVER { + int ready = select(FD_SETSIZE, &readset, nullptr, nullptr, &timeout); + if (ready < 0) { + qWarning() << "DataControlOffer: select() failed"; + return false; + } else if (ready == 0) { + qWarning("DataControlOffer: timeout reading from pipe"); + return false; + } else { + char buf[4096]; + int n = read(fd, buf, sizeof buf); + + if (n < 0) { + qWarning("DataControlOffer: read() failed"); + return false; + } else if (n == 0) { + return true; + } else if (n > 0) { + data.append(buf, n); + } + } + } +} + + +class DataControlSource : public QObject, public QtWayland::zwlr_data_control_source_v1 +{ + Q_OBJECT +public: + DataControlSource(struct ::zwlr_data_control_source_v1 *id, QMimeData *mimeData); + DataControlSource(); + ~DataControlSource() { + destroy(); + } + + QMimeData *mimeData() { + return m_mimeData; + } + +Q_SIGNALS: + void cancelled(); + +protected: + void zwlr_data_control_source_v1_send(const QString &mime_type, int32_t fd) override; + void zwlr_data_control_source_v1_cancelled() override; +private: + QMimeData *m_mimeData; +}; + +DataControlSource::DataControlSource(struct ::zwlr_data_control_source_v1 *id, QMimeData *mimeData) + : QtWayland::zwlr_data_control_source_v1(id) + , m_mimeData(mimeData) +{ + for (const QString &format: mimeData->formats()) { + offer(format); + } +} + +void DataControlSource::zwlr_data_control_source_v1_send(const QString &mime_type, int32_t fd) +{ + QFile c; + if (c.open(fd, QFile::WriteOnly, QFile::AutoCloseHandle)) { + c.write(m_mimeData->data(mime_type)); + c.close(); + } +} + +void DataControlSource::zwlr_data_control_source_v1_cancelled() +{ + Q_EMIT cancelled(); +} + +class DataControlDevice : public QObject, public QtWayland::zwlr_data_control_device_v1 +{ + Q_OBJECT +public: + DataControlDevice(struct ::zwlr_data_control_device_v1 *id) + : QtWayland::zwlr_data_control_device_v1(id) + {} + + ~DataControlDevice() { + destroy(); + } + + void setSelection(std::unique_ptr selection); + QMimeData *receivedSelection() { + return m_receivedSelection.get(); + } + QMimeData *selection() { + return m_selection ? m_selection->mimeData() : nullptr; + } + +Q_SIGNALS: + void receivedSelectionChanged(); + void selectionChanged(); +protected: + void zwlr_data_control_device_v1_data_offer(struct ::zwlr_data_control_offer_v1 *id) override { + new DataControlOffer(id); + // this will become memory managed when we retrieve the selection event + // a compositor calling data_offer without doing that would be a bug + } + + void zwlr_data_control_device_v1_selection(struct ::zwlr_data_control_offer_v1 *id) override { + if(!id ) { + m_receivedSelection.reset(); + } else { + auto deriv = QtWayland::zwlr_data_control_offer_v1::fromObject(id); + auto offer = dynamic_cast(deriv); // dynamic because of the dual inheritance + m_receivedSelection.reset(offer); + } + emit receivedSelectionChanged(); + } + +private: + std::unique_ptr m_selection; // selection set locally + std::unique_ptr m_receivedSelection; // latest selection set from externally to here +}; + + +void DataControlDevice::setSelection(std::unique_ptr selection) +{ + m_selection = std::move(selection); + connect(m_selection.get(), &DataControlSource::cancelled, this, [this]() { + m_selection.reset(); + Q_EMIT selectionChanged(); + }); + set_selection(m_selection->object()); + Q_EMIT selectionChanged(); +} + +WaylandClipboard::WaylandClipboard(QObject *parent) + : SystemClipboard(parent) + , m_manager(new DataControlDeviceManager) +{ + connect(m_manager.get(), &DataControlDeviceManager::activeChanged, this, [this]() { + if (m_manager->isActive()) { + + QPlatformNativeInterface *native = qApp->platformNativeInterface(); + if (!native) { + return; + } + auto seat = static_cast(native->nativeResourceForIntegration("wl_seat")); + if (!seat) { + return; + } + + m_device.reset(new DataControlDevice(m_manager->get_data_device(seat))); + + connect(m_device.get(), &DataControlDevice::receivedSelectionChanged, this, [this]() { + emit changed(QClipboard::Clipboard); + }); + connect(m_device.get(), &DataControlDevice::selectionChanged, this, [this]() { + emit changed(QClipboard::Clipboard); + }); + } else { + m_device.reset(); + } + }); +} + +void WaylandClipboard::setMimeData(QMimeData *mime, QClipboard::Mode mode) +{ + if (!m_device) { + return; + } + auto source = std::make_unique(m_manager->create_data_source(), mime); + if (mode == QClipboard::Clipboard) { + m_device->setSelection(std::move(source)); + } +} + +void WaylandClipboard::clear(QClipboard::Mode mode) +{ + if (!m_device) { + return; + } + if (mode == QClipboard::Clipboard) { + m_device->set_selection(nullptr); + } else if (mode == QClipboard::Selection) { + m_device->set_primary_selection(nullptr); + } +} + +const QMimeData *WaylandClipboard::mimeData(QClipboard::Mode mode) const +{ + if (!m_device) { + return nullptr; + } + if (mode == QClipboard::Clipboard) { + // return our locally set selection if it's not cancelled to avoid copying data to ourselves + return m_device->selection() ? m_device->selection() : m_device->receivedSelection(); + } + return nullptr; +} + +#include "waylandclipboard.moc" diff --git a/klipper/systemclipboard/waylandclipboard.h b/klipper/systemclipboard/waylandclipboard.h new file mode 100644 index 0000000000000000000000000000000000000000..7d950cb12b99514365fb822060a6c88e4c8953a6 --- /dev/null +++ b/klipper/systemclipboard/waylandclipboard.h @@ -0,0 +1,37 @@ +/* + Copyright (C) 2020 David Edmundson + + This program is free software; you can redistribute it and/or + modify it under the terms of the Lesser 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 Lesser GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#pragma once +#include "systemclipboard.h" +#include + +class DataControlDevice; +class DataControlDeviceManager; + +class WaylandClipboard : public SystemClipboard +{ +public: + WaylandClipboard(QObject *parent); + void setMimeData(QMimeData *mime, QClipboard::Mode mode) override; + void clear(QClipboard::Mode mode) override; + const QMimeData *mimeData(QClipboard::Mode mode) const override; +private: + std::unique_ptr m_manager; + std::unique_ptr m_device; +}; diff --git a/klipper/systemclipboard/wlr-data-control-unstable-v1.xml b/klipper/systemclipboard/wlr-data-control-unstable-v1.xml new file mode 100644 index 0000000000000000000000000000000000000000..75e8671b0deffb1810ec120135976098ed266a7c --- /dev/null +++ b/klipper/systemclipboard/wlr-data-control-unstable-v1.xml @@ -0,0 +1,278 @@ + + + + Copyright © 2018 Simon Ser + Copyright © 2019 Ivan Molodetskikh + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + This protocol allows a privileged client to control data devices. In + particular, the client will be able to manage the current selection and take + the role of a clipboard manager. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + This interface is a manager that allows creating per-seat data device + controls. + + + + + Create a new data source. + + + + + + + Create a data device that can be used to manage a seat's selection. + + + + + + + + All objects created by the manager will still remain valid, until their + appropriate destroy request has been called. + + + + + + + This interface allows a client to manage a seat's selection. + + When the seat is destroyed, this object becomes inert. + + + + + This request asks the compositor to set the selection to the data from + the source on behalf of the client. + + The given source may not be used in any further set_selection or + set_primary_selection requests. Attempting to use a previously used + source is a protocol error. + + To unset the selection, set the source to NULL. + + + + + + + Destroys the data device object. + + + + + + The data_offer event introduces a new wlr_data_control_offer object, + which will subsequently be used in either the + wlr_data_control_device.selection event (for the regular clipboard + selections) or the wlr_data_control_device.primary_selection event (for + the primary clipboard selections). Immediately following the + wlr_data_control_device.data_offer event, the new data_offer object + will send out wlr_data_control_offer.offer events to describe the MIME + types it offers. + + + + + + + The selection event is sent out to notify the client of a new + wlr_data_control_offer for the selection for this device. The + wlr_data_control_device.data_offer and the wlr_data_control_offer.offer + events are sent out immediately before this event to introduce the data + offer object. The selection event is sent to a client when a new + selection is set. The wlr_data_control_offer is valid until a new + wlr_data_control_offer or NULL is received. The client must destroy the + previous selection wlr_data_control_offer, if any, upon receiving this + event. + + The first selection event is sent upon binding the + wlr_data_control_device object. + + + + + + + This data control object is no longer valid and should be destroyed by + the client. + + + + + + + + The primary_selection event is sent out to notify the client of a new + wlr_data_control_offer for the primary selection for this device. The + wlr_data_control_device.data_offer and the wlr_data_control_offer.offer + events are sent out immediately before this event to introduce the data + offer object. The primary_selection event is sent to a client when a + new primary selection is set. The wlr_data_control_offer is valid until + a new wlr_data_control_offer or NULL is received. The client must + destroy the previous primary selection wlr_data_control_offer, if any, + upon receiving this event. + + If the compositor supports primary selection, the first + primary_selection event is sent upon binding the + wlr_data_control_device object. + + + + + + + This request asks the compositor to set the primary selection to the + data from the source on behalf of the client. + + The given source may not be used in any further set_selection or + set_primary_selection requests. Attempting to use a previously used + source is a protocol error. + + To unset the primary selection, set the source to NULL. + + The compositor will ignore this request if it does not support primary + selection. + + + + + + + + + + + + The wlr_data_control_source object is the source side of a + wlr_data_control_offer. It is created by the source client in a data + transfer and provides a way to describe the offered data and a way to + respond to requests to transfer the data. + + + + + + + + + This request adds a MIME type to the set of MIME types advertised to + targets. Can be called several times to offer multiple types. + + Calling this after wlr_data_control_device.set_selection is a protocol + error. + + + + + + + Destroys the data source object. + + + + + + Request for data from the client. Send the data as the specified MIME + type over the passed file descriptor, then close it. + + + + + + + + This data source is no longer valid. The data source has been replaced + by another data source. + + The client should clean up and destroy this data source. + + + + + + + A wlr_data_control_offer represents a piece of data offered for transfer + by another client (the source client). The offer describes the different + MIME types that the data can be converted to and provides the mechanism + for transferring the data directly from the source client. + + + + + To transfer the offered data, the client issues this request and + indicates the MIME type it wants to receive. The transfer happens + through the passed file descriptor (typically created with the pipe + system call). The source client writes the data in the MIME type + representation requested and then closes the file descriptor. + + The receiving client reads from the read end of the pipe until EOF and + then closes its end, at which point the transfer is complete. + + This request may happen multiple times for different MIME types. + + + + + + + + Destroys the data offer object. + + + + + + Sent immediately after creating the wlr_data_control_offer object. + One event per offered MIME type. + + + + +