Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update wayland clipboard #2585

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions src/gui/mainwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1279,12 +1279,6 @@ void MainWindow::onBrowserCreated(ClipboardBrowser *browser)
connect( browser, &ClipboardBrowser::itemWidgetCreated,
this, &MainWindow::onItemWidgetCreated );

if (isScriptOverridden(ScriptOverrides::OnItemsLoaded)) {
runEventHandlerScript(
QStringLiteral("onItemsLoaded()"),
createDataMap(mimeCurrentTab, browser->tabName()));
}

connect( browser, &ClipboardBrowser::itemsAboutToBeRemoved,
browser, [this, browser](const QModelIndex &, int first, int last) {
if (isScriptOverridden(ScriptOverrides::OnItemsRemoved))
Expand All @@ -1308,6 +1302,12 @@ void MainWindow::onBrowserCreated(ClipboardBrowser *browser)
const int index = ui->tabWidget->currentIndex();
tabChanged(index, index);
}

if (isScriptOverridden(ScriptOverrides::OnItemsLoaded)) {
runEventHandlerScript(
QStringLiteral("onItemsLoaded()"),
createDataMap(mimeCurrentTab, browser->tabName()));
}
}

void MainWindow::onBrowserDestroyed(ClipboardBrowserPlaceholder *placeholder)
Expand Down
5 changes: 5 additions & 0 deletions src/platform/x11/systemclipboard/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,18 @@ if(WITH_QT6)
qt_generate_wayland_protocol_client_sources(systemclipboard
FILES
"${CMAKE_CURRENT_SOURCE_DIR}/wlr-data-control-unstable-v1.xml"
"${Wayland_DATADIR}/wayland.xml"
)
else()
find_package(QtWaylandScanner REQUIRED)
ecm_add_qtwayland_client_protocol(systemclipboard_SRCS
PROTOCOL wlr-data-control-unstable-v1.xml
BASENAME wlr-data-control-unstable-v1
)
ecm_add_qtwayland_client_protocol(systemclipboard_SRCS
PROTOCOL ${Wayland_DATADIR}/wayland.xml
BASENAME wayland
)
add_library(systemclipboard STATIC ${systemclipboard_SRCS})
endif()

Expand Down
127 changes: 117 additions & 10 deletions src/platform/x11/systemclipboard/waylandclipboard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@
#include <QImageWriter>
#include <QMimeData>
#include <QThread>
#include <QtWaylandClient/QWaylandClientExtension>
#include <QWaylandClientExtension>
#include <QWindow>
#include <QtWaylandClientVersion>
#include <qpa/qplatformnativeinterface.h>
#include <qtwaylandclientversion.h>

#include <errno.h>
#include <fcntl.h>
Expand All @@ -27,6 +28,7 @@
#include <string.h>
#include <unistd.h>

#include "qwayland-wayland.h"
#include "qwayland-wlr-data-control-unstable-v1.h"

static inline QString applicationQtXImageLiteral()
Expand Down Expand Up @@ -337,20 +339,20 @@ class DataControlSource : public QObject, public QtWayland::zwlr_data_control_so
Q_OBJECT
public:
DataControlSource(struct ::zwlr_data_control_source_v1 *id, QMimeData *mimeData);

DataControlSource() = default;
~DataControlSource()
{
if (m_mimeData) {
m_mimeData->deleteLater();
m_mimeData = nullptr;
}
if ( isInitialized() )
destroy();
}

QMimeData *mimeData()
{
return m_mimeData;
return m_mimeData.get();
}
std::unique_ptr<QMimeData> releaseMimeData()
{
return std::move(m_mimeData);
}

bool isCancelled() const { return m_cancelled; }
Expand All @@ -363,7 +365,7 @@ class DataControlSource : public QObject, public QtWayland::zwlr_data_control_so
void zwlr_data_control_source_v1_cancelled() override;

private:
QMimeData *m_mimeData = nullptr;
std::unique_ptr<QMimeData> m_mimeData;
bool m_cancelled = false;
};

Expand Down Expand Up @@ -520,6 +522,7 @@ class DataControlDevice : public QObject, public QtWayland::zwlr_data_control_de

std::unique_ptr<DataControlSource> m_primarySelection; // selection set locally
std::unique_ptr<DataControlOffer> m_receivedPrimarySelection; // latest selection set from externally to here
friend WaylandClipboard;
};

void DataControlDevice::setSelection(std::unique_ptr<DataControlSource> selection)
Expand All @@ -546,9 +549,82 @@ void DataControlDevice::setPrimarySelection(std::unique_ptr<DataControlSource> s
Q_EMIT primarySelectionChanged();
}
}
class Keyboard;
// We are binding to Seat/Keyboard manually because we want to react to gaining focus but inside Qt the events are Qt and arrive to late
class KeyboardFocusWatcher : public QWaylandClientExtensionTemplate<KeyboardFocusWatcher>, public QtWayland::wl_seat
{
Q_OBJECT
public:
KeyboardFocusWatcher()
: QWaylandClientExtensionTemplate(5)
{
#if QTWAYLANDCLIENT_VERSION >= QT_VERSION_CHECK(6, 2, 0)
initialize();
#else
QMetaObject::invokeMethod(this, "addRegistryListener");
#endif
auto native = qGuiApp->platformNativeInterface();
auto display = static_cast<struct ::wl_display *>(native->nativeResourceForIntegration("wl_display"));
// so we get capabilities
wl_display_roundtrip(display);
}
~KeyboardFocusWatcher() override
{
if (isActive()) {
release();
}
}
void seat_capabilities(uint32_t capabilities) override
{
const bool hasKeyboard = capabilities & capability_keyboard;
if (hasKeyboard && !m_keyboard) {
m_keyboard = std::make_unique<Keyboard>(get_keyboard(), *this);
} else if (!hasKeyboard && m_keyboard) {
m_keyboard.reset();
}
}
bool hasFocus() const
{
return m_focus;
}
Q_SIGNALS:
void keyboardEntered();

private:
friend Keyboard;
bool m_focus = false;
std::unique_ptr<Keyboard> m_keyboard;
};

class Keyboard : public QtWayland::wl_keyboard
{
public:
Keyboard(::wl_keyboard *keyboard, KeyboardFocusWatcher &seat)
: wl_keyboard(keyboard)
, m_seat(seat)
{
}
~Keyboard()
{
release();
}

private:
void keyboard_enter([[maybe_unused]] uint32_t serial, [[maybe_unused]] wl_surface *surface, [[maybe_unused]] wl_array *keys) override
{
m_seat.m_focus = true;
Q_EMIT m_seat.keyboardEntered();
}
void keyboard_leave([[maybe_unused]] uint32_t serial, [[maybe_unused]] wl_surface *surface) override
{
m_seat.m_focus = false;
}
KeyboardFocusWatcher &m_seat;
};

WaylandClipboard::WaylandClipboard(QObject *parent)
: QObject(parent)
, m_keyboardFocusWatcher(new KeyboardFocusWatcher)
, m_manager(new DataControlDeviceManager)
{
connect(m_manager.get(), &DataControlDeviceManager::activeChanged, this, [this]() {
Expand All @@ -561,7 +637,6 @@ WaylandClipboard::WaylandClipboard(QObject *parent)
if (!seat) {
return;
}

m_device.reset(new DataControlDevice(m_manager->get_data_device(seat)));

connect(m_device.get(), &DataControlDevice::receivedSelectionChanged, this, [this]() {
Expand Down Expand Up @@ -616,6 +691,20 @@ void WaylandClipboard::setMimeData(QMimeData *mime, QClipboard::Mode mode)
if (!m_device) {
return;
}

// roundtrip to have accurate focus state when losing focus but setting mime data before processing wayland events.
auto native = qGuiApp->platformNativeInterface();
auto display = static_cast<struct ::wl_display *>(native->nativeResourceForIntegration("wl_display"));
wl_display_roundtrip(display);

// If the application is focused, use the normal mechanism so a future paste will not deadlock itselfs
if (m_keyboardFocusWatcher->hasFocus()) {
QGuiApplication::clipboard()->setMimeData(mime, mode);
return;
}
// If not, set the clipboard once the app receives focus to avoid the deadlock
connect(m_keyboardFocusWatcher.get(), &KeyboardFocusWatcher::keyboardEntered, this, &WaylandClipboard::gainedFocus, Qt::UniqueConnection);

std::unique_ptr<DataControlSource> source(new DataControlSource(m_manager->create_data_source(), mime));
if (mode == QClipboard::Clipboard) {
m_device->setSelection(std::move(source));
Expand All @@ -624,16 +713,34 @@ void WaylandClipboard::setMimeData(QMimeData *mime, QClipboard::Mode mode)
}
}

void WaylandClipboard::gainedFocus()
{
disconnect(m_keyboardFocusWatcher.get(), &KeyboardFocusWatcher::keyboardEntered, this, nullptr);
// QClipboard takes ownership of the QMimeData so we need to transfer and unset our selections
if (auto &selection = m_device->m_selection) {
std::unique_ptr<QMimeData> data = selection->releaseMimeData();
WaylandClipboard::clear(QClipboard::Clipboard);
QGuiApplication::clipboard()->setMimeData(data.release(), QClipboard::Clipboard);
}
if (auto &primarySelection = m_device->m_primarySelection) {
std::unique_ptr<QMimeData> data = primarySelection->releaseMimeData();
WaylandClipboard::clear(QClipboard::Selection);
QGuiApplication::clipboard()->setMimeData(data.release(), QClipboard::Selection);
}
}

void WaylandClipboard::clear(QClipboard::Mode mode)
{
if (!m_device) {
return;
}
if (mode == QClipboard::Clipboard) {
m_device->set_selection(nullptr);
m_device->m_selection.reset();
} else if (mode == QClipboard::Selection) {
if (zwlr_data_control_device_v1_get_version(m_device->object()) >= ZWLR_DATA_CONTROL_DEVICE_V1_SET_PRIMARY_SELECTION_SINCE_VERSION) {
m_device->set_primary_selection(nullptr);
m_device->m_primarySelection.reset();
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/platform/x11/systemclipboard/waylandclipboard.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

class DataControlDevice;
class DataControlDeviceManager;
class KeyboardFocusWatcher;
class QMimeData;

class WaylandClipboard final : public QObject
Expand All @@ -35,6 +36,8 @@ class WaylandClipboard final : public QObject
explicit WaylandClipboard(QObject *parent);
static WaylandClipboard *createInstance();

void gainedFocus();
std::unique_ptr<KeyboardFocusWatcher> m_keyboardFocusWatcher;
std::unique_ptr<DataControlDeviceManager> m_manager;
std::unique_ptr<DataControlDevice> m_device;
};
Loading