8#include "wlrwaylandclipboard_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 WlrDataControlDeviceManager :
public QWaylandClientExtensionTemplate<WlrDataControlDeviceManager>,
public QtWayland::zwlr_data_control_manager_v1
70 WlrDataControlDeviceManager()
71 : QWaylandClientExtensionTemplate<WlrDataControlDeviceManager>(2)
80 ~WlrDataControlDeviceManager()
82 if (isInitialized()) {
88class WlrDataControlOffer :
public QMimeData,
public QtWayland::zwlr_data_control_offer_v1
92 WlrDataControlOffer(struct ::zwlr_data_control_offer_v1 *
id)
93 : QtWayland::zwlr_data_control_offer_v1(id)
97 ~WlrDataControlOffer()
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<WlrDataControlOffer *
>(
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 WlrDataControlOffer::readData(
int fd,
QByteArray &data)
258 pfds[0].events = POLLIN;
261 const int ready = poll(pfds, 1, 1000);
263 if (errno != EINTR) {
264 qWarning(
"WlrDataControlOffer: poll() failed: %s", strerror(errno));
267 }
else if (ready == 0) {
268 qWarning(
"WlrDataControlOffer: timeout reading from pipe");
272 int n =
read(fd, buf,
sizeof buf);
275 qWarning(
"WlrDataControlOffer: read() failed: %s", strerror(errno));
286class WlrDataControlSource :
public QObject,
public QtWayland::zwlr_data_control_source_v1
290 WlrDataControlSource(struct ::zwlr_data_control_source_v1 *
id, QMimeData *mimeData);
291 WlrDataControlSource() =
default;
292 ~WlrDataControlSource()
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;
317WlrDataControlSource::WlrDataControlSource(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 WlrDataControlSource::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 WlrDataControlSource::zwlr_data_control_source_v1_cancelled()
397class WlrDataControlDevice :
public QObject,
public QtWayland::zwlr_data_control_device_v1
401 WlrDataControlDevice(struct ::zwlr_data_control_device_v1 *
id)
402 : QtWayland::zwlr_data_control_device_v1(id)
406 ~WlrDataControlDevice()
411 void setSelection(std::unique_ptr<WlrDataControlSource> 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<WlrDataControlSource> 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 WlrDataControlOffer(
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<WlrDataControlOffer *
>(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<WlrDataControlOffer *
>(derivated);
465 m_receivedPrimarySelection.reset(offer);
467 Q_EMIT receivedPrimarySelectionChanged();
471 std::unique_ptr<WlrDataControlSource> m_selection;
472 std::unique_ptr<WlrDataControlOffer> m_receivedSelection;
474 std::unique_ptr<WlrDataControlSource> m_primarySelection;
475 std::unique_ptr<WlrDataControlOffer> m_receivedPrimarySelection;
476 friend WlrWaylandClipboard;
479void WlrDataControlDevice::setSelection(std::unique_ptr<WlrDataControlSource> selection)
481 m_selection = std::move(selection);
482 connect(m_selection.get(), &WlrDataControlSource::cancelled,
this, [
this]() {
485 set_selection(m_selection->object());
486 Q_EMIT selectionChanged();
489void WlrDataControlDevice::setPrimarySelection(std::unique_ptr<WlrDataControlSource> selection)
491 m_primarySelection = std::move(selection);
492 connect(m_primarySelection.get(), &WlrDataControlSource::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();
504class WlrKeyboardFocusWatcher :
public QWaylandClientExtensionTemplate<WlrKeyboardFocusWatcher>,
public QtWayland::wl_seat
508 WlrKeyboardFocusWatcher()
509 : QWaylandClientExtensionTemplate(5)
512 auto waylandApp = qGuiApp->nativeInterface<QNativeInterface::QWaylandApplication>();
513 auto display = waylandApp->
display();
515 wl_display_roundtrip(display);
517 ~WlrKeyboardFocusWatcher()
override
523 void seat_capabilities(uint32_t capabilities)
override
525 const bool hasWlrKeyboard =
capabilities & capability_keyboard;
526 if (hasWlrKeyboard && !m_keyboard) {
527 m_keyboard = std::make_unique<WlrKeyboard>(get_keyboard(), *
this);
528 }
else if (!hasWlrKeyboard && m_keyboard) {
532 bool hasFocus()
const
537 void keyboardEntered();
541 bool m_focus =
false;
542 std::unique_ptr<WlrKeyboard> m_keyboard;
545class WlrKeyboard :
public QtWayland::wl_keyboard
548 WlrKeyboard(::wl_keyboard *keyboard, WlrKeyboardFocusWatcher &seat)
549 : wl_keyboard(keyboard)
559 void keyboard_enter([[maybe_unused]] uint32_t serial, [[maybe_unused]] wl_surface *surface, [[maybe_unused]] wl_array *keys)
override
561 m_seat.m_focus =
true;
562 Q_EMIT m_seat.keyboardEntered();
564 void keyboard_leave([[maybe_unused]] uint32_t serial, [[maybe_unused]] wl_surface *surface)
override
566 m_seat.m_focus =
false;
568 WlrKeyboardFocusWatcher &m_seat;
571WlrWaylandClipboard::WlrWaylandClipboard(
QObject *parent)
573 , m_keyboardFocusWatcher(new WlrKeyboardFocusWatcher)
574 , m_manager(new WlrDataControlDeviceManager)
576 connect(m_manager.get(), &WlrDataControlDeviceManager::activeChanged,
this, [
this]() {
577 if (m_manager->isActive()) {
578 auto waylandApp = qGuiApp->nativeInterface<QNativeInterface::QWaylandApplication>();
582 auto seat = waylandApp->seat();
587 m_device.reset(new WlrDataControlDevice(m_manager->get_data_device(seat)));
589 connect(m_device.get(), &WlrDataControlDevice::receivedSelectionChanged, this, [this]() {
591 if (!m_device->selection()) {
592 Q_EMIT changed(QClipboard::Clipboard);
595 connect(m_device.get(), &WlrDataControlDevice::selectionChanged, this, [this]() {
596 Q_EMIT changed(QClipboard::Clipboard);
599 connect(m_device.get(), &WlrDataControlDevice::receivedPrimarySelectionChanged, this, [this]() {
601 if (!m_device->primarySelection()) {
602 Q_EMIT changed(QClipboard::Selection);
605 connect(m_device.get(), &WlrDataControlDevice::primarySelectionChanged, this, [this]() {
606 Q_EMIT changed(QClipboard::Selection);
614 m_manager->instantiate();
617WlrWaylandClipboard::~WlrWaylandClipboard() =
default;
619WlrWaylandClipboard *WlrWaylandClipboard::create(
QObject *parent)
621 auto clipboard =
new WlrWaylandClipboard(parent);
622 if (clipboard->isValid()) {
629bool WlrWaylandClipboard::isValid()
631 return m_manager && m_manager->isInitialized();
642 auto display = waylandApp->
display();
643 wl_display_roundtrip(display);
646 if (m_keyboardFocusWatcher->hasFocus()) {
651 wl_display_roundtrip(display);
655 connect(m_keyboardFocusWatcher.get(), &WlrKeyboardFocusWatcher::keyboardEntered,
this, &WlrWaylandClipboard::gainedFocus,
Qt::UniqueConnection);
656 auto source = std::make_unique<WlrDataControlSource>(m_manager->create_data_source(), mime);
658 m_device->setSelection(std::move(source));
660 m_device->setPrimarySelection(std::move(source));
664void WlrWaylandClipboard::gainedFocus()
666 disconnect(m_keyboardFocusWatcher.get(), &WlrKeyboardFocusWatcher::keyboardEntered,
this,
nullptr);
668 if (
auto &selection = m_device->m_selection) {
669 std::unique_ptr<QMimeData> data = selection->releaseMimeData();
673 if (
auto &primarySelection = m_device->m_primarySelection) {
674 std::unique_ptr<QMimeData> data = primarySelection->releaseMimeData();
675 primarySelection.reset();
686 m_device->set_selection(
nullptr);
687 m_device->m_selection.reset();
689 if (zwlr_data_control_device_v1_get_version(m_device->object()) >= ZWLR_DATA_CONTROL_DEVICE_V1_SET_PRIMARY_SELECTION_SINCE_VERSION) {
690 m_device->set_primary_selection(
nullptr);
691 m_device->m_primarySelection.reset();
704 if (m_device->selection()) {
705 return m_device->selection();
711 return m_device->receivedSelection();
713 if (m_device->primarySelection()) {
714 return m_device->primarySelection();
720 return m_device->receivedPrimarySelection();
725#include "wlrwaylandclipboard.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)
QAction * close(const QObject *recvr, const char *slot, QObject *parent)
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)