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