clipboard: add support for wayland DataDeviceControl
This commit is contained in:
parent
aa7d6d2339
commit
9ee096d18c
7 changed files with 354 additions and 53 deletions
|
@ -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()
|
||||
|
|
|
@ -19,35 +19,27 @@
|
|||
*/
|
||||
|
||||
#include "clipboardlistener.h"
|
||||
#include "clipboardlistenerqt.h"
|
||||
#ifdef WITH_KWAYLAND
|
||||
#include "clipboardlistenerwayland.h"
|
||||
#endif
|
||||
|
||||
ClipboardListener::ClipboardListener()
|
||||
: clipboard(QGuiApplication::clipboard())
|
||||
{}
|
||||
|
||||
ClipboardListener* ClipboardListener::instance()
|
||||
{
|
||||
#ifdef Q_OS_MAC
|
||||
connect(&m_clipboardMonitorTimer, &QTimer::timeout, this, [this](){ updateClipboard(QClipboard::Clipboard); });
|
||||
m_clipboardMonitorTimer.start(1000); // Refresh 1s
|
||||
static ClipboardListener* me = nullptr;
|
||||
if (!me) {
|
||||
#ifdef WITH_KWAYLAND
|
||||
if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) {
|
||||
me = new ClipboardListenerWayland();
|
||||
}
|
||||
else
|
||||
#endif
|
||||
connect(clipboard, &QClipboard::changed, this, &ClipboardListener::updateClipboard);
|
||||
}
|
||||
|
||||
void ClipboardListener::updateClipboard(QClipboard::Mode mode)
|
||||
{
|
||||
if (mode != QClipboard::Clipboard) {
|
||||
return;
|
||||
{
|
||||
me = new ClipboardListenerQt();
|
||||
}
|
||||
|
||||
QString content = clipboard->text();
|
||||
|
||||
if (content == currentContent) {
|
||||
return;
|
||||
}
|
||||
currentContent = content;
|
||||
|
||||
Q_EMIT clipboardChanged(content);
|
||||
}
|
||||
|
||||
void ClipboardListener::setText(const QString& content)
|
||||
{
|
||||
currentContent = content;
|
||||
clipboard->setText(content);
|
||||
return me;
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
#ifndef CLIPBOARDLISTENER_H
|
||||
#define CLIPBOARDLISTENER_H
|
||||
|
||||
#include <QTimer>
|
||||
#include <QObject>
|
||||
#include <QClipboard>
|
||||
#include <QGuiApplication>
|
||||
|
@ -33,28 +32,12 @@ 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;
|
||||
}
|
||||
static ClipboardListener* instance();
|
||||
|
||||
void updateClipboard(QClipboard::Mode mode);
|
||||
|
||||
void setText(const QString& content);
|
||||
virtual void setText(const QString& content) = 0;
|
||||
|
||||
Q_SIGNALS:
|
||||
void clipboardChanged(const QString& content);
|
||||
|
|
54
plugins/clipboard/clipboardlistenerqt.cpp
Normal file
54
plugins/clipboard/clipboardlistenerqt.cpp
Normal file
|
@ -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);
|
||||
}
|
46
plugins/clipboard/clipboardlistenerqt.h
Normal file
46
plugins/clipboard/clipboardlistenerqt.h
Normal file
|
@ -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
|
172
plugins/clipboard/clipboardlistenerwayland.cpp
Normal file
172
plugins/clipboard/clipboardlistenerwayland.cpp
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
51
plugins/clipboard/clipboardlistenerwayland.h
Normal file
51
plugins/clipboard/clipboardlistenerwayland.h
Normal file
|
@ -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
|
Loading…
Reference in a new issue