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-ext-data-control-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::ext_data_control_manager_v1
70 DataControlDeviceManager()
71 : QWaylandClientExtensionTemplate<DataControlDeviceManager>(1)
80 ~DataControlDeviceManager()
82 if (isInitialized()) {
88class DataControlOffer :
public QMimeData,
public QtWayland::ext_data_control_offer_v1
92 DataControlOffer(struct ::ext_data_control_offer_v1 *
id)
93 : QtWayland::ext_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 ext_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::ext_data_control_source_v1
290 DataControlSource(struct ::ext_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 ext_data_control_source_v1_send(
const QString &mime_type, int32_t fd)
override;
311 void ext_data_control_source_v1_cancelled()
override;
314 std::unique_ptr<QMimeData> m_mimeData;
317DataControlSource::DataControlSource(struct ::ext_data_control_source_v1 *
id,
QMimeData *mimeData)
318 : QtWayland::ext_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::ext_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::ext_data_control_source_v1_cancelled()
397class DataControlDevice :
public QObject,
public QtWayland::ext_data_control_device_v1
401 DataControlDevice(struct ::ext_data_control_device_v1 *
id)
402 : QtWayland::ext_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 ext_data_control_device_v1_data_offer(struct ::ext_data_control_offer_v1 *
id)
override
443 new DataControlOffer(
id);
446 void ext_data_control_device_v1_selection(struct ::ext_data_control_offer_v1 *
id)
override
449 m_receivedSelection.reset();
451 auto derivated = QtWayland::ext_data_control_offer_v1::fromObject(
id);
452 auto offer =
dynamic_cast<DataControlOffer *
>(derivated);
453 m_receivedSelection.reset(offer);
455 Q_EMIT receivedSelectionChanged();
458 void ext_data_control_device_v1_primary_selection(struct ::ext_data_control_offer_v1 *
id)
override
461 m_receivedPrimarySelection.reset();
463 auto derivated = QtWayland::ext_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 set_primary_selection(m_primarySelection->object());
497 Q_EMIT primarySelectionChanged();
501class KeyboardFocusWatcher :
public QWaylandClientExtensionTemplate<KeyboardFocusWatcher>,
public QtWayland::wl_seat
505 KeyboardFocusWatcher()
506 : QWaylandClientExtensionTemplate(5)
509 auto waylandApp = qGuiApp->nativeInterface<QNativeInterface::QWaylandApplication>();
510 auto display = waylandApp->
display();
512 wl_display_roundtrip(display);
514 ~KeyboardFocusWatcher()
override
520 void seat_capabilities(uint32_t capabilities)
override
522 const bool hasKeyboard =
capabilities & capability_keyboard;
523 if (hasKeyboard && !m_keyboard) {
524 m_keyboard = std::make_unique<Keyboard>(get_keyboard(), *
this);
525 }
else if (!hasKeyboard && m_keyboard) {
529 bool hasFocus()
const
534 void keyboardEntered();
538 bool m_focus =
false;
539 std::unique_ptr<Keyboard> m_keyboard;
542class Keyboard :
public QtWayland::wl_keyboard
545 Keyboard(::wl_keyboard *keyboard, KeyboardFocusWatcher &seat)
546 : wl_keyboard(keyboard)
556 void keyboard_enter([[maybe_unused]] uint32_t serial, [[maybe_unused]] wl_surface *surface, [[maybe_unused]] wl_array *keys)
override
558 m_seat.m_focus =
true;
559 Q_EMIT m_seat.keyboardEntered();
561 void keyboard_leave([[maybe_unused]] uint32_t serial, [[maybe_unused]] wl_surface *surface)
override
563 m_seat.m_focus =
false;
565 KeyboardFocusWatcher &m_seat;
568WaylandClipboard::WaylandClipboard(
QObject *parent)
570 , m_keyboardFocusWatcher(new KeyboardFocusWatcher)
571 , m_manager(new DataControlDeviceManager)
573 connect(m_manager.get(), &DataControlDeviceManager::activeChanged,
this, [
this]() {
574 if (m_manager->isActive()) {
575 auto waylandApp = qGuiApp->nativeInterface<QNativeInterface::QWaylandApplication>();
579 auto seat = waylandApp->seat();
584 m_device.reset(new DataControlDevice(m_manager->get_data_device(seat)));
586 connect(m_device.get(), &DataControlDevice::receivedSelectionChanged, this, [this]() {
588 if (!m_device->selection()) {
589 Q_EMIT changed(QClipboard::Clipboard);
592 connect(m_device.get(), &DataControlDevice::selectionChanged, this, [this]() {
593 Q_EMIT changed(QClipboard::Clipboard);
596 connect(m_device.get(), &DataControlDevice::receivedPrimarySelectionChanged, this, [this]() {
598 if (!m_device->primarySelection()) {
599 Q_EMIT changed(QClipboard::Selection);
602 connect(m_device.get(), &DataControlDevice::primarySelectionChanged, this, [this]() {
603 Q_EMIT changed(QClipboard::Selection);
611 m_manager->instantiate();
614WaylandClipboard::~WaylandClipboard() =
default;
616WaylandClipboard *WaylandClipboard::create(
QObject *parent)
618 auto clipboard =
new WaylandClipboard(parent);
619 if (clipboard->isValid()) {
626bool WaylandClipboard::isValid()
628 return m_manager && m_manager->isInitialized();
639 auto display = waylandApp->
display();
640 wl_display_roundtrip(display);
643 if (m_keyboardFocusWatcher->hasFocus()) {
648 wl_display_roundtrip(display);
652 connect(m_keyboardFocusWatcher.get(), &KeyboardFocusWatcher::keyboardEntered,
this, &WaylandClipboard::gainedFocus,
Qt::UniqueConnection);
653 auto source = std::make_unique<DataControlSource>(m_manager->create_data_source(), mime);
655 m_device->setSelection(std::move(source));
657 m_device->setPrimarySelection(std::move(source));
661void WaylandClipboard::gainedFocus()
663 disconnect(m_keyboardFocusWatcher.get(), &KeyboardFocusWatcher::keyboardEntered,
this,
nullptr);
665 if (
auto &selection = m_device->m_selection) {
666 std::unique_ptr<QMimeData> data = selection->releaseMimeData();
670 if (
auto &primarySelection = m_device->m_primarySelection) {
671 std::unique_ptr<QMimeData> data = primarySelection->releaseMimeData();
672 primarySelection.reset();
683 m_device->set_selection(
nullptr);
684 m_device->m_selection.reset();
686 m_device->set_primary_selection(
nullptr);
687 m_device->m_primarySelection.reset();
699 if (m_device->selection()) {
700 return m_device->selection();
706 return m_device->receivedSelection();
708 if (m_device->primarySelection()) {
709 return m_device->primarySelection();
715 return m_device->receivedPrimarySelection();
720#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)
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)