8#include "waylandclipboard_p.h"
12#include <QGuiApplication>
13#include <QImageReader>
14#include <QImageWriter>
17#include <QWaylandClientExtension>
19#include <QtWaylandClientVersion>
28#include "qwayland-wayland.h"
29#include "qwayland-wlr-data-control-unstable-v1.h"
31static inline QString applicationQtXImageLiteral()
33 return QStringLiteral(
"application/x-qt-image");
39 return QStringLiteral(
"text/plain;charset=utf-8");
46 for (
const auto &format : imageFormats)
50 if (pngIndex != -1 && pngIndex != 0)
51 formats.
move(pngIndex, 0);
66class DataControlDeviceManager :
public QWaylandClientExtensionTemplate<DataControlDeviceManager>,
public QtWayland::zwlr_data_control_manager_v1
70 DataControlDeviceManager()
71 : QWaylandClientExtensionTemplate<DataControlDeviceManager>(2)
80 ~DataControlDeviceManager()
82 if (isInitialized()) {
88class DataControlOffer :
public QMimeData,
public QtWayland::zwlr_data_control_offer_v1
92 DataControlOffer(struct ::zwlr_data_control_offer_v1 *
id)
93 : QtWayland::zwlr_data_control_offer_v1(id)
102 QStringList formats()
const override
104 return m_receivedFormats;
107 bool containsImageData()
const
109 if (m_receivedFormats.contains(applicationQtXImageLiteral())) {
112 const auto formats = imageReadMimeFormats();
113 for (
const auto &receivedFormat : m_receivedFormats) {
114 if (formats.contains(receivedFormat)) {
121 bool hasFormat(
const QString &mimeType)
const override
123 if (mimeType == QStringLiteral(
"text/plain") && m_receivedFormats.contains(utf8Text())) {
126 if (m_receivedFormats.contains(mimeType)) {
131 if (containsImageData()) {
133 const QStringList imageFormats = imageWriteMimeFormats();
134 for (
const QString &imageFormat : imageFormats) {
135 if (imageFormat == mimeType) {
139 if (mimeType == applicationQtXImageLiteral()) {
148 void zwlr_data_control_offer_v1_offer(
const QString &mime_type)
override
150 if (!m_receivedFormats.contains(mime_type)) {
151 m_receivedFormats << mime_type;
155 QVariant retrieveData(
const QString &mimeType, QMetaType type)
const override;
161 static bool readData(
int fd, QByteArray &
data);
162 QStringList m_receivedFormats;
163 mutable QHash<QString, QVariant> m_data;
170 auto it = m_data.constFind(mimeType);
171 if (it != m_data.constEnd())
175 if (!m_receivedFormats.contains(mimeType)) {
176 if (mimeType == QStringLiteral(
"text/plain") && m_receivedFormats.contains(utf8Text())) {
178 }
else if (mimeType == applicationQtXImageLiteral()) {
179 const auto writeFormats = imageWriteMimeFormats();
180 for (
const auto &receivedFormat : m_receivedFormats) {
181 if (writeFormats.contains(receivedFormat)) {
182 mime = receivedFormat;
188 mime = QStringLiteral(
"image/png");
200 if (pipe(pipeFds) != 0) {
204 auto t =
const_cast<DataControlOffer *
>(
this);
205 t->receive(mime, pipeFds[1]);
216 auto waylandApp = qGuiApp->nativeInterface<QNativeInterface::QWaylandApplication>();
217 auto display = waylandApp->display();
219 wl_display_flush(display);
224 if (readData(pipeFds[0],
data)) {
227 if (mimeType == applicationQtXImageLiteral()) {
230 m_data.insert(mimeType, img);
233 }
else if (
data.size() > 1 && mimeType == u
"text/uri-list") {
237 for (
const QByteArray &s :
urls) {
242 m_data.insert(mimeType, list);
245 m_data.insert(mimeType,
data);
254bool DataControlOffer::readData(
int fd,
QByteArray &data)
258 pfds[0].events = POLLIN;
261 const int ready = poll(pfds, 1, 1000);
263 if (errno != EINTR) {
264 qWarning(
"DataControlOffer: poll() failed: %s", strerror(errno));
267 }
else if (ready == 0) {
268 qWarning(
"DataControlOffer: timeout reading from pipe");
272 int n =
read(fd, buf,
sizeof buf);
275 qWarning(
"DataControlOffer: read() failed: %s", strerror(errno));
286class DataControlSource :
public QObject,
public QtWayland::zwlr_data_control_source_v1
290 DataControlSource(struct ::zwlr_data_control_source_v1 *
id, QMimeData *mimeData);
291 DataControlSource() =
default;
297 QMimeData *mimeData()
299 return m_mimeData.get();
301 std::unique_ptr<QMimeData> releaseMimeData()
303 return std::move(m_mimeData);
310 void zwlr_data_control_source_v1_send(
const QString &mime_type, int32_t fd)
override;
311 void zwlr_data_control_source_v1_cancelled()
override;
314 std::unique_ptr<QMimeData> m_mimeData;
317DataControlSource::DataControlSource(struct ::zwlr_data_control_source_v1 *
id,
QMimeData *mimeData)
318 : QtWayland::zwlr_data_control_source_v1(id)
319 , m_mimeData(mimeData)
321 const auto formats = mimeData->
formats();
322 for (
const QString &format : formats) {
327 offer(QStringLiteral(
"text/plain;charset=utf-8"));
331 const QStringList imageFormats = imageWriteMimeFormats();
332 for (const QString &imageFormat : imageFormats) {
333 if (!formats.contains(imageFormat)) {
340void DataControlSource::zwlr_data_control_source_v1_send(
const QString &mime_type, int32_t fd)
342 QString send_mime_type = mime_type;
343 if (send_mime_type == QStringLiteral(
"text/plain;charset=utf-8")) {
345 send_mime_type = QStringLiteral(
"text/plain");
349 if (m_mimeData->hasImage()) {
351 if (mime_type == applicationQtXImageLiteral()) {
352 QImage image = qvariant_cast<QImage>(m_mimeData->imageData());
356 image.
save(&buf,
"PNG");
358 }
else if (mime_type.
startsWith(QLatin1String(
"image/"))) {
359 QImage image = qvariant_cast<QImage>(m_mimeData->imageData());
362 image.
save(&buf, mime_type.
mid(mime_type.
indexOf(QLatin1Char(
'/')) + 1).toLatin1().toUpper().data());
366 ba = m_mimeData->
data(send_mime_type);
375 struct sigaction action, oldAction;
376 action.sa_handler = SIG_IGN;
377 sigemptyset(&action.sa_mask);
379 sigaction(SIGPIPE, &action, &oldAction);
380 const int flags = fcntl(fd, F_GETFL, 0);
381 if (flags & O_NONBLOCK) {
382 fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
384 const qint64 written = c.
write(ba);
385 sigaction(SIGPIPE, &oldAction,
nullptr);
387 if (written != ba.
size()) {
388 qWarning() <<
"Failed to send all clipobard data; sent" << written <<
"bytes out of" << ba.
size();
392void DataControlSource::zwlr_data_control_source_v1_cancelled()
397class DataControlDevice :
public QObject,
public QtWayland::zwlr_data_control_device_v1
401 DataControlDevice(struct ::zwlr_data_control_device_v1 *
id)
402 : QtWayland::zwlr_data_control_device_v1(id)
411 void setSelection(std::unique_ptr<DataControlSource> selection);
412 QMimeData *receivedSelection()
414 return m_receivedSelection.get();
416 QMimeData *selection()
418 return m_selection ? m_selection->mimeData() :
nullptr;
421 void setPrimarySelection(std::unique_ptr<DataControlSource> selection);
422 QMimeData *receivedPrimarySelection()
424 return m_receivedPrimarySelection.get();
426 QMimeData *primarySelection()
428 return m_primarySelection ? m_primarySelection->mimeData() :
nullptr;
432 void receivedSelectionChanged();
433 void selectionChanged();
435 void receivedPrimarySelectionChanged();
436 void primarySelectionChanged();
439 void zwlr_data_control_device_v1_data_offer(struct ::zwlr_data_control_offer_v1 *
id)
override
443 new DataControlOffer(
id);
446 void zwlr_data_control_device_v1_selection(struct ::zwlr_data_control_offer_v1 *
id)
override
449 m_receivedSelection.reset();
451 auto derivated = QtWayland::zwlr_data_control_offer_v1::fromObject(
id);
452 auto offer =
dynamic_cast<DataControlOffer *
>(derivated);
453 m_receivedSelection.reset(offer);
455 Q_EMIT receivedSelectionChanged();
458 void zwlr_data_control_device_v1_primary_selection(struct ::zwlr_data_control_offer_v1 *
id)
override
461 m_receivedPrimarySelection.reset();
463 auto derivated = QtWayland::zwlr_data_control_offer_v1::fromObject(
id);
464 auto offer =
dynamic_cast<DataControlOffer *
>(derivated);
465 m_receivedPrimarySelection.reset(offer);
467 Q_EMIT receivedPrimarySelectionChanged();
471 std::unique_ptr<DataControlSource> m_selection;
472 std::unique_ptr<DataControlOffer> m_receivedSelection;
474 std::unique_ptr<DataControlSource> m_primarySelection;
475 std::unique_ptr<DataControlOffer> m_receivedPrimarySelection;
476 friend WaylandClipboard;
479void DataControlDevice::setSelection(std::unique_ptr<DataControlSource> selection)
481 m_selection = std::move(selection);
482 connect(m_selection.get(), &DataControlSource::cancelled,
this, [
this]() {
485 set_selection(m_selection->object());
486 Q_EMIT selectionChanged();
489void DataControlDevice::setPrimarySelection(std::unique_ptr<DataControlSource> selection)
491 m_primarySelection = std::move(selection);
492 connect(m_primarySelection.get(), &DataControlSource::cancelled,
this, [
this]() {
493 m_primarySelection.reset();
496 if (zwlr_data_control_device_v1_get_version(
object()) >= ZWLR_DATA_CONTROL_DEVICE_V1_SET_PRIMARY_SELECTION_SINCE_VERSION) {
497 set_primary_selection(m_primarySelection->object());
498 Q_EMIT primarySelectionChanged();
503class KeyboardFocusWatcher :
public QWaylandClientExtensionTemplate<KeyboardFocusWatcher>,
public QtWayland::wl_seat
507 KeyboardFocusWatcher()
508 : QWaylandClientExtensionTemplate(5)
511 auto waylandApp = qGuiApp->nativeInterface<QNativeInterface::QWaylandApplication>();
512 auto display = waylandApp->
display();
514 wl_display_roundtrip(display);
516 ~KeyboardFocusWatcher()
override
522 void seat_capabilities(uint32_t capabilities)
override
524 const bool hasKeyboard =
capabilities & capability_keyboard;
525 if (hasKeyboard && !m_keyboard) {
526 m_keyboard = std::make_unique<Keyboard>(get_keyboard(), *
this);
527 }
else if (!hasKeyboard && m_keyboard) {
531 bool hasFocus()
const
536 void keyboardEntered();
540 bool m_focus =
false;
541 std::unique_ptr<Keyboard> m_keyboard;
544class Keyboard :
public QtWayland::wl_keyboard
547 Keyboard(::wl_keyboard *keyboard, KeyboardFocusWatcher &seat)
548 : wl_keyboard(keyboard)
558 void keyboard_enter([[maybe_unused]] uint32_t serial, [[maybe_unused]] wl_surface *surface, [[maybe_unused]] wl_array *keys)
override
560 m_seat.m_focus =
true;
561 Q_EMIT m_seat.keyboardEntered();
563 void keyboard_leave([[maybe_unused]] uint32_t serial, [[maybe_unused]] wl_surface *surface)
override
565 m_seat.m_focus =
false;
567 KeyboardFocusWatcher &m_seat;
570WaylandClipboard::WaylandClipboard(
QObject *parent)
572 , m_keyboardFocusWatcher(new KeyboardFocusWatcher)
573 , m_manager(new DataControlDeviceManager)
575 connect(m_manager.get(), &DataControlDeviceManager::activeChanged,
this, [
this]() {
576 if (m_manager->isActive()) {
577 auto waylandApp = qGuiApp->nativeInterface<QNativeInterface::QWaylandApplication>();
581 auto seat = waylandApp->seat();
586 m_device.reset(new DataControlDevice(m_manager->get_data_device(seat)));
588 connect(m_device.get(), &DataControlDevice::receivedSelectionChanged, this, [this]() {
590 if (!m_device->selection()) {
591 Q_EMIT changed(QClipboard::Clipboard);
594 connect(m_device.get(), &DataControlDevice::selectionChanged, this, [this]() {
595 Q_EMIT changed(QClipboard::Clipboard);
598 connect(m_device.get(), &DataControlDevice::receivedPrimarySelectionChanged, this, [this]() {
600 if (!m_device->primarySelection()) {
601 Q_EMIT changed(QClipboard::Selection);
604 connect(m_device.get(), &DataControlDevice::primarySelectionChanged, this, [this]() {
605 Q_EMIT changed(QClipboard::Selection);
613 m_manager->instantiate();
616WaylandClipboard::~WaylandClipboard() =
default;
618bool WaylandClipboard::isValid()
620 return m_manager && m_manager->isInitialized();
631 auto display = waylandApp->
display();
632 wl_display_roundtrip(display);
635 if (m_keyboardFocusWatcher->hasFocus()) {
640 wl_display_roundtrip(display);
644 connect(m_keyboardFocusWatcher.get(), &KeyboardFocusWatcher::keyboardEntered,
this, &WaylandClipboard::gainedFocus,
Qt::UniqueConnection);
645 auto source = std::make_unique<DataControlSource>(m_manager->create_data_source(), mime);
647 m_device->setSelection(std::move(source));
649 m_device->setPrimarySelection(std::move(source));
653void WaylandClipboard::gainedFocus()
655 disconnect(m_keyboardFocusWatcher.get(), &KeyboardFocusWatcher::keyboardEntered,
this,
nullptr);
657 if (
auto &selection = m_device->m_selection) {
658 std::unique_ptr<QMimeData> data = selection->releaseMimeData();
662 if (
auto &primarySelection = m_device->m_primarySelection) {
663 std::unique_ptr<QMimeData> data = primarySelection->releaseMimeData();
664 primarySelection.reset();
675 m_device->set_selection(
nullptr);
676 m_device->m_selection.reset();
678 if (zwlr_data_control_device_v1_get_version(m_device->object()) >= ZWLR_DATA_CONTROL_DEVICE_V1_SET_PRIMARY_SELECTION_SINCE_VERSION) {
679 m_device->set_primary_selection(
nullptr);
680 m_device->m_primarySelection.reset();
693 if (m_device->selection()) {
694 return m_device->selection();
700 return m_device->receivedSelection();
702 if (m_device->primarySelection()) {
703 return m_device->primarySelection();
709 return m_device->receivedPrimarySelection();
714#include "waylandclipboard.moc"
This class mimics QClipboard but unlike QClipboard it will continue to get updates even when our wind...
KCALUTILS_EXPORT QString mimeType()
Capabilities capabilities()
QVariant read(const QByteArray &data, int versionOverride=0)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
void initialize(StandardShortcut id)
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
qint64 write(const QByteArray &data)
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)