Paste P462

Masterwork From Distant Lands
ActivePublic

Authored by apol on Sep 5 2019, 2:15 PM.
commit 9ee096d18cb59b612f8094bddc6cf865a9d6611e
Author: Aleix Pol <aleixpol@kde.org>
Date: Sun Jul 21 11:29:17 2019 +0200
clipboard: add support for wayland DataDeviceControl
diff --git a/plugins/clipboard/CMakeLists.txt b/plugins/clipboard/CMakeLists.txt
index 3481a9a1..f55ff508 100644
--- a/plugins/clipboard/CMakeLists.txt
+++ b/plugins/clipboard/CMakeLists.txt
@@ -1,8 +1,11 @@
-set(kdeconnect_clipboard_SRCS
- clipboardplugin.cpp
- clipboardlistener.cpp
-)
+find_package(KF5 ${KF5_MIN_VERSION} QUIET OPTIONAL_COMPONENTS Wayland)
-kdeconnect_add_plugin(kdeconnect_clipboard JSON kdeconnect_clipboard.json SOURCES ${kdeconnect_clipboard_SRCS})
+kdeconnect_add_plugin(kdeconnect_clipboard JSON kdeconnect_clipboard.json SOURCES clipboardplugin.cpp clipboardlistener.cpp clipboardlistenerqt.cpp)
target_link_libraries(kdeconnect_clipboard kdeconnectcore Qt5::Gui)
+
+if(TARGET KF5::WaylandClient)
+ target_sources(kdeconnect_clipboard PRIVATE clipboardlistenerwayland.cpp)
+ target_compile_definitions(kdeconnect_clipboard PRIVATE -DWITH_KWAYLAND)
+ target_link_libraries(kdeconnect_clipboard KF5::WaylandClient)
+endif()
diff --git a/plugins/clipboard/clipboardlistener.cpp b/plugins/clipboard/clipboardlistener.cpp
index abd43876..fa82ad49 100644
--- a/plugins/clipboard/clipboardlistener.cpp
+++ b/plugins/clipboard/clipboardlistener.cpp
@@ -19,35 +19,27 @@
*/
#include "clipboardlistener.h"
-
-ClipboardListener::ClipboardListener()
- : clipboard(QGuiApplication::clipboard())
-{
-#ifdef Q_OS_MAC
- connect(&m_clipboardMonitorTimer, &QTimer::timeout, this, [this](){ updateClipboard(QClipboard::Clipboard); });
- m_clipboardMonitorTimer.start(1000); // Refresh 1s
+#include "clipboardlistenerqt.h"
+#ifdef WITH_KWAYLAND
+#include "clipboardlistenerwayland.h"
#endif
- connect(clipboard, &QClipboard::changed, this, &ClipboardListener::updateClipboard);
-}
-
-void ClipboardListener::updateClipboard(QClipboard::Mode mode)
-{
- if (mode != QClipboard::Clipboard) {
- return;
- }
- QString content = clipboard->text();
+ClipboardListener::ClipboardListener()
+{}
- if (content == currentContent) {
- return;
- }
- currentContent = content;
-
- Q_EMIT clipboardChanged(content);
-}
-
-void ClipboardListener::setText(const QString& content)
+ClipboardListener* ClipboardListener::instance()
{
- currentContent = content;
- clipboard->setText(content);
+ static ClipboardListener* me = nullptr;
+ if (!me) {
+#ifdef WITH_KWAYLAND
+ if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) {
+ me = new ClipboardListenerWayland();
+ }
+ else
+#endif
+ {
+ me = new ClipboardListenerQt();
+ }
+ }
+ return me;
}
diff --git a/plugins/clipboard/clipboardlistener.h b/plugins/clipboard/clipboardlistener.h
index 20973b12..2bac5f27 100644
--- a/plugins/clipboard/clipboardlistener.h
+++ b/plugins/clipboard/clipboardlistener.h
@@ -21,7 +21,6 @@
#ifndef CLIPBOARDLISTENER_H
#define CLIPBOARDLISTENER_H
-#include <QTimer>
#include <QObject>
#include <QClipboard>
#include <QGuiApplication>
@@ -29,32 +28,16 @@
/**
* Wrapper around QClipboard, which emits clipboardChanged only when it really changed
*/
-class ClipboardListener : public QObject
+class ClipboardListener : public QObject
{
Q_OBJECT
-private:
- ClipboardListener();
- QString currentContent;
- QClipboard* clipboard;
-#ifdef Q_OS_MAC
- QTimer m_clipboardMonitorTimer;
-#endif
-
public:
+ ClipboardListener();
- static ClipboardListener* instance()
- {
- static ClipboardListener* me = nullptr;
- if (!me) {
- me = new ClipboardListener();
- }
- return me;
- }
-
- void updateClipboard(QClipboard::Mode mode);
+ static ClipboardListener* instance();
- void setText(const QString& content);
+ virtual void setText(const QString& content) = 0;
Q_SIGNALS:
void clipboardChanged(const QString& content);
diff --git a/plugins/clipboard/clipboardlistenerqt.cpp b/plugins/clipboard/clipboardlistenerqt.cpp
new file mode 100644
index 00000000..c53f34b6
--- /dev/null
+++ b/plugins/clipboard/clipboardlistenerqt.cpp
@@ -0,0 +1,54 @@
+/**
+ * Copyright 2016 Albert Vaca <albertvaka@gmail.com>
+ *
+ * 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) version 3 or any later version
+ * accepted by the membership of KDE e.V. (or its successor approved
+ * by the membership of KDE e.V.), which shall act as a proxy
+ * defined in Section 14 of version 3 of the license.
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "clipboardlistenerqt.h"
+
+ClipboardListenerQt::ClipboardListenerQt()
+ : ClipboardListener()
+ , m_clipboard(QGuiApplication::clipboard())
+{
+#ifdef Q_OS_MAC
+ connect(&m_clipboardMonitorTimer, &QTimer::timeout, this, [this](){ updateClipboard(QClipboard::Clipboard); });
+ m_clipboardMonitorTimer.start(1000); // Refresh 1s
+#endif
+ connect(m_clipboard, &QClipboard::changed, this, &ClipboardListenerQt::updateClipboard);
+}
+
+void ClipboardListenerQt::setText(const QString& content)
+{
+ m_currentContent = content;
+ m_clipboard->setText(content);
+}
+
+void ClipboardListenerQt::updateClipboard(QClipboard::Mode mode)
+{
+ if (mode != QClipboard::Clipboard) {
+ return;
+ }
+
+ QString content = m_clipboard->text();
+
+ if (content == m_currentContent) {
+ return;
+ }
+ m_currentContent = content;
+
+ Q_EMIT clipboardChanged(content);
+}
diff --git a/plugins/clipboard/clipboardlistenerqt.h b/plugins/clipboard/clipboardlistenerqt.h
new file mode 100644
index 00000000..a46f7913
--- /dev/null
+++ b/plugins/clipboard/clipboardlistenerqt.h
@@ -0,0 +1,46 @@
+/**
+ * Copyright 2016 Albert Vaca <albertvaka@gmail.com>
+ *
+ * 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) version 3 or any later version
+ * accepted by the membership of KDE e.V. (or its successor approved
+ * by the membership of KDE e.V.), which shall act as a proxy
+ * defined in Section 14 of version 3 of the license.
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef CLIPBOARDLISTENERQT_H
+#define CLIPBOARDLISTENERQT_H
+
+#include "clipboardlistener.h"
+#include <QTimer>
+
+class ClipboardListenerQt : public ClipboardListener
+{
+ Q_OBJECT
+public:
+ ClipboardListenerQt();
+
+ void setText(const QString & content) override;
+
+private:
+ void updateClipboard(QClipboard::Mode mode);
+
+ QString m_currentContent;
+ QClipboard* m_clipboard;
+#ifdef Q_OS_MAC
+ QTimer m_clipboardMonitorTimer;
+#endif
+
+};
+
+#endif
diff --git a/plugins/clipboard/clipboardlistenerwayland.cpp b/plugins/clipboard/clipboardlistenerwayland.cpp
new file mode 100644
index 00000000..008e9944
--- /dev/null
+++ b/plugins/clipboard/clipboardlistenerwayland.cpp
@@ -0,0 +1,172 @@
+/**
+ * Copyright 2019 Aleix Pol Gonzalez
+ *
+ * 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) version 3 or any later version
+ * accepted by the membership of KDE e.V. (or its successor approved
+ * by the membership of KDE e.V.), which shall act as a proxy
+ * defined in Section 14 of version 3 of the license.
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "clipboardlistenerwayland.h"
+#include <KWayland/Client/registry.h>
+#include <KWayland/Client/datacontroldevice.h>
+#include <KWayland/Client/seat.h>
+#include <KWayland/Client/connection_thread.h>
+#include <QDebug>
+#include <QMimeType>
+#include <QMimeData>
+
+#include <unistd.h>
+#include <signal.h>
+#include <fcntl.h>
+
+using namespace KWayland::Client;
+
+ClipboardListenerWayland::ClipboardListenerWayland()
+{
+ auto registry = new Registry(this);
+
+ m_waylandConnection = ConnectionThread::fromApplication(this);
+ if (!m_waylandConnection) {
+ qWarning() << "Failed getting Wayland connection from QPA";
+ return;
+ }
+ registry->create(m_waylandConnection);
+ registry->setup();
+
+ const auto ifaces = registry->interfaces(KWayland::Client::Registry::Interface::DataControlDeviceManager);
+ connect(registry, &Registry::dataControlDeviceManagerAnnounced, this, [this, registry](quint32 name, quint32 version) {
+ m_manager = registry->createDataControlDeviceManager(name, version, this);
+ m_source = m_manager->createDataSource(this);
+ connect(m_source, &DataControlSource::sendDataRequested, this, &ClipboardListenerWayland::sendData);
+ });
+
+ connect(registry, &Registry::interfacesAnnounced, this, [this] {
+ Q_ASSERT(m_seat);
+ m_dataControlDevice = m_manager->getDataDevice(m_seat, this);
+ connect(m_dataControlDevice, &DataControlDevice::selectionOffered, this, &ClipboardListenerWayland::selectionReceived);
+ });
+}
+
+const QString textMimetype = QStringLiteral("text/plain");
+
+void ClipboardListenerWayland::setText(const QString& content)
+{
+ if (m_currentContent == content)
+ return;
+
+ m_currentContent = content;
+ m_source->offer(textMimetype);
+}
+
+void ClipboardListenerWayland::sendData(const QString& mimeType, qint32 fd)
+{
+ Q_ASSERT(mimeType == textMimetype);
+
+ QByteArray content = m_currentContent.toUtf8();
+ if (!content.isEmpty()) {
+ // Create a sigpipe handler that does nothing, or clients may be forced to terminate
+ // if the pipe is closed in the other end.
+ struct sigaction action, oldAction;
+ action.sa_handler = SIG_IGN;
+ sigemptyset (&action.sa_mask);
+ action.sa_flags = 0;
+
+ sigaction(SIGPIPE, &action, &oldAction);
+ write(fd, content.constData(), content.size());
+ sigaction(SIGPIPE, &oldAction, nullptr);
+ }
+ close(fd);
+}
+
+static inline int qt_safe_pipe(int pipefd[2], int flags = 0)
+{
+ Q_ASSERT((flags & ~O_NONBLOCK) == 0);
+
+#ifdef QT_THREADSAFE_CLOEXEC
+ // use pipe2
+ flags |= O_CLOEXEC;
+ return ::pipe2(pipefd, flags); // pipe2 is documented not to return EINTR
+#else
+ int ret = ::pipe(pipefd);
+ if (ret == -1)
+ return -1;
+
+ ::fcntl(pipefd[0], F_SETFD, FD_CLOEXEC);
+ ::fcntl(pipefd[1], F_SETFD, FD_CLOEXEC);
+
+ // set non-block too?
+ if (flags & O_NONBLOCK) {
+ ::fcntl(pipefd[0], F_SETFL, ::fcntl(pipefd[0], F_GETFL) | O_NONBLOCK);
+ ::fcntl(pipefd[1], F_SETFL, ::fcntl(pipefd[1], F_GETFL) | O_NONBLOCK);
+ }
+
+ return 0;
+#endif
+}
+
+static int readData(int fd, QByteArray &data)
+{
+ char buf[4096];
+ int retryCount = 0;
+ int n;
+ while (true) {
+ n = read(fd, buf, sizeof buf);
+ if (n == -1 && (errno == EAGAIN || errno == EWOULDBLOCK) && ++retryCount < 1000)
+ usleep(1000);
+ else
+ break;
+ }
+ if (retryCount >= 1000)
+ qWarning("ClipboardListenerWayland: timeout reading from pipe");
+ if (n > 0) {
+ data.append(buf, n);
+ n = readData(fd, data);
+ }
+ return n;
+}
+
+QString ClipboardListenerWayland::readString(const QMimeType& mimeType, KWayland::Client::DataControlOffer* offer)
+{
+ int pipefd[2];
+ if (qt_safe_pipe(pipefd, O_NONBLOCK) == -1) {
+ qWarning() << "unable to open offer";
+ return {};
+ }
+
+ offer->receive(mimeType, pipefd[1]);
+ m_waylandConnection->flush();
+
+ close(pipefd[1]);
+
+ QByteArray content;
+ if (readData(pipefd[0], content) != 0) {
+ qWarning("ClipboardListenerWayland: error reading data for mimeType %s", qPrintable(mimeType.name()));
+ content = {};
+ }
+
+ close(pipefd[0]);
+ return QString::fromUtf8(content);
+}
+
+void ClipboardListenerWayland::selectionReceived(KWayland::Client::DataControlOffer* offer)
+{
+ const auto mimetypes = offer->offeredMimeTypes();
+ for (const auto &mimetype : mimetypes) {
+ if (mimetype.inherits(textMimetype)) {
+ m_currentContent = readString(mimetype, offer);
+ break;
+ }
+ }
+}
diff --git a/plugins/clipboard/clipboardlistenerwayland.h b/plugins/clipboard/clipboardlistenerwayland.h
new file mode 100644
index 00000000..f63e919a
--- /dev/null
+++ b/plugins/clipboard/clipboardlistenerwayland.h
@@ -0,0 +1,51 @@
+/**
+ * Copyright 2019 Aleix Pol Gonzalez
+ *
+ * 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) version 3 or any later version
+ * accepted by the membership of KDE e.V. (or its successor approved
+ * by the membership of KDE e.V.), which shall act as a proxy
+ * defined in Section 14 of version 3 of the license.
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef CLIPBOARDLISTENERWAYLAND_H
+#define CLIPBOARDLISTENERWAYLAND_H
+
+#include "clipboardlistener.h"
+#include <KWayland/Client/datacontroldevicemanager.h>
+#include <KWayland/Client/datacontrolsource.h>
+#include <KWayland/Client/datacontroloffer.h>
+#include <KWayland/Client/connection_thread.h>
+
+class ClipboardListenerWayland : public ClipboardListener
+{
+ Q_OBJECT
+public:
+ ClipboardListenerWayland();
+
+ void setText(const QString & content) override;
+
+private:
+ void sendData(const QString &mimeType, qint32 fd);
+ void selectionReceived(KWayland::Client::DataControlOffer* offer);
+ QString readString(const QMimeType& mimeType, KWayland::Client::DataControlOffer* offer);
+
+ QString m_currentContent;
+ KWayland::Client::ConnectionThread* m_waylandConnection = nullptr;
+ KWayland::Client::DataControlDeviceManager* m_manager = nullptr;
+ KWayland::Client::DataControlSource* m_source = nullptr;
+ KWayland::Client::Seat* m_seat = nullptr;
+ KWayland::Client::DataControlDevice* m_dataControlDevice = nullptr;
+};
+
+#endif
apol edited the content of this paste. (Show Details)Sep 5 2019, 2:15 PM
apol changed the title of this paste from untitled to Masterwork From Distant Lands.