diff --git a/CMakeLists.txt b/CMakeLists.txt index 47732b5..6605366 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,6 +27,5 @@ add_subdirectory(session) add_subdirectory(cutefish-services) add_subdirectory(cpufreq) add_subdirectory(sddm-helper) -add_subdirectory(clipboard) install(FILES cutefish DESTINATION /etc/ COMPONENT Runtime) diff --git a/clipboard/CMakeLists.txt b/clipboard/CMakeLists.txt deleted file mode 100644 index 3af14a6..0000000 --- a/clipboard/CMakeLists.txt +++ /dev/null @@ -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}) diff --git a/clipboard/clipboard.cpp b/clipboard/clipboard.cpp deleted file mode 100644 index a0f4c28..0000000 --- a/clipboard/clipboard.cpp +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2021 CutefishOS Team. - * - * Author: Kate Leet - * - * 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 . - */ - -#include "clipboard.h" - -#include -#include -#include -#include - -#include - -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(); -} diff --git a/clipboard/clipboard.h b/clipboard/clipboard.h deleted file mode 100644 index 51676ef..0000000 --- a/clipboard/clipboard.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2021 CutefishOS Team. - * - * Author: Kate Leet - * - * 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 . - */ - -#ifndef CLIPBOARD_H -#define CLIPBOARD_H - -#include -#include - -class Clipboard : public QObject -{ - Q_OBJECT - -public: - explicit Clipboard(QObject *parent = nullptr); - -private slots: - void onDataChanged(); - -private: - QClipboard *m_qtClipboard; -}; - -#endif // CLIPBOARD_H diff --git a/clipboard/main.cpp b/clipboard/main.cpp deleted file mode 100644 index 4759269..0000000 --- a/clipboard/main.cpp +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2021 CutefishOS Team. - * - * Author: Kate Leet - * - * 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 . - */ - -#include -#include "clipboard.h" - -int main(int argc, char *argv[]) -{ - QApplication a(argc, argv); - - Clipboard clipboard; - - return a.exec(); -} diff --git a/cutefish-services/CMakeLists.txt b/cutefish-services/CMakeLists.txt index 7c051aa..2cd4911 100644 --- a/cutefish-services/CMakeLists.txt +++ b/cutefish-services/CMakeLists.txt @@ -3,6 +3,8 @@ set(TARGET cutefish-services) find_package(KF6IdleTime REQUIRED) find_package(KWayland REQUIRED) +find_package(Qt6 REQUIRED COMPONENTS WaylandClient) +find_package(WaylandScanner REQUIRED) file(GLOB_RECURSE SOURCES "*.cpp" @@ -37,6 +39,11 @@ qt6_add_dbus_adaptor(DBUS_SOURCES set_source_files_properties(${DBUS_SOURCES} PROPERTIES SKIP_AUTOGEN ON) 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} Qt6::Core Qt6::Gui @@ -44,6 +51,7 @@ target_link_libraries(${TARGET} Qt6::DBus Qt6::Xml KF6::IdleTime + Qt6::WaylandClient Plasma::KWaylandClient ) diff --git a/cutefish-services/application.cpp b/cutefish-services/application.cpp index 1470c85..6f48ca2 100644 --- a/cutefish-services/application.cpp +++ b/cutefish-services/application.cpp @@ -39,6 +39,7 @@ Application::Application(int &argc, char **argv) , m_language(Language::self()) , m_mouse(new Mouse(this)) , m_touchpad(new TouchpadManager(this)) + , m_clipboard(new Clipboard(this)) , m_cpuManagement(new CPUManagement(this)) , m_powerManager(new PowerManager(m_upowerManager, m_cpuManagement, this)) { diff --git a/cutefish-services/application.h b/cutefish-services/application.h index a8553e6..e979585 100644 --- a/cutefish-services/application.h +++ b/cutefish-services/application.h @@ -22,6 +22,7 @@ #include #include "appearance/thememanager.h" +#include "clipboard/clipboard.h" #include "brightness/brightnessmanager.h" #include "battery/upowermanager.h" #include "locale/language.h" @@ -46,6 +47,7 @@ private: Language *m_language; Mouse *m_mouse; TouchpadManager *m_touchpad; + Clipboard *m_clipboard; CPUManagement *m_cpuManagement; PowerManager *m_powerManager; }; diff --git a/cutefish-services/clipboard/clipboard.cpp b/cutefish-services/clipboard/clipboard.cpp new file mode 100644 index 0000000..ff996e5 --- /dev/null +++ b/cutefish-services/clipboard/clipboard.cpp @@ -0,0 +1,77 @@ +#include "clipboard.h" +#include "waylandclipboard.h" + +#include +#include +#include +#include +#include + +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()) { + const QImage image = imageData.value(); + if (!image.isNull()) + newMimeData->setImageData(image); + } else if (imageData.canConvert()) { + const QPixmap pixmap = imageData.value(); + 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; + } +} diff --git a/cutefish-services/clipboard/clipboard.h b/cutefish-services/clipboard/clipboard.h new file mode 100644 index 0000000..2b4f005 --- /dev/null +++ b/cutefish-services/clipboard/clipboard.h @@ -0,0 +1,24 @@ +#ifndef CLIPBOARD_H +#define CLIPBOARD_H + +#include +#include + +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 diff --git a/cutefish-services/clipboard/waylandclipboard.cpp b/cutefish-services/clipboard/waylandclipboard.cpp new file mode 100644 index 0000000..972a980 --- /dev/null +++ b/cutefish-services/clipboard/waylandclipboard.cpp @@ -0,0 +1,554 @@ +#include "waylandclipboard.h" + +#include "qwayland-wlr-data-control-unstable-v1.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include + +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 formats = QImageWriter::imageFormatsForMimeType(mimeType.toLatin1()); + return formats.isEmpty() ? QByteArrayLiteral("PNG") : formats.constFirst(); +} + +QByteArray imageReadFormatForMimeType(const QString &mimeType) +{ + const QList 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(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(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(this)->receive(offeredMimeType, pipeFds[1]); + close(pipeFds[1]); + + auto *waylandApp = qGuiApp->nativeInterface(); + 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()) { + QList 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 variantUrls; + for (const QUrl &url : urls) + variantUrls.append(url); + result = variantUrls; + } else { + result = QVariant::fromValue(urls); + } + } else if (preferredType == QMetaType::fromType()) { + 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 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 mimeData, + std::function cancelled) + : QtWayland::zwlr_data_control_source_v1(object) + , m_mimeData(std::move(mimeData)) + , m_cancelled(std::move(cancelled)) + { + QSet 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()) + image = imageData.value(); + else if (imageData.canConvert()) + image = imageData.value().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 m_mimeData; + std::function 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 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 mimeData); + +protected: + void zwlr_data_control_device_v1_data_offer(struct ::zwlr_data_control_offer_v1 *object) override + { + m_pendingOffer = std::make_unique(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(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 m_selectionChanged; + std::unique_ptr m_source; + std::unique_ptr m_selection; + std::unique_ptr m_pendingOffer; +}; + +class WlrDataControlManager final + : public QWaylandClientExtensionTemplate + , public QtWayland::zwlr_data_control_manager_v1 +{ +public: + WlrDataControlManager() + : QWaylandClientExtensionTemplate(2) + { + } + + ~WlrDataControlManager() + { + if (isInitialized()) + destroy(); + } + + void start() + { + initialize(); + } +}; + +void WlrDataControlDevice::setMimeData(std::unique_ptr 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( + 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(); + if (!waylandApp || !waylandApp->seat()) + return; + + auto *object = manager.get_data_device(waylandApp->seat()); + if (!object) + return; + + device = std::make_unique(object, &manager, [this] { + emit this->q->dataChanged(); + }); + emit this->q->availabilityChanged(); + }); + } + + WlrClipboard *q; + WlrDataControlManager manager; + std::unique_ptr device; +}; + +WlrClipboard::WlrClipboard(QObject *parent) + : QObject(parent) + , d(new Private(this)) +{ + QMetaObject::invokeMethod(d, [this] { + auto *waylandApp = qGuiApp->nativeInterface(); + 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(mimeData)); +} diff --git a/cutefish-services/clipboard/waylandclipboard.h b/cutefish-services/clipboard/waylandclipboard.h new file mode 100644 index 0000000..34fa82d --- /dev/null +++ b/cutefish-services/clipboard/waylandclipboard.h @@ -0,0 +1,29 @@ +#ifndef WAYLANDCLIPBOARD_H +#define WAYLANDCLIPBOARD_H + +#include + +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 diff --git a/cutefish-services/clipboard/wlr-data-control-unstable-v1.xml b/cutefish-services/clipboard/wlr-data-control-unstable-v1.xml new file mode 100644 index 0000000..5210e6d --- /dev/null +++ b/cutefish-services/clipboard/wlr-data-control-unstable-v1.xml @@ -0,0 +1,82 @@ + + + + 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. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/debian/control b/debian/control index e2a941d..3a9b987 100644 --- a/debian/control +++ b/debian/control @@ -13,6 +13,7 @@ Build-Depends: cmake, libkscreen-dev, kwayland-dev, qt6-base-dev, + qt6-wayland-dev, qt6-declarative-dev, qt6-tools-dev, qt6-tools-dev-tools diff --git a/session/processmanager.cpp b/session/processmanager.cpp index 27e1e7e..3c291a1 100644 --- a/session/processmanager.cpp +++ b/session/processmanager.cpp @@ -158,7 +158,6 @@ void ProcessManager::startDesktopProcess() // Desktop components // The status bar, dock, launcher, desktop and notifications are one process now. list << qMakePair(QString("cutefish-shell"), QStringList()); - list << qMakePair(QString("cutefish-clipboard"), QStringList()); // For CutefishOS. if (QFile("/usr/bin/cutefish-welcome").exists() &&