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)
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;
169 if (!m_receivedFormats.contains(mimeType)) {
170 if (mimeType == QStringLiteral(
"text/plain") && m_receivedFormats.contains(utf8Text())) {
172 }
else if (mimeType == applicationQtXImageLiteral()) {
173 const auto writeFormats = imageWriteMimeFormats();
174 for (
const auto &receivedFormat : m_receivedFormats) {
175 if (writeFormats.contains(receivedFormat)) {
176 mime = receivedFormat;
182 mime = QStringLiteral(
"image/png");
194 if (pipe(pipeFds) != 0) {
198 auto t =
const_cast<DataControlOffer *
>(
this);
199 t->receive(mime, pipeFds[1]);
211 auto display = waylandApp->
display();
213 wl_display_flush(display);
218 if (readData(pipeFds[0],
data)) {
221 if (mimeType == applicationQtXImageLiteral()) {
226 }
else if (
data.
size() > 1 && mimeType == u
"text/uri-list") {
231#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
252 pfds[0].events = POLLIN;
255 const int ready = poll(pfds, 1, 1000);
257 if (errno != EINTR) {
258 qWarning(
"DataControlOffer: poll() failed: %s", strerror(errno));
261 }
else if (ready == 0) {
262 qWarning(
"DataControlOffer: timeout reading from pipe");
266 int n =
read(fd, buf,
sizeof buf);
269 qWarning(
"DataControlOffer: read() failed: %s", strerror(errno));
280class DataControlSource :
public QObject,
public QtWayland::zwlr_data_control_source_v1
284 DataControlSource(struct ::zwlr_data_control_source_v1 *
id,
QMimeData *mimeData);
285 DataControlSource() =
default;
293 return m_mimeData.get();
295 std::unique_ptr<QMimeData> releaseMimeData()
297 return std::move(m_mimeData);
304 void zwlr_data_control_source_v1_send(
const QString &mime_type, int32_t fd)
override;
305 void zwlr_data_control_source_v1_cancelled()
override;
308 std::unique_ptr<QMimeData> m_mimeData;
311DataControlSource::DataControlSource(struct ::zwlr_data_control_source_v1 *
id,
QMimeData *mimeData)
312 : QtWayland::zwlr_data_control_source_v1(id)
313 , m_mimeData(mimeData)
315 const auto formats = mimeData->
formats();
316 for (
const QString &format : formats) {
321 offer(QStringLiteral(
"text/plain;charset=utf-8"));
325 const QStringList imageFormats = imageWriteMimeFormats();
326 for (const QString &imageFormat : imageFormats) {
327 if (!formats.contains(imageFormat)) {
334void DataControlSource::zwlr_data_control_source_v1_send(
const QString &mime_type, int32_t fd)
336 QString send_mime_type = mime_type;
337 if (send_mime_type == QStringLiteral(
"text/plain;charset=utf-8")) {
339 send_mime_type = QStringLiteral(
"text/plain");
343 if (m_mimeData->hasImage()) {
345 if (mime_type == applicationQtXImageLiteral()) {
346 QImage image = qvariant_cast<QImage>(m_mimeData->imageData());
350 image.
save(&buf,
"PNG");
353 QImage image = qvariant_cast<QImage>(m_mimeData->imageData());
360 ba = m_mimeData->data(send_mime_type);
365 struct sigaction action, oldAction;
366 action.sa_handler = SIG_IGN;
367 sigemptyset(&action.sa_mask);
369 sigaction(SIGPIPE, &action, &oldAction);
371 sigaction(SIGPIPE, &oldAction,
nullptr);
375void DataControlSource::zwlr_data_control_source_v1_cancelled()
380class DataControlDevice :
public QObject,
public QtWayland::zwlr_data_control_device_v1
384 DataControlDevice(struct ::zwlr_data_control_device_v1 *
id)
385 : QtWayland::zwlr_data_control_device_v1(id)
394 void setSelection(std::unique_ptr<DataControlSource> selection);
397 return m_receivedSelection.get();
401 return m_selection ? m_selection->mimeData() :
nullptr;
404 void setPrimarySelection(std::unique_ptr<DataControlSource> selection);
407 return m_receivedPrimarySelection.get();
411 return m_primarySelection ? m_primarySelection->mimeData() :
nullptr;
415 void receivedSelectionChanged();
416 void selectionChanged();
418 void receivedPrimarySelectionChanged();
419 void primarySelectionChanged();
422 void zwlr_data_control_device_v1_data_offer(struct ::zwlr_data_control_offer_v1 *
id)
override
426 new DataControlOffer(
id);
429 void zwlr_data_control_device_v1_selection(struct ::zwlr_data_control_offer_v1 *
id)
override
432 m_receivedSelection.reset();
434 auto derivated = QtWayland::zwlr_data_control_offer_v1::fromObject(
id);
435 auto offer =
dynamic_cast<DataControlOffer *
>(derivated);
436 m_receivedSelection.reset(offer);
438 Q_EMIT receivedSelectionChanged();
441 void zwlr_data_control_device_v1_primary_selection(struct ::zwlr_data_control_offer_v1 *
id)
override
444 m_receivedPrimarySelection.reset();
446 auto derivated = QtWayland::zwlr_data_control_offer_v1::fromObject(
id);
447 auto offer =
dynamic_cast<DataControlOffer *
>(derivated);
448 m_receivedPrimarySelection.reset(offer);
450 Q_EMIT receivedPrimarySelectionChanged();
454 std::unique_ptr<DataControlSource> m_selection;
455 std::unique_ptr<DataControlOffer> m_receivedSelection;
457 std::unique_ptr<DataControlSource> m_primarySelection;
458 std::unique_ptr<DataControlOffer> m_receivedPrimarySelection;
459 friend WaylandClipboard;
462void DataControlDevice::setSelection(std::unique_ptr<DataControlSource> selection)
464 m_selection = std::move(selection);
465 connect(m_selection.get(), &DataControlSource::cancelled,
this, [
this]() {
468 set_selection(m_selection->object());
469 Q_EMIT selectionChanged();
472void DataControlDevice::setPrimarySelection(std::unique_ptr<DataControlSource> selection)
474 m_primarySelection = std::move(selection);
475 connect(m_primarySelection.get(), &DataControlSource::cancelled,
this, [
this]() {
476 m_primarySelection.reset();
479 if (zwlr_data_control_device_v1_get_version(
object()) >= ZWLR_DATA_CONTROL_DEVICE_V1_SET_PRIMARY_SELECTION_SINCE_VERSION) {
480 set_primary_selection(m_primarySelection->object());
481 Q_EMIT primarySelectionChanged();
486class KeyboardFocusWatcher :
public QWaylandClientExtensionTemplate<KeyboardFocusWatcher>,
public QtWayland::wl_seat
490 KeyboardFocusWatcher()
491 : QWaylandClientExtensionTemplate(5)
495 auto display = waylandApp->
display();
497 wl_display_roundtrip(display);
499 ~KeyboardFocusWatcher()
override
505 void seat_capabilities(uint32_t capabilities)
override
507 const bool hasKeyboard =
capabilities & capability_keyboard;
508 if (hasKeyboard && !m_keyboard) {
509 m_keyboard = std::make_unique<Keyboard>(get_keyboard(), *
this);
510 }
else if (!hasKeyboard && m_keyboard) {
514 bool hasFocus()
const
519 void keyboardEntered();
523 bool m_focus =
false;
524 std::unique_ptr<Keyboard> m_keyboard;
527class Keyboard :
public QtWayland::wl_keyboard
530 Keyboard(::wl_keyboard *keyboard, KeyboardFocusWatcher &seat)
531 : wl_keyboard(keyboard)
541 void keyboard_enter([[maybe_unused]] uint32_t serial, [[maybe_unused]] wl_surface *surface, [[maybe_unused]] wl_array *keys)
override
543 m_seat.m_focus =
true;
544 Q_EMIT m_seat.keyboardEntered();
546 void keyboard_leave([[maybe_unused]] uint32_t serial, [[maybe_unused]] wl_surface *surface)
override
548 m_seat.m_focus =
false;
550 KeyboardFocusWatcher &m_seat;
553WaylandClipboard::WaylandClipboard(
QObject *parent)
555 , m_keyboardFocusWatcher(new KeyboardFocusWatcher)
556 , m_manager(new DataControlDeviceManager)
558 connect(m_manager.get(), &DataControlDeviceManager::activeChanged,
this, [
this]() {
559 if (m_manager->isActive()) {
560 auto waylandApp = qGuiApp->nativeInterface<QNativeInterface::QWaylandApplication>();
564 auto seat = waylandApp->seat();
569 m_device.reset(new DataControlDevice(m_manager->get_data_device(seat)));
571 connect(m_device.get(), &DataControlDevice::receivedSelectionChanged, this, [this]() {
573 if (!m_device->selection()) {
574 Q_EMIT changed(QClipboard::Clipboard);
577 connect(m_device.get(), &DataControlDevice::selectionChanged, this, [this]() {
578 Q_EMIT changed(QClipboard::Clipboard);
581 connect(m_device.get(), &DataControlDevice::receivedPrimarySelectionChanged, this, [this]() {
583 if (!m_device->primarySelection()) {
584 Q_EMIT changed(QClipboard::Selection);
587 connect(m_device.get(), &DataControlDevice::primarySelectionChanged, this, [this]() {
588 Q_EMIT changed(QClipboard::Selection);
596 m_manager->instantiate();
599WaylandClipboard::~WaylandClipboard() =
default;
601bool WaylandClipboard::isValid()
603 return m_manager && m_manager->isInitialized();
614 auto display = waylandApp->
display();
615 wl_display_roundtrip(display);
618 if (m_keyboardFocusWatcher->hasFocus()) {
623 wl_display_roundtrip(display);
627 connect(m_keyboardFocusWatcher.get(), &KeyboardFocusWatcher::keyboardEntered,
this, &WaylandClipboard::gainedFocus,
Qt::UniqueConnection);
628 auto source = std::make_unique<DataControlSource>(m_manager->create_data_source(), mime);
630 m_device->setSelection(std::move(source));
632 m_device->setPrimarySelection(std::move(source));
636void WaylandClipboard::gainedFocus()
638 disconnect(m_keyboardFocusWatcher.get(), &KeyboardFocusWatcher::keyboardEntered,
this,
nullptr);
640 if (
auto &selection = m_device->m_selection) {
641 std::unique_ptr<QMimeData> data = selection->releaseMimeData();
645 if (
auto &primarySelection = m_device->m_primarySelection) {
646 std::unique_ptr<QMimeData> data = primarySelection->releaseMimeData();
647 primarySelection.reset();
658 m_device->set_selection(
nullptr);
659 m_device->m_selection.reset();
661 if (zwlr_data_control_device_v1_get_version(m_device->object()) >= ZWLR_DATA_CONTROL_DEVICE_V1_SET_PRIMARY_SELECTION_SINCE_VERSION) {
662 m_device->set_primary_selection(
nullptr);
663 m_device->m_primarySelection.reset();
676 if (m_device->selection()) {
677 return m_device->selection();
683 return m_device->receivedSelection();
685 if (m_device->primarySelection()) {
686 return m_device->primarySelection();
692 return m_device->receivedPrimarySelection();
697#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 QList< QKeySequence > & close()
QByteArray & append(QByteArrayView data)
const char * constData() const const
qsizetype size() const const
QList< QByteArray > split(char sep) 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
bool contains(QLatin1StringView str, 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)
bool isValid() const const