mirror of https://github.com/cutefishos/core
fix(clipboard): support Wayland data control
parent
38206d3c52
commit
39bb9f83dc
@ -1,26 +0,0 @@
|
|||||||
cmake_minimum_required(VERSION 3.14)
|
|
||||||
|
|
||||||
project(cutefish-clipboard LANGUAGES CXX)
|
|
||||||
|
|
||||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
|
||||||
|
|
||||||
set(CMAKE_AUTOUIC ON)
|
|
||||||
set(CMAKE_AUTOMOC ON)
|
|
||||||
set(CMAKE_AUTORCC ON)
|
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 11)
|
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
||||||
|
|
||||||
find_package(Qt6 COMPONENTS Core Gui Widgets REQUIRED)
|
|
||||||
|
|
||||||
add_executable(cutefish-clipboard
|
|
||||||
main.cpp
|
|
||||||
clipboard.cpp
|
|
||||||
)
|
|
||||||
target_link_libraries(cutefish-clipboard
|
|
||||||
Qt6::Core
|
|
||||||
Qt6::Gui
|
|
||||||
Qt6::Widgets
|
|
||||||
)
|
|
||||||
|
|
||||||
install(TARGETS cutefish-clipboard DESTINATION ${CMAKE_INSTALL_BINDIR})
|
|
||||||
@ -1,75 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2021 CutefishOS Team.
|
|
||||||
*
|
|
||||||
* Author: Kate Leet <kate@cutefishos.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 3 of the License, or
|
|
||||||
* 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 "clipboard.h"
|
|
||||||
|
|
||||||
#include <QApplication>
|
|
||||||
#include <QMimeData>
|
|
||||||
#include <QPixmap>
|
|
||||||
#include <QDebug>
|
|
||||||
|
|
||||||
#include <QBuffer>
|
|
||||||
|
|
||||||
Clipboard::Clipboard(QObject *parent)
|
|
||||||
: QObject(parent)
|
|
||||||
, m_qtClipboard(qApp->clipboard())
|
|
||||||
{
|
|
||||||
connect(m_qtClipboard, &QClipboard::dataChanged, this, &Clipboard::onDataChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Clipboard::onDataChanged()
|
|
||||||
{
|
|
||||||
const QMimeData *mimeData = m_qtClipboard->mimeData();
|
|
||||||
|
|
||||||
if (mimeData->formats().isEmpty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (mimeData->hasFormat("application/x-cutefish-clipboard") &&
|
|
||||||
mimeData->data("application/x-cutefish-clipboard") == "1")
|
|
||||||
return;
|
|
||||||
|
|
||||||
QByteArray timeStamp = mimeData->data("TIMESTAMP");
|
|
||||||
|
|
||||||
QMimeData *newMimeData = new QMimeData;
|
|
||||||
if (mimeData->hasImage()) {
|
|
||||||
QPixmap srcPix = m_qtClipboard->pixmap();
|
|
||||||
|
|
||||||
QByteArray bArray;
|
|
||||||
QBuffer buffer(&bArray);
|
|
||||||
buffer.open(QIODevice::WriteOnly);
|
|
||||||
|
|
||||||
srcPix.save(&buffer);
|
|
||||||
|
|
||||||
newMimeData->setImageData(srcPix);
|
|
||||||
newMimeData->setData("TIMESTAMP", timeStamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const QString &key : mimeData->formats()) {
|
|
||||||
if (key == "image/png" || key == "application/x-qt-image")
|
|
||||||
continue;
|
|
||||||
|
|
||||||
newMimeData->setData(key, mimeData->data(key));
|
|
||||||
}
|
|
||||||
|
|
||||||
// cutefish flag.
|
|
||||||
newMimeData->setData("application/x-cutefish-clipboard", QByteArray("1"));
|
|
||||||
|
|
||||||
m_qtClipboard->setMimeData(newMimeData);
|
|
||||||
newMimeData->deleteLater();
|
|
||||||
}
|
|
||||||
@ -1,40 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2021 CutefishOS Team.
|
|
||||||
*
|
|
||||||
* Author: Kate Leet <kate@cutefishos.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 3 of the License, or
|
|
||||||
* 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 CLIPBOARD_H
|
|
||||||
#define CLIPBOARD_H
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include <QClipboard>
|
|
||||||
|
|
||||||
class Clipboard : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit Clipboard(QObject *parent = nullptr);
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void onDataChanged();
|
|
||||||
|
|
||||||
private:
|
|
||||||
QClipboard *m_qtClipboard;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // CLIPBOARD_H
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2021 CutefishOS Team.
|
|
||||||
*
|
|
||||||
* Author: Kate Leet <kate@cutefishos.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 3 of the License, or
|
|
||||||
* 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 <QApplication>
|
|
||||||
#include "clipboard.h"
|
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
|
||||||
{
|
|
||||||
QApplication a(argc, argv);
|
|
||||||
|
|
||||||
Clipboard clipboard;
|
|
||||||
|
|
||||||
return a.exec();
|
|
||||||
}
|
|
||||||
@ -0,0 +1,77 @@
|
|||||||
|
#include "clipboard.h"
|
||||||
|
#include "waylandclipboard.h"
|
||||||
|
|
||||||
|
#include <QGuiApplication>
|
||||||
|
#include <QImage>
|
||||||
|
#include <QMimeData>
|
||||||
|
#include <QPixmap>
|
||||||
|
#include <QVariant>
|
||||||
|
|
||||||
|
Clipboard::Clipboard(QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
, m_qtClipboard(QGuiApplication::clipboard())
|
||||||
|
{
|
||||||
|
if (QGuiApplication::platformName().startsWith(QStringLiteral("wayland"))) {
|
||||||
|
m_waylandClipboard = new WlrClipboard(this);
|
||||||
|
connect(m_waylandClipboard, &WlrClipboard::dataChanged,
|
||||||
|
this, &Clipboard::onDataChanged);
|
||||||
|
connect(m_waylandClipboard, &WlrClipboard::availabilityChanged,
|
||||||
|
this, &Clipboard::onDataChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_qtClipboard) {
|
||||||
|
connect(m_qtClipboard, &QClipboard::dataChanged,
|
||||||
|
this, &Clipboard::onDataChanged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Clipboard::onDataChanged()
|
||||||
|
{
|
||||||
|
const bool useWaylandClipboard = m_waylandClipboard
|
||||||
|
&& m_waylandClipboard->isAvailable();
|
||||||
|
const QMimeData *mimeData = useWaylandClipboard
|
||||||
|
? m_waylandClipboard->mimeData()
|
||||||
|
: (m_qtClipboard ? m_qtClipboard->mimeData(QClipboard::Clipboard) : nullptr);
|
||||||
|
|
||||||
|
if (!mimeData || mimeData->formats().isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (mimeData->hasFormat(QStringLiteral("application/x-cutefish-clipboard")) &&
|
||||||
|
mimeData->data(QStringLiteral("application/x-cutefish-clipboard")) == QByteArrayLiteral("1"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
const QByteArray timeStamp = mimeData->data(QStringLiteral("TIMESTAMP"));
|
||||||
|
|
||||||
|
QMimeData *newMimeData = new QMimeData;
|
||||||
|
if (mimeData->hasImage()) {
|
||||||
|
const QVariant imageData = mimeData->imageData();
|
||||||
|
if (imageData.canConvert<QImage>()) {
|
||||||
|
const QImage image = imageData.value<QImage>();
|
||||||
|
if (!image.isNull())
|
||||||
|
newMimeData->setImageData(image);
|
||||||
|
} else if (imageData.canConvert<QPixmap>()) {
|
||||||
|
const QPixmap pixmap = imageData.value<QPixmap>();
|
||||||
|
if (!pixmap.isNull())
|
||||||
|
newMimeData->setImageData(pixmap);
|
||||||
|
}
|
||||||
|
newMimeData->setData(QStringLiteral("TIMESTAMP"), timeStamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const QString &format : mimeData->formats()) {
|
||||||
|
if (format == QStringLiteral("image/png")
|
||||||
|
|| format == QStringLiteral("application/x-qt-image"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
newMimeData->setData(format, mimeData->data(format));
|
||||||
|
}
|
||||||
|
|
||||||
|
newMimeData->setData(QStringLiteral("application/x-cutefish-clipboard"), QByteArrayLiteral("1"));
|
||||||
|
|
||||||
|
if (useWaylandClipboard) {
|
||||||
|
m_waylandClipboard->setMimeData(newMimeData);
|
||||||
|
} else if (m_qtClipboard) {
|
||||||
|
m_qtClipboard->setMimeData(newMimeData, QClipboard::Clipboard);
|
||||||
|
} else {
|
||||||
|
delete newMimeData;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
#ifndef CLIPBOARD_H
|
||||||
|
#define CLIPBOARD_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QClipboard>
|
||||||
|
|
||||||
|
class WlrClipboard;
|
||||||
|
|
||||||
|
class Clipboard : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit Clipboard(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onDataChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QClipboard *m_qtClipboard = nullptr;
|
||||||
|
WlrClipboard *m_waylandClipboard = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // CLIPBOARD_H
|
||||||
@ -0,0 +1,554 @@
|
|||||||
|
#include "waylandclipboard.h"
|
||||||
|
|
||||||
|
#include "qwayland-wlr-data-control-unstable-v1.h"
|
||||||
|
|
||||||
|
#include <QBuffer>
|
||||||
|
#include <QElapsedTimer>
|
||||||
|
#include <QGuiApplication>
|
||||||
|
#include <QImage>
|
||||||
|
#include <QImageReader>
|
||||||
|
#include <QImageWriter>
|
||||||
|
#include <QMetaType>
|
||||||
|
#include <QMimeData>
|
||||||
|
#include <QPixmap>
|
||||||
|
#include <QSet>
|
||||||
|
#include <QUrl>
|
||||||
|
#include <QWaylandClientExtensionTemplate>
|
||||||
|
|
||||||
|
#include <qguiapplication_platform.h>
|
||||||
|
|
||||||
|
#include <cerrno>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <wayland-client-core.h>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
const QString s_textMimeType = QStringLiteral("text/plain");
|
||||||
|
const QString s_utf8TextMimeType = QStringLiteral("text/plain;charset=utf-8");
|
||||||
|
const QString s_imageMimeType = QStringLiteral("application/x-qt-image");
|
||||||
|
|
||||||
|
bool isImageMimeType(const QString &mimeType)
|
||||||
|
{
|
||||||
|
return mimeType.startsWith(QStringLiteral("image/"));
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList imageMimeTypes()
|
||||||
|
{
|
||||||
|
QStringList mimeTypes;
|
||||||
|
for (const QByteArray &mimeType : QImageWriter::supportedMimeTypes()) {
|
||||||
|
const QString value = QString::fromLatin1(mimeType);
|
||||||
|
if (isImageMimeType(value) && !mimeTypes.contains(value))
|
||||||
|
mimeTypes.append(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mimeTypes.contains(QStringLiteral("image/png")))
|
||||||
|
mimeTypes.prepend(QStringLiteral("image/png"));
|
||||||
|
else
|
||||||
|
mimeTypes.move(mimeTypes.indexOf(QStringLiteral("image/png")), 0);
|
||||||
|
|
||||||
|
return mimeTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray imageFormatForMimeType(const QString &mimeType)
|
||||||
|
{
|
||||||
|
const QList<QByteArray> formats = QImageWriter::imageFormatsForMimeType(mimeType.toLatin1());
|
||||||
|
return formats.isEmpty() ? QByteArrayLiteral("PNG") : formats.constFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray imageReadFormatForMimeType(const QString &mimeType)
|
||||||
|
{
|
||||||
|
const QList<QByteArray> formats = QImageReader::imageFormatsForMimeType(mimeType.toLatin1());
|
||||||
|
return formats.isEmpty() ? QByteArray() : formats.constFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool readPipe(int fd, QByteArray *data)
|
||||||
|
{
|
||||||
|
const int flags = fcntl(fd, F_GETFL, 0);
|
||||||
|
if (flags >= 0)
|
||||||
|
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
||||||
|
|
||||||
|
pollfd descriptor{fd, POLLIN, 0};
|
||||||
|
QElapsedTimer timeout;
|
||||||
|
timeout.start();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const int remaining = 1000 - static_cast<int>(timeout.elapsed());
|
||||||
|
if (remaining <= 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const int result = poll(&descriptor, 1, remaining);
|
||||||
|
if (result < 0) {
|
||||||
|
if (errno == EINTR)
|
||||||
|
continue;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (result == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
char buffer[4096];
|
||||||
|
const ssize_t length = read(fd, buffer, sizeof(buffer));
|
||||||
|
if (length > 0) {
|
||||||
|
data->append(buffer, length);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (length == 0)
|
||||||
|
return true;
|
||||||
|
if (errno == EINTR)
|
||||||
|
continue;
|
||||||
|
if (errno == EAGAIN || errno == EWOULDBLOCK)
|
||||||
|
break;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (descriptor.revents & (POLLERR | POLLNVAL))
|
||||||
|
return false;
|
||||||
|
if (descriptor.revents & POLLHUP)
|
||||||
|
return true;
|
||||||
|
descriptor.revents = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool writePipe(int fd, const QByteArray &data)
|
||||||
|
{
|
||||||
|
struct sigaction ignoreAction{};
|
||||||
|
struct sigaction oldAction{};
|
||||||
|
ignoreAction.sa_handler = SIG_IGN;
|
||||||
|
sigemptyset(&ignoreAction.sa_mask);
|
||||||
|
const bool restoreSignal = sigaction(SIGPIPE, &ignoreAction, &oldAction) == 0;
|
||||||
|
|
||||||
|
const char *buffer = data.constData();
|
||||||
|
qsizetype remaining = data.size();
|
||||||
|
bool success = true;
|
||||||
|
|
||||||
|
const int flags = fcntl(fd, F_GETFL, 0);
|
||||||
|
if (flags >= 0 && (flags & O_NONBLOCK))
|
||||||
|
fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
|
||||||
|
|
||||||
|
while (remaining > 0) {
|
||||||
|
const ssize_t length = write(fd, buffer, static_cast<size_t>(remaining));
|
||||||
|
if (length > 0) {
|
||||||
|
buffer += length;
|
||||||
|
remaining -= length;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (length < 0 && errno == EINTR)
|
||||||
|
continue;
|
||||||
|
success = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (restoreSignal)
|
||||||
|
sigaction(SIGPIPE, &oldAction, nullptr);
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
class WlrDataControlOffer final : public QMimeData,
|
||||||
|
public QtWayland::zwlr_data_control_offer_v1
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit WlrDataControlOffer(struct ::zwlr_data_control_offer_v1 *object)
|
||||||
|
: QtWayland::zwlr_data_control_offer_v1(object)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
~WlrDataControlOffer() override
|
||||||
|
{
|
||||||
|
if (isInitialized())
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList formats() const override
|
||||||
|
{
|
||||||
|
QStringList result = m_formats;
|
||||||
|
if (hasRemoteImage() && !result.contains(s_imageMimeType))
|
||||||
|
result.append(s_imageMimeType);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasFormat(const QString &mimeType) const override
|
||||||
|
{
|
||||||
|
if (mimeType == s_imageMimeType)
|
||||||
|
return hasRemoteImage();
|
||||||
|
if (mimeType == s_textMimeType && !m_formats.contains(s_textMimeType))
|
||||||
|
return m_formats.contains(s_utf8TextMimeType);
|
||||||
|
return m_formats.contains(mimeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void zwlr_data_control_offer_v1_offer(const QString &mimeType) override
|
||||||
|
{
|
||||||
|
if (!m_formats.contains(mimeType))
|
||||||
|
m_formats.append(mimeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant retrieveData(const QString &mimeType, QMetaType preferredType) const override
|
||||||
|
{
|
||||||
|
if (mimeType == s_imageMimeType && m_data.contains(mimeType))
|
||||||
|
return m_data.value(mimeType);
|
||||||
|
|
||||||
|
QString offeredMimeType = mimeType;
|
||||||
|
if (mimeType == s_textMimeType && !m_formats.contains(s_textMimeType))
|
||||||
|
offeredMimeType = s_utf8TextMimeType;
|
||||||
|
else if (mimeType == s_imageMimeType)
|
||||||
|
offeredMimeType = remoteImageMimeType();
|
||||||
|
|
||||||
|
if (offeredMimeType.isEmpty())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
int pipeFds[2];
|
||||||
|
if (pipe(pipeFds) != 0)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
const_cast<WlrDataControlOffer *>(this)->receive(offeredMimeType, pipeFds[1]);
|
||||||
|
close(pipeFds[1]);
|
||||||
|
|
||||||
|
auto *waylandApp = qGuiApp->nativeInterface<QNativeInterface::QWaylandApplication>();
|
||||||
|
if (!waylandApp || !waylandApp->display()) {
|
||||||
|
close(pipeFds[0]);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wl_display_flush(waylandApp->display()) < 0 && errno != EAGAIN) {
|
||||||
|
close(pipeFds[0]);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray data;
|
||||||
|
const bool complete = readPipe(pipeFds[0], &data);
|
||||||
|
close(pipeFds[0]);
|
||||||
|
if (!complete)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
QVariant result;
|
||||||
|
if (mimeType == s_imageMimeType) {
|
||||||
|
const QByteArray format = imageReadFormatForMimeType(offeredMimeType);
|
||||||
|
result = format.isEmpty()
|
||||||
|
? QVariant(QImage::fromData(data))
|
||||||
|
: QVariant(QImage::fromData(data, format.constData()));
|
||||||
|
} else if (mimeType == QStringLiteral("text/uri-list")
|
||||||
|
&& data.size() > 1
|
||||||
|
&& preferredType != QMetaType::fromType<QByteArray>()) {
|
||||||
|
QList<QUrl> urls;
|
||||||
|
for (const QByteArray &encodedUrl : data.split('\n')) {
|
||||||
|
const QUrl url = QUrl::fromEncoded(encodedUrl.trimmed());
|
||||||
|
if (url.isValid())
|
||||||
|
urls.append(url);
|
||||||
|
}
|
||||||
|
if (preferredType == QMetaType::fromType<QVariantList>()) {
|
||||||
|
QVariantList variantUrls;
|
||||||
|
for (const QUrl &url : urls)
|
||||||
|
variantUrls.append(url);
|
||||||
|
result = variantUrls;
|
||||||
|
} else {
|
||||||
|
result = QVariant::fromValue(urls);
|
||||||
|
}
|
||||||
|
} else if (preferredType == QMetaType::fromType<QString>()) {
|
||||||
|
result = QString::fromUtf8(data);
|
||||||
|
} else {
|
||||||
|
result = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mimeType == s_imageMimeType)
|
||||||
|
m_data.insert(mimeType, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool hasRemoteImage() const
|
||||||
|
{
|
||||||
|
return m_formats.contains(s_imageMimeType) || !remoteImageMimeType().isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString remoteImageMimeType() const
|
||||||
|
{
|
||||||
|
if (m_formats.contains(s_imageMimeType))
|
||||||
|
return s_imageMimeType;
|
||||||
|
for (const QString &mimeType : m_formats) {
|
||||||
|
if (isImageMimeType(mimeType)
|
||||||
|
&& !QImageReader::imageFormatsForMimeType(mimeType.toLatin1()).isEmpty())
|
||||||
|
return mimeType;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList m_formats;
|
||||||
|
mutable QHash<QString, QVariant> m_data;
|
||||||
|
};
|
||||||
|
|
||||||
|
class WlrDataControlSource final : public QObject,
|
||||||
|
public QtWayland::zwlr_data_control_source_v1
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
WlrDataControlSource(struct ::zwlr_data_control_source_v1 *object,
|
||||||
|
std::unique_ptr<QMimeData> mimeData,
|
||||||
|
std::function<void()> cancelled)
|
||||||
|
: QtWayland::zwlr_data_control_source_v1(object)
|
||||||
|
, m_mimeData(std::move(mimeData))
|
||||||
|
, m_cancelled(std::move(cancelled))
|
||||||
|
{
|
||||||
|
QSet<QString> offeredFormats;
|
||||||
|
for (const QString &mimeType : m_mimeData->formats()) {
|
||||||
|
offer(mimeType);
|
||||||
|
offeredFormats.insert(mimeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_mimeData->hasText() && !offeredFormats.contains(s_utf8TextMimeType))
|
||||||
|
offer(s_utf8TextMimeType);
|
||||||
|
|
||||||
|
if (m_mimeData->hasImage()) {
|
||||||
|
for (const QString &mimeType : imageMimeTypes()) {
|
||||||
|
if (!offeredFormats.contains(mimeType))
|
||||||
|
offer(mimeType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~WlrDataControlSource() override
|
||||||
|
{
|
||||||
|
if (isInitialized())
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
const QMimeData *mimeData() const
|
||||||
|
{
|
||||||
|
return m_mimeData.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void zwlr_data_control_source_v1_send(const QString &mimeType, int32_t fd) override
|
||||||
|
{
|
||||||
|
QByteArray data;
|
||||||
|
if (m_mimeData->hasImage() && (mimeType == s_imageMimeType || isImageMimeType(mimeType))) {
|
||||||
|
const QVariant imageData = m_mimeData->imageData();
|
||||||
|
QImage image;
|
||||||
|
if (imageData.canConvert<QImage>())
|
||||||
|
image = imageData.value<QImage>();
|
||||||
|
else if (imageData.canConvert<QPixmap>())
|
||||||
|
image = imageData.value<QPixmap>().toImage();
|
||||||
|
|
||||||
|
if (!image.isNull()) {
|
||||||
|
QBuffer buffer(&data);
|
||||||
|
buffer.open(QIODevice::WriteOnly);
|
||||||
|
image.save(&buffer, imageFormatForMimeType(mimeType).constData());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
QString requestedMimeType = mimeType;
|
||||||
|
if (mimeType == s_utf8TextMimeType && !m_mimeData->hasFormat(mimeType))
|
||||||
|
requestedMimeType = s_textMimeType;
|
||||||
|
data = m_mimeData->data(requestedMimeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
writePipe(fd, data);
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
void zwlr_data_control_source_v1_cancelled() override
|
||||||
|
{
|
||||||
|
if (m_cancelled)
|
||||||
|
m_cancelled();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<QMimeData> m_mimeData;
|
||||||
|
std::function<void()> m_cancelled;
|
||||||
|
};
|
||||||
|
|
||||||
|
class WlrDataControlManager;
|
||||||
|
|
||||||
|
class WlrDataControlDevice final : public QObject,
|
||||||
|
public QtWayland::zwlr_data_control_device_v1
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
WlrDataControlDevice(struct ::zwlr_data_control_device_v1 *object,
|
||||||
|
WlrDataControlManager *manager,
|
||||||
|
std::function<void()> selectionChanged)
|
||||||
|
: QtWayland::zwlr_data_control_device_v1(object)
|
||||||
|
, m_manager(manager)
|
||||||
|
, m_selectionChanged(std::move(selectionChanged))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
~WlrDataControlDevice() override
|
||||||
|
{
|
||||||
|
m_source.reset();
|
||||||
|
m_selection.reset();
|
||||||
|
m_pendingOffer.reset();
|
||||||
|
if (isInitialized())
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
const QMimeData *mimeData() const
|
||||||
|
{
|
||||||
|
return m_source ? m_source->mimeData() : m_selection.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setMimeData(std::unique_ptr<QMimeData> mimeData);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void zwlr_data_control_device_v1_data_offer(struct ::zwlr_data_control_offer_v1 *object) override
|
||||||
|
{
|
||||||
|
m_pendingOffer = std::make_unique<WlrDataControlOffer>(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
void zwlr_data_control_device_v1_selection(struct ::zwlr_data_control_offer_v1 *object) override
|
||||||
|
{
|
||||||
|
if (!object) {
|
||||||
|
m_selection.reset();
|
||||||
|
} else {
|
||||||
|
auto *baseOffer = QtWayland::zwlr_data_control_offer_v1::fromObject(object);
|
||||||
|
auto *offer = dynamic_cast<WlrDataControlOffer *>(baseOffer);
|
||||||
|
if (offer != m_pendingOffer.get())
|
||||||
|
return;
|
||||||
|
m_selection = std::move(m_pendingOffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_selectionChanged)
|
||||||
|
m_selectionChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void zwlr_data_control_device_v1_primary_selection(struct ::zwlr_data_control_offer_v1 *) override
|
||||||
|
{
|
||||||
|
m_pendingOffer.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void zwlr_data_control_device_v1_finished() override
|
||||||
|
{
|
||||||
|
if (isInitialized())
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
WlrDataControlManager *m_manager;
|
||||||
|
std::function<void()> m_selectionChanged;
|
||||||
|
std::unique_ptr<WlrDataControlSource> m_source;
|
||||||
|
std::unique_ptr<WlrDataControlOffer> m_selection;
|
||||||
|
std::unique_ptr<WlrDataControlOffer> m_pendingOffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
class WlrDataControlManager final
|
||||||
|
: public QWaylandClientExtensionTemplate<WlrDataControlManager>
|
||||||
|
, public QtWayland::zwlr_data_control_manager_v1
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
WlrDataControlManager()
|
||||||
|
: QWaylandClientExtensionTemplate<WlrDataControlManager>(2)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
~WlrDataControlManager()
|
||||||
|
{
|
||||||
|
if (isInitialized())
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
void start()
|
||||||
|
{
|
||||||
|
initialize();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void WlrDataControlDevice::setMimeData(std::unique_ptr<QMimeData> mimeData)
|
||||||
|
{
|
||||||
|
if (!m_manager || !m_manager->isActive())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!mimeData)
|
||||||
|
return;
|
||||||
|
|
||||||
|
struct ::zwlr_data_control_source_v1 *object = m_manager->create_data_source();
|
||||||
|
if (!object)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto source = std::make_unique<WlrDataControlSource>(
|
||||||
|
object, std::move(mimeData), [this] {
|
||||||
|
QMetaObject::invokeMethod(this, [this] {
|
||||||
|
m_source.reset();
|
||||||
|
if (m_selectionChanged)
|
||||||
|
m_selectionChanged();
|
||||||
|
}, Qt::QueuedConnection);
|
||||||
|
});
|
||||||
|
|
||||||
|
set_selection(source->object());
|
||||||
|
m_source = std::move(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
class WlrClipboard::Private : public QObject
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit Private(WlrClipboard *q)
|
||||||
|
: QObject(q)
|
||||||
|
, q(q)
|
||||||
|
{
|
||||||
|
QObject::connect(&manager, &QWaylandClientExtension::activeChanged,
|
||||||
|
this, [this] {
|
||||||
|
if (!manager.isActive()) {
|
||||||
|
device.reset();
|
||||||
|
emit this->q->availabilityChanged();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto *waylandApp = qGuiApp->nativeInterface<QNativeInterface::QWaylandApplication>();
|
||||||
|
if (!waylandApp || !waylandApp->seat())
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto *object = manager.get_data_device(waylandApp->seat());
|
||||||
|
if (!object)
|
||||||
|
return;
|
||||||
|
|
||||||
|
device = std::make_unique<WlrDataControlDevice>(object, &manager, [this] {
|
||||||
|
emit this->q->dataChanged();
|
||||||
|
});
|
||||||
|
emit this->q->availabilityChanged();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
WlrClipboard *q;
|
||||||
|
WlrDataControlManager manager;
|
||||||
|
std::unique_ptr<WlrDataControlDevice> device;
|
||||||
|
};
|
||||||
|
|
||||||
|
WlrClipboard::WlrClipboard(QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
, d(new Private(this))
|
||||||
|
{
|
||||||
|
QMetaObject::invokeMethod(d, [this] {
|
||||||
|
auto *waylandApp = qGuiApp->nativeInterface<QNativeInterface::QWaylandApplication>();
|
||||||
|
if (!waylandApp || !waylandApp->display() || !waylandApp->seat())
|
||||||
|
return;
|
||||||
|
d->manager.start();
|
||||||
|
}, Qt::QueuedConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
WlrClipboard::~WlrClipboard()
|
||||||
|
{
|
||||||
|
delete d;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WlrClipboard::isAvailable() const
|
||||||
|
{
|
||||||
|
return d->device != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QMimeData *WlrClipboard::mimeData() const
|
||||||
|
{
|
||||||
|
return d->device ? d->device->mimeData() : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WlrClipboard::setMimeData(QMimeData *mimeData)
|
||||||
|
{
|
||||||
|
if (!d->device) {
|
||||||
|
delete mimeData;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
d->device->setMimeData(std::unique_ptr<QMimeData>(mimeData));
|
||||||
|
}
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
#ifndef WAYLANDCLIPBOARD_H
|
||||||
|
#define WAYLANDCLIPBOARD_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
class QMimeData;
|
||||||
|
|
||||||
|
class WlrClipboard : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit WlrClipboard(QObject *parent = nullptr);
|
||||||
|
~WlrClipboard() override;
|
||||||
|
|
||||||
|
bool isAvailable() const;
|
||||||
|
const QMimeData *mimeData() const;
|
||||||
|
void setMimeData(QMimeData *mimeData);
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void availabilityChanged();
|
||||||
|
void dataChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
class Private;
|
||||||
|
Private *d;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // WAYLANDCLIPBOARD_H
|
||||||
@ -0,0 +1,82 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<protocol name="wlr_data_control_unstable_v1">
|
||||||
|
<copyright>
|
||||||
|
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, DIRECT, 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.
|
||||||
|
</copyright>
|
||||||
|
|
||||||
|
<interface name="zwlr_data_control_manager_v1" version="2">
|
||||||
|
<request name="create_data_source">
|
||||||
|
<arg name="id" type="new_id" interface="zwlr_data_control_source_v1"/>
|
||||||
|
</request>
|
||||||
|
<request name="get_data_device">
|
||||||
|
<arg name="id" type="new_id" interface="zwlr_data_control_device_v1"/>
|
||||||
|
<arg name="seat" type="object" interface="wl_seat"/>
|
||||||
|
</request>
|
||||||
|
<request name="destroy" type="destructor"/>
|
||||||
|
</interface>
|
||||||
|
|
||||||
|
<interface name="zwlr_data_control_device_v1" version="2">
|
||||||
|
<request name="set_selection">
|
||||||
|
<arg name="source" type="object" interface="zwlr_data_control_source_v1" allow-null="true"/>
|
||||||
|
</request>
|
||||||
|
<request name="destroy" type="destructor"/>
|
||||||
|
<event name="data_offer">
|
||||||
|
<arg name="id" type="new_id" interface="zwlr_data_control_offer_v1"/>
|
||||||
|
</event>
|
||||||
|
<event name="selection">
|
||||||
|
<arg name="id" type="object" interface="zwlr_data_control_offer_v1" allow-null="true"/>
|
||||||
|
</event>
|
||||||
|
<event name="finished"/>
|
||||||
|
<event name="primary_selection" since="2">
|
||||||
|
<arg name="id" type="object" interface="zwlr_data_control_offer_v1" allow-null="true"/>
|
||||||
|
</event>
|
||||||
|
<request name="set_primary_selection" since="2">
|
||||||
|
<arg name="source" type="object" interface="zwlr_data_control_source_v1" allow-null="true"/>
|
||||||
|
</request>
|
||||||
|
</interface>
|
||||||
|
|
||||||
|
<interface name="zwlr_data_control_source_v1" version="1">
|
||||||
|
<request name="offer">
|
||||||
|
<arg name="mime_type" type="string"/>
|
||||||
|
</request>
|
||||||
|
<request name="destroy" type="destructor"/>
|
||||||
|
<event name="send">
|
||||||
|
<arg name="mime_type" type="string"/>
|
||||||
|
<arg name="fd" type="fd"/>
|
||||||
|
</event>
|
||||||
|
<event name="cancelled"/>
|
||||||
|
</interface>
|
||||||
|
|
||||||
|
<interface name="zwlr_data_control_offer_v1" version="1">
|
||||||
|
<request name="receive">
|
||||||
|
<arg name="mime_type" type="string"/>
|
||||||
|
<arg name="fd" type="fd"/>
|
||||||
|
</request>
|
||||||
|
<request name="destroy" type="destructor"/>
|
||||||
|
<event name="offer">
|
||||||
|
<arg name="mime_type" type="string"/>
|
||||||
|
</event>
|
||||||
|
</interface>
|
||||||
|
</protocol>
|
||||||
Loading…
Reference in New Issue