8#include "waylandclipboard_p.h"
12#include <QGuiApplication>
13#include <QImageReader>
14#include <QImageWriter>
17#include <QWaylandClientExtension>
19#include <QtWaylandClientVersion>
27#include "qwayland-wayland.h"
28#include "qwayland-wlr-data-control-unstable-v1.h"
30static inline QString applicationQtXImageLiteral()
32 return QStringLiteral(
"application/x-qt-image");
38 return QStringLiteral(
"text/plain;charset=utf-8");
45 for (
const auto &format : imageFormats)
49 if (pngIndex != -1 && pngIndex != 0)
50 formats.
move(pngIndex, 0);
65class DataControlDeviceManager :
public QWaylandClientExtensionTemplate<DataControlDeviceManager>,
public QtWayland::zwlr_data_control_manager_v1
69 DataControlDeviceManager()
70 : QWaylandClientExtensionTemplate<DataControlDeviceManager>(2)
79 ~DataControlDeviceManager()
81 if (isInitialized()) {
87class DataControlOffer :
public QMimeData,
public QtWayland::zwlr_data_control_offer_v1
91 DataControlOffer(struct ::zwlr_data_control_offer_v1 *
id)
92 : QtWayland::zwlr_data_control_offer_v1(id)
101 QStringList formats()
const override
103 return m_receivedFormats;
106 bool containsImageData()
const
108 if (m_receivedFormats.contains(applicationQtXImageLiteral())) {
111 const auto formats = imageReadMimeFormats();
112 for (
const auto &receivedFormat : m_receivedFormats) {
113 if (formats.contains(receivedFormat)) {
120 bool hasFormat(
const QString &mimeType)
const override
122 if (mimeType == QStringLiteral(
"text/plain") && m_receivedFormats.contains(utf8Text())) {
125 if (m_receivedFormats.contains(mimeType)) {
130 if (containsImageData()) {
132 const QStringList imageFormats = imageWriteMimeFormats();
133 for (
const QString &imageFormat : imageFormats) {
134 if (imageFormat == mimeType) {
138 if (mimeType == applicationQtXImageLiteral()) {
147 void zwlr_data_control_offer_v1_offer(
const QString &mime_type)
override
149 if (!m_receivedFormats.contains(mime_type)) {
150 m_receivedFormats << mime_type;
154 QVariant retrieveData(
const QString &mimeType, QMetaType type)
const override;
160 static bool readData(
int fd, QByteArray &
data);
161 QStringList m_receivedFormats;
162 mutable QHash<QString, QVariant> m_data;
169 auto it = m_data.constFind(mimeType);
170 if (it != m_data.constEnd())
174 if (!m_receivedFormats.contains(mimeType)) {
175 if (mimeType == QStringLiteral(
"text/plain") && m_receivedFormats.contains(utf8Text())) {
177 }
else if (mimeType == applicationQtXImageLiteral()) {
178 const auto writeFormats = imageWriteMimeFormats();
179 for (
const auto &receivedFormat : m_receivedFormats) {
180 if (writeFormats.contains(receivedFormat)) {
181 mime = receivedFormat;
187 mime = QStringLiteral(
"image/png");
199 if (pipe(pipeFds) != 0) {
203 auto t =
const_cast<DataControlOffer *
>(
this);
204 t->receive(mime, pipeFds[1]);
215 auto waylandApp = qGuiApp->nativeInterface<QNativeInterface::QWaylandApplication>();
216 auto display = waylandApp->display();
218 wl_display_flush(display);
223 if (readData(pipeFds[0],
data)) {
226 if (mimeType == applicationQtXImageLiteral()) {
229 m_data.insert(mimeType, img);
232 }
else if (
data.size() > 1 && mimeType == u
"text/uri-list") {
236 for (
const QByteArray &s :
urls) {
237#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
240 if (QUrl url(
QUrl::fromEncoded(QByteArrayView(s).trimmed().toByteArray())); url.isValid()) {
245 m_data.insert(mimeType, list);
248 m_data.insert(mimeType,
data);
257bool DataControlOffer::readData(
int fd, QByteArray &
data)
261 pfds[0].events = POLLIN;
264 const int ready = poll(pfds, 1, 1000);
266 if (errno != EINTR) {
267 qWarning(
"DataControlOffer: poll() failed: %s", strerror(errno));
270 }
else if (ready == 0) {
271 qWarning(
"DataControlOffer: timeout reading from pipe");
275 int n =
read(fd, buf,
sizeof buf);
278 qWarning(
"DataControlOffer: read() failed: %s", strerror(errno));
289class DataControlSource :
public QObject,
public QtWayland::zwlr_data_control_source_v1
293 DataControlSource(struct ::zwlr_data_control_source_v1 *
id, QMimeData *mimeData);
294 DataControlSource() =
default;
300 QMimeData *mimeData()
302 return m_mimeData.get();
304 std::unique_ptr<QMimeData> releaseMimeData()
306 return std::move(m_mimeData);
313 void zwlr_data_control_source_v1_send(
const QString &mime_type, int32_t fd)
override;
314 void zwlr_data_control_source_v1_cancelled()
override;
317 std::unique_ptr<QMimeData> m_mimeData;
320DataControlSource::DataControlSource(struct ::zwlr_data_control_source_v1 *
id,
QMimeData *mimeData)
321 : QtWayland::zwlr_data_control_source_v1(id)
322 , m_mimeData(mimeData)
324 const auto formats = mimeData->
formats();
325 for (
const QString &format : formats) {
330 offer(QStringLiteral(
"text/plain;charset=utf-8"));
334 const QStringList imageFormats = imageWriteMimeFormats();
335 for (const QString &imageFormat : imageFormats) {
336 if (!formats.contains(imageFormat)) {
343void DataControlSource::zwlr_data_control_source_v1_send(
const QString &mime_type, int32_t fd)
345 QString send_mime_type = mime_type;
346 if (send_mime_type == QStringLiteral(
"text/plain;charset=utf-8")) {
348 send_mime_type = QStringLiteral(
"text/plain");
352 if (m_mimeData->hasImage()) {
354 if (mime_type == applicationQtXImageLiteral()) {
355 QImage image = qvariant_cast<QImage>(m_mimeData->imageData());
359 image.
save(&buf,
"PNG");
361 }
else if (mime_type.
startsWith(QLatin1String(
"image/"))) {
362 QImage image = qvariant_cast<QImage>(m_mimeData->imageData());
365 image.
save(&buf, mime_type.
mid(mime_type.
indexOf(QLatin1Char(
'/')) + 1).toLatin1().toUpper().data());
369 ba = m_mimeData->
data(send_mime_type);
374 struct sigaction action, oldAction;
375 action.sa_handler = SIG_IGN;
376 sigemptyset(&action.sa_mask);
378 sigaction(SIGPIPE, &action, &oldAction);
380 sigaction(SIGPIPE, &oldAction,
nullptr);
384void DataControlSource::zwlr_data_control_source_v1_cancelled()
389class DataControlDevice :
public QObject,
public QtWayland::zwlr_data_control_device_v1
393 DataControlDevice(struct ::zwlr_data_control_device_v1 *
id)
394 : QtWayland::zwlr_data_control_device_v1(id)
403 void setSelection(std::unique_ptr<DataControlSource> selection);
404 QMimeData *receivedSelection()
406 return m_receivedSelection.get();
408 QMimeData *selection()
410 return m_selection ? m_selection->mimeData() :
nullptr;
413 void setPrimarySelection(std::unique_ptr<DataControlSource> selection);
414 QMimeData *receivedPrimarySelection()
416 return m_receivedPrimarySelection.get();
418 QMimeData *primarySelection()
420 return m_primarySelection ? m_primarySelection->mimeData() :
nullptr;
424 void receivedSelectionChanged();
425 void selectionChanged();
427 void receivedPrimarySelectionChanged();
428 void primarySelectionChanged();
431 void zwlr_data_control_device_v1_data_offer(struct ::zwlr_data_control_offer_v1 *
id)
override
435 new DataControlOffer(
id);
438 void zwlr_data_control_device_v1_selection(struct ::zwlr_data_control_offer_v1 *
id)
override
441 m_receivedSelection.reset();
443 auto derivated = QtWayland::zwlr_data_control_offer_v1::fromObject(
id);
444 auto offer =
dynamic_cast<DataControlOffer *
>(derivated);
445 m_receivedSelection.reset(offer);
447 Q_EMIT receivedSelectionChanged();
450 void zwlr_data_control_device_v1_primary_selection(struct ::zwlr_data_control_offer_v1 *
id)
override
453 m_receivedPrimarySelection.reset();
455 auto derivated = QtWayland::zwlr_data_control_offer_v1::fromObject(
id);
456 auto offer =
dynamic_cast<DataControlOffer *
>(derivated);
457 m_receivedPrimarySelection.reset(offer);
459 Q_EMIT receivedPrimarySelectionChanged();
463 std::unique_ptr<DataControlSource> m_selection;
464 std::unique_ptr<DataControlOffer> m_receivedSelection;
466 std::unique_ptr<DataControlSource> m_primarySelection;
467 std::unique_ptr<DataControlOffer> m_receivedPrimarySelection;
468 friend WaylandClipboard;
471void DataControlDevice::setSelection(std::unique_ptr<DataControlSource> selection)
473 m_selection = std::move(selection);
474 connect(m_selection.get(), &DataControlSource::cancelled,
this, [
this]() {
477 set_selection(m_selection->object());
478 Q_EMIT selectionChanged();
481void DataControlDevice::setPrimarySelection(std::unique_ptr<DataControlSource> selection)
483 m_primarySelection = std::move(selection);
484 connect(m_primarySelection.get(), &DataControlSource::cancelled,
this, [
this]() {
485 m_primarySelection.reset();
488 if (zwlr_data_control_device_v1_get_version(
object()) >= ZWLR_DATA_CONTROL_DEVICE_V1_SET_PRIMARY_SELECTION_SINCE_VERSION) {
489 set_primary_selection(m_primarySelection->object());
490 Q_EMIT primarySelectionChanged();
495class KeyboardFocusWatcher :
public QWaylandClientExtensionTemplate<KeyboardFocusWatcher>,
public QtWayland::wl_seat
499 KeyboardFocusWatcher()
500 : QWaylandClientExtensionTemplate(5)
503 auto waylandApp = qGuiApp->nativeInterface<QNativeInterface::QWaylandApplication>();
504 auto display = waylandApp->
display();
506 wl_display_roundtrip(display);
508 ~KeyboardFocusWatcher()
override
514 void seat_capabilities(uint32_t capabilities)
override
516 const bool hasKeyboard =
capabilities & capability_keyboard;
517 if (hasKeyboard && !m_keyboard) {
518 m_keyboard = std::make_unique<Keyboard>(get_keyboard(), *
this);
519 }
else if (!hasKeyboard && m_keyboard) {
523 bool hasFocus()
const
528 void keyboardEntered();
532 bool m_focus =
false;
533 std::unique_ptr<Keyboard> m_keyboard;
536class Keyboard :
public QtWayland::wl_keyboard
539 Keyboard(::wl_keyboard *keyboard, KeyboardFocusWatcher &seat)
540 : wl_keyboard(keyboard)
550 void keyboard_enter([[maybe_unused]] uint32_t serial, [[maybe_unused]] wl_surface *surface, [[maybe_unused]] wl_array *keys)
override
552 m_seat.m_focus =
true;
553 Q_EMIT m_seat.keyboardEntered();
555 void keyboard_leave([[maybe_unused]] uint32_t serial, [[maybe_unused]] wl_surface *surface)
override
557 m_seat.m_focus =
false;
559 KeyboardFocusWatcher &m_seat;
562WaylandClipboard::WaylandClipboard(
QObject *parent)
564 , m_keyboardFocusWatcher(new KeyboardFocusWatcher)
565 , m_manager(new DataControlDeviceManager)
567 connect(m_manager.get(), &DataControlDeviceManager::activeChanged,
this, [
this]() {
568 if (m_manager->isActive()) {
569 auto waylandApp = qGuiApp->nativeInterface<QNativeInterface::QWaylandApplication>();
573 auto seat = waylandApp->seat();
578 m_device.reset(new DataControlDevice(m_manager->get_data_device(seat)));
580 connect(m_device.get(), &DataControlDevice::receivedSelectionChanged, this, [this]() {
582 if (!m_device->selection()) {
583 Q_EMIT changed(QClipboard::Clipboard);
586 connect(m_device.get(), &DataControlDevice::selectionChanged, this, [this]() {
587 Q_EMIT changed(QClipboard::Clipboard);
590 connect(m_device.get(), &DataControlDevice::receivedPrimarySelectionChanged, this, [this]() {
592 if (!m_device->primarySelection()) {
593 Q_EMIT changed(QClipboard::Selection);
596 connect(m_device.get(), &DataControlDevice::primarySelectionChanged, this, [this]() {
597 Q_EMIT changed(QClipboard::Selection);
605 m_manager->instantiate();
608WaylandClipboard::~WaylandClipboard() =
default;
610bool WaylandClipboard::isValid()
612 return m_manager && m_manager->isInitialized();
623 auto display = waylandApp->
display();
624 wl_display_roundtrip(display);
627 if (m_keyboardFocusWatcher->hasFocus()) {
632 wl_display_roundtrip(display);
636 connect(m_keyboardFocusWatcher.get(), &KeyboardFocusWatcher::keyboardEntered,
this, &WaylandClipboard::gainedFocus,
Qt::UniqueConnection);
637 auto source = std::make_unique<DataControlSource>(m_manager->create_data_source(), mime);
639 m_device->setSelection(std::move(source));
641 m_device->setPrimarySelection(std::move(source));
645void WaylandClipboard::gainedFocus()
647 disconnect(m_keyboardFocusWatcher.get(), &KeyboardFocusWatcher::keyboardEntered,
this,
nullptr);
649 if (
auto &selection = m_device->m_selection) {
650 std::unique_ptr<QMimeData> data = selection->releaseMimeData();
654 if (
auto &primarySelection = m_device->m_primarySelection) {
655 std::unique_ptr<QMimeData> data = primarySelection->releaseMimeData();
656 primarySelection.reset();
667 m_device->set_selection(
nullptr);
668 m_device->m_selection.reset();
670 if (zwlr_data_control_device_v1_get_version(m_device->object()) >= ZWLR_DATA_CONTROL_DEVICE_V1_SET_PRIMARY_SELECTION_SINCE_VERSION) {
671 m_device->set_primary_selection(
nullptr);
672 m_device->m_primarySelection.reset();
685 if (m_device->selection()) {
686 return m_device->selection();
692 return m_device->receivedSelection();
694 if (m_device->primarySelection()) {
695 return m_device->primarySelection();
701 return m_device->receivedPrimarySelection();
706#include "waylandclipboard.moc"
This class mimics QClipboard but unlike QClipboard it will continue to get updates even when our wind...
KCALUTILS_EXPORT QString mimeType()
KCRASH_EXPORT void initialize()
Capabilities capabilities()
QVariant read(const QByteArray &data, int versionOverride=0)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
const char * constData() const const
qsizetype size() const const
const QMimeData * mimeData(Mode mode) const const
void setMimeData(QMimeData *src, Mode mode)
bool open(FILE *fh, OpenMode mode, FileHandleFlags handleFlags)
QImage fromData(QByteArrayView data, const char *format)
bool isNull() const const
bool save(QIODevice *device, const char *format, int quality) const const
void append(QList< T > &&value)
reference emplace_back(Args &&... args)
void move(qsizetype from, qsizetype to)
void reserve(qsizetype size)
qsizetype size() const const
QByteArray data(const QString &mimeType) const const
bool hasImage() const const
bool hasText() const const
QList< QUrl > urls() const const
virtual wl_display * display() const const=0
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
QString mid(qsizetype position, qsizetype n) const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
qsizetype indexOf(const QRegularExpression &re, qsizetype from) const const
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QUrl fromEncoded(const QByteArray &input, ParsingMode parsingMode)