fix(clipboard): support Wayland data control

main
Reion Wong 3 weeks ago
parent 38206d3c52
commit 39bb9f83dc

@ -27,6 +27,5 @@ add_subdirectory(session)
add_subdirectory(cutefish-services) add_subdirectory(cutefish-services)
add_subdirectory(cpufreq) add_subdirectory(cpufreq)
add_subdirectory(sddm-helper) add_subdirectory(sddm-helper)
add_subdirectory(clipboard)
install(FILES cutefish DESTINATION /etc/ COMPONENT Runtime) install(FILES cutefish DESTINATION /etc/ COMPONENT Runtime)

@ -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();
}

@ -3,6 +3,8 @@ set(TARGET cutefish-services)
find_package(KF6IdleTime REQUIRED) find_package(KF6IdleTime REQUIRED)
find_package(KWayland REQUIRED) find_package(KWayland REQUIRED)
find_package(Qt6 REQUIRED COMPONENTS WaylandClient)
find_package(WaylandScanner REQUIRED)
file(GLOB_RECURSE SOURCES file(GLOB_RECURSE SOURCES
"*.cpp" "*.cpp"
@ -37,6 +39,11 @@ qt6_add_dbus_adaptor(DBUS_SOURCES
set_source_files_properties(${DBUS_SOURCES} PROPERTIES SKIP_AUTOGEN ON) set_source_files_properties(${DBUS_SOURCES} PROPERTIES SKIP_AUTOGEN ON)
add_executable(${TARGET} ${SOURCES} ${DBUS_SOURCES} ${HEADERS} ${FORMS} ${RESOURCES}) add_executable(${TARGET} ${SOURCES} ${DBUS_SOURCES} ${HEADERS} ${FORMS} ${RESOURCES})
qt6_generate_wayland_protocol_client_sources(
${TARGET}
PRIVATE_CODE
FILES ${CMAKE_CURRENT_SOURCE_DIR}/clipboard/wlr-data-control-unstable-v1.xml
)
target_link_libraries(${TARGET} target_link_libraries(${TARGET}
Qt6::Core Qt6::Core
Qt6::Gui Qt6::Gui
@ -44,6 +51,7 @@ target_link_libraries(${TARGET}
Qt6::DBus Qt6::DBus
Qt6::Xml Qt6::Xml
KF6::IdleTime KF6::IdleTime
Qt6::WaylandClient
Plasma::KWaylandClient Plasma::KWaylandClient
) )

@ -39,6 +39,7 @@ Application::Application(int &argc, char **argv)
, m_language(Language::self()) , m_language(Language::self())
, m_mouse(new Mouse(this)) , m_mouse(new Mouse(this))
, m_touchpad(new TouchpadManager(this)) , m_touchpad(new TouchpadManager(this))
, m_clipboard(new Clipboard(this))
, m_cpuManagement(new CPUManagement(this)) , m_cpuManagement(new CPUManagement(this))
, m_powerManager(new PowerManager(m_upowerManager, m_cpuManagement, this)) , m_powerManager(new PowerManager(m_upowerManager, m_cpuManagement, this))
{ {

@ -22,6 +22,7 @@
#include <QApplication> #include <QApplication>
#include "appearance/thememanager.h" #include "appearance/thememanager.h"
#include "clipboard/clipboard.h"
#include "brightness/brightnessmanager.h" #include "brightness/brightnessmanager.h"
#include "battery/upowermanager.h" #include "battery/upowermanager.h"
#include "locale/language.h" #include "locale/language.h"
@ -46,6 +47,7 @@ private:
Language *m_language; Language *m_language;
Mouse *m_mouse; Mouse *m_mouse;
TouchpadManager *m_touchpad; TouchpadManager *m_touchpad;
Clipboard *m_clipboard;
CPUManagement *m_cpuManagement; CPUManagement *m_cpuManagement;
PowerManager *m_powerManager; PowerManager *m_powerManager;
}; };

@ -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>

1
debian/control vendored

@ -13,6 +13,7 @@ Build-Depends: cmake,
libkscreen-dev, libkscreen-dev,
kwayland-dev, kwayland-dev,
qt6-base-dev, qt6-base-dev,
qt6-wayland-dev,
qt6-declarative-dev, qt6-declarative-dev,
qt6-tools-dev, qt6-tools-dev,
qt6-tools-dev-tools qt6-tools-dev-tools

@ -158,7 +158,6 @@ void ProcessManager::startDesktopProcess()
// Desktop components // Desktop components
// The status bar, dock, launcher, desktop and notifications are one process now. // The status bar, dock, launcher, desktop and notifications are one process now.
list << qMakePair(QString("cutefish-shell"), QStringList()); list << qMakePair(QString("cutefish-shell"), QStringList());
list << qMakePair(QString("cutefish-clipboard"), QStringList());
// For CutefishOS. // For CutefishOS.
if (QFile("/usr/bin/cutefish-welcome").exists() && if (QFile("/usr/bin/cutefish-welcome").exists() &&

Loading…
Cancel
Save