8#include "waylandclipboard_p.h"
12#include <QGuiApplication>
13#include <QImageReader>
14#include <QImageWriter>
17#include <QWaylandClientExtension>
19#include <QtWaylandClientVersion>
20#include <qpa/qplatformnativeinterface.h>
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)
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 m_receivedFormats << mime_type;
168 if (!m_receivedFormats.
contains(mimeType)) {
169 if (mimeType == QStringLiteral(
"text/plain") && m_receivedFormats.
contains(utf8Text())) {
171 }
else if (mimeType == applicationQtXImageLiteral()) {
172 const auto writeFormats = imageWriteMimeFormats();
173 for (
const auto &receivedFormat : m_receivedFormats) {
174 if (writeFormats.contains(receivedFormat)) {
175 mime = receivedFormat;
181 mime = QStringLiteral(
"image/png");
193 if (pipe(pipeFds) != 0) {
197 auto t =
const_cast<DataControlOffer *
>(
this);
198 t->receive(mime, pipeFds[1]);
209 QPlatformNativeInterface *native = qGuiApp->platformNativeInterface();
210 auto display =
static_cast<struct ::wl_display *
>(native->nativeResourceForIntegration(
"wl_display"));
211 wl_display_flush(display);
216 if (readData(pipeFds[0],
data)) {
219 if (mimeType == applicationQtXImageLiteral()) {
232bool DataControlOffer::readData(
int fd,
QByteArray &data)
236 pfds[0].events = POLLIN;
239 const int ready = poll(pfds, 1, 1000);
241 if (errno != EINTR) {
242 qWarning(
"DataControlOffer: poll() failed: %s", strerror(errno));
245 }
else if (ready == 0) {
246 qWarning(
"DataControlOffer: timeout reading from pipe");
250 int n =
read(fd, buf,
sizeof buf);
253 qWarning(
"DataControlOffer: read() failed: %s", strerror(errno));
264class DataControlSource :
public QObject,
public QtWayland::zwlr_data_control_source_v1
268 DataControlSource(struct ::zwlr_data_control_source_v1 *
id,
QMimeData *mimeData);
269 DataControlSource() =
default;
277 return m_mimeData.get();
279 std::unique_ptr<QMimeData> releaseMimeData()
281 return std::move(m_mimeData);
288 void zwlr_data_control_source_v1_send(
const QString &mime_type, int32_t fd)
override;
289 void zwlr_data_control_source_v1_cancelled()
override;
292 std::unique_ptr<QMimeData> m_mimeData;
295DataControlSource::DataControlSource(struct ::zwlr_data_control_source_v1 *
id,
QMimeData *mimeData)
296 : QtWayland::zwlr_data_control_source_v1(id)
297 , m_mimeData(mimeData)
299 const auto formats = mimeData->
formats();
300 for (
const QString &format : formats) {
305 offer(QStringLiteral(
"text/plain;charset=utf-8"));
309 const QStringList imageFormats = imageWriteMimeFormats();
310 for (
const QString &imageFormat : imageFormats) {
311 if (!formats.
contains(imageFormat)) {
318void DataControlSource::zwlr_data_control_source_v1_send(
const QString &mime_type, int32_t fd)
320 QString send_mime_type = mime_type;
321 if (send_mime_type == QStringLiteral(
"text/plain;charset=utf-8")) {
323 send_mime_type = QStringLiteral(
"text/plain");
327 if (m_mimeData->hasImage()) {
329 if (mime_type == applicationQtXImageLiteral()) {
330 QImage image = qvariant_cast<QImage>(m_mimeData->imageData());
334 image.
save(&buf,
"PNG");
337 QImage image = qvariant_cast<QImage>(m_mimeData->imageData());
344 ba = m_mimeData->data(send_mime_type);
349 struct sigaction action, oldAction;
350 action.sa_handler = SIG_IGN;
351 sigemptyset(&action.sa_mask);
353 sigaction(SIGPIPE, &action, &oldAction);
355 sigaction(SIGPIPE, &oldAction,
nullptr);
359void DataControlSource::zwlr_data_control_source_v1_cancelled()
364class DataControlDevice :
public QObject,
public QtWayland::zwlr_data_control_device_v1
368 DataControlDevice(struct ::zwlr_data_control_device_v1 *
id)
369 : QtWayland::zwlr_data_control_device_v1(id)
378 void setSelection(std::unique_ptr<DataControlSource> selection);
381 return m_receivedSelection.get();
385 return m_selection ? m_selection->mimeData() :
nullptr;
388 void setPrimarySelection(std::unique_ptr<DataControlSource> selection);
391 return m_receivedPrimarySelection.get();
395 return m_primarySelection ? m_primarySelection->mimeData() :
nullptr;
399 void receivedSelectionChanged();
400 void selectionChanged();
402 void receivedPrimarySelectionChanged();
403 void primarySelectionChanged();
406 void zwlr_data_control_device_v1_data_offer(struct ::zwlr_data_control_offer_v1 *
id)
override
410 new DataControlOffer(
id);
413 void zwlr_data_control_device_v1_selection(struct ::zwlr_data_control_offer_v1 *
id)
override
416 m_receivedSelection.reset();
418 auto derivated = QtWayland::zwlr_data_control_offer_v1::fromObject(
id);
419 auto offer =
dynamic_cast<DataControlOffer *
>(derivated);
420 m_receivedSelection.reset(offer);
422 Q_EMIT receivedSelectionChanged();
425 void zwlr_data_control_device_v1_primary_selection(struct ::zwlr_data_control_offer_v1 *
id)
override
428 m_receivedPrimarySelection.reset();
430 auto derivated = QtWayland::zwlr_data_control_offer_v1::fromObject(
id);
431 auto offer =
dynamic_cast<DataControlOffer *
>(derivated);
432 m_receivedPrimarySelection.reset(offer);
434 Q_EMIT receivedPrimarySelectionChanged();
438 std::unique_ptr<DataControlSource> m_selection;
439 std::unique_ptr<DataControlOffer> m_receivedSelection;
441 std::unique_ptr<DataControlSource> m_primarySelection;
442 std::unique_ptr<DataControlOffer> m_receivedPrimarySelection;
443 friend WaylandClipboard;
446void DataControlDevice::setSelection(std::unique_ptr<DataControlSource> selection)
448 m_selection = std::move(selection);
449 connect(m_selection.get(), &DataControlSource::cancelled,
this, [
this]() {
452 set_selection(m_selection->object());
453 Q_EMIT selectionChanged();
456void DataControlDevice::setPrimarySelection(std::unique_ptr<DataControlSource> selection)
458 m_primarySelection = std::move(selection);
459 connect(m_primarySelection.get(), &DataControlSource::cancelled,
this, [
this]() {
460 m_primarySelection.reset();
463 if (zwlr_data_control_device_v1_get_version(
object()) >= ZWLR_DATA_CONTROL_DEVICE_V1_SET_PRIMARY_SELECTION_SINCE_VERSION) {
464 set_primary_selection(m_primarySelection->object());
465 Q_EMIT primarySelectionChanged();
470class KeyboardFocusWatcher :
public QWaylandClientExtensionTemplate<KeyboardFocusWatcher>,
public QtWayland::wl_seat
474 KeyboardFocusWatcher()
475 : QWaylandClientExtensionTemplate(5)
478 auto native = qGuiApp->platformNativeInterface();
479 auto display =
static_cast<struct ::wl_display *
>(native->nativeResourceForIntegration(
"wl_display"));
481 wl_display_roundtrip(display);
483 ~KeyboardFocusWatcher()
override
489 void seat_capabilities(uint32_t capabilities)
override
491 const bool hasKeyboard =
capabilities & capability_keyboard;
492 if (hasKeyboard && !m_keyboard) {
493 m_keyboard = std::make_unique<Keyboard>(get_keyboard(), *
this);
494 }
else if (!hasKeyboard && m_keyboard) {
498 bool hasFocus()
const
503 void keyboardEntered();
507 bool m_focus =
false;
508 std::unique_ptr<Keyboard> m_keyboard;
511class Keyboard :
public QtWayland::wl_keyboard
514 Keyboard(::wl_keyboard *keyboard, KeyboardFocusWatcher &seat)
515 : wl_keyboard(keyboard)
525 void keyboard_enter([[maybe_unused]] uint32_t serial, [[maybe_unused]] wl_surface *surface, [[maybe_unused]] wl_array *keys)
override
527 m_seat.m_focus =
true;
528 Q_EMIT m_seat.keyboardEntered();
530 void keyboard_leave([[maybe_unused]] uint32_t serial, [[maybe_unused]] wl_surface *surface)
override
532 m_seat.m_focus =
false;
534 KeyboardFocusWatcher &m_seat;
537WaylandClipboard::WaylandClipboard(
QObject *parent)
539 , m_keyboardFocusWatcher(new KeyboardFocusWatcher)
540 , m_manager(new DataControlDeviceManager)
542 connect(m_manager.get(), &DataControlDeviceManager::activeChanged,
this, [
this]() {
543 if (m_manager->isActive()) {
544 QPlatformNativeInterface *native = qApp->platformNativeInterface();
548 auto seat = static_cast<struct ::wl_seat *>(native->nativeResourceForIntegration(
"wl_seat"));
552 m_device.reset(new DataControlDevice(m_manager->get_data_device(seat)));
554 connect(m_device.get(), &DataControlDevice::receivedSelectionChanged, this, [this]() {
556 if (!m_device->selection()) {
557 Q_EMIT changed(QClipboard::Clipboard);
560 connect(m_device.get(), &DataControlDevice::selectionChanged, this, [this]() {
561 Q_EMIT changed(QClipboard::Clipboard);
564 connect(m_device.get(), &DataControlDevice::receivedPrimarySelectionChanged, this, [this]() {
566 if (!m_device->primarySelection()) {
567 Q_EMIT changed(QClipboard::Selection);
570 connect(m_device.get(), &DataControlDevice::primarySelectionChanged, this, [this]() {
571 Q_EMIT changed(QClipboard::Selection);
579 m_manager->instantiate();
582WaylandClipboard::~WaylandClipboard() =
default;
584bool WaylandClipboard::isValid()
586 return m_manager && m_manager->isInitialized();
596 auto native = qGuiApp->platformNativeInterface();
597 auto display =
static_cast<struct ::wl_display *
>(native->nativeResourceForIntegration(
"wl_display"));
598 wl_display_roundtrip(display);
601 if (m_keyboardFocusWatcher->hasFocus()) {
606 connect(m_keyboardFocusWatcher.get(), &KeyboardFocusWatcher::keyboardEntered,
this, &WaylandClipboard::gainedFocus,
Qt::UniqueConnection);
607 auto source = std::make_unique<DataControlSource>(m_manager->create_data_source(), mime);
609 m_device->setSelection(std::move(source));
611 m_device->setPrimarySelection(std::move(source));
615void WaylandClipboard::gainedFocus()
617 disconnect(m_keyboardFocusWatcher.get(), &KeyboardFocusWatcher::keyboardEntered,
this,
nullptr);
619 if (
auto &selection = m_device->m_selection) {
620 std::unique_ptr<QMimeData> data = selection->releaseMimeData();
624 if (
auto &primarySelection = m_device->m_primarySelection) {
625 std::unique_ptr<QMimeData> data = primarySelection->releaseMimeData();
637 m_device->set_selection(
nullptr);
638 m_device->m_selection.reset();
640 if (zwlr_data_control_device_v1_get_version(m_device->object()) >= ZWLR_DATA_CONTROL_DEVICE_V1_SET_PRIMARY_SELECTION_SINCE_VERSION) {
641 m_device->set_primary_selection(
nullptr);
642 m_device->m_primarySelection.reset();
655 if (m_device->selection()) {
656 return m_device->selection();
662 return m_device->receivedSelection();
664 if (m_device->primarySelection()) {
665 return m_device->primarySelection();
671 return m_device->receivedPrimarySelection();
676#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)
const QList< QKeySequence > & close()
void initialize(StandardShortcut id)
QByteArray & append(QByteArrayView data)
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 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
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)