SPDX-FileCopyrightText: 2003 Lubos Lunak <l.lunak@kde.org>
SPDX-License-Identifier: MIT
*/
#include "kselectionowner.h"
#include "kwindowsystem.h"
#include "kxcbevent_p.h"
#include <config-kwindowsystem.h>
#include <QAbstractNativeEventFilter>
#include <QBasicTimer>
#include <QDebug>
#include <QGuiApplication>
#include <QTimerEvent>
#include <private/qtx11extras_p.h>
static xcb_window_t get_selection_owner(xcb_connection_t *c, xcb_atom_t selection)
{
xcb_window_t owner = XCB_NONE;
xcb_get_selection_owner_reply_t *reply = xcb_get_selection_owner_reply(c, xcb_get_selection_owner(c, selection), nullptr);
if (reply) {
owner = reply->owner;
free(reply);
}
return owner;
}
static xcb_atom_t intern_atom(xcb_connection_t *c, const char *name)
{
xcb_atom_t atom = XCB_NONE;
xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(c, xcb_intern_atom(c, false, strlen(name), name), nullptr);
if (reply) {
atom = reply->atom;
free(reply);
}
return atom;
}
class Q_DECL_HIDDEN KSelectionOwner::Private : public QAbstractNativeEventFilter
{
public:
enum State { Idle, WaitingForTimestamp, WaitingForPreviousOwner };
Private(KSelectionOwner *owner_P, xcb_atom_t selection_P, xcb_connection_t *c, xcb_window_t root)
: state(Idle)
, selection(selection_P)
, connection(c)
, root(root)
, window(XCB_NONE)
, prev_owner(XCB_NONE)
, timestamp(XCB_CURRENT_TIME)
, extra1(0)
, extra2(0)
, force_kill(false)
, owner(owner_P)
{
QCoreApplication::instance()->installNativeEventFilter(this);
}
void claimSucceeded();
void gotTimestamp();
void timeout();
State state;
const xcb_atom_t selection;
xcb_connection_t *connection;
xcb_window_t root;
xcb_window_t window;
xcb_window_t prev_owner;
xcb_timestamp_t timestamp;
uint32_t extra1, extra2;
QBasicTimer timer;
bool force_kill;
static xcb_atom_t manager_atom;
static xcb_atom_t xa_multiple;
static xcb_atom_t xa_targets;
static xcb_atom_t xa_timestamp;
static Private *create(KSelectionOwner *owner, xcb_atom_t selection_P, int screen_P);
static Private *create(KSelectionOwner *owner, const char *selection_P, int screen_P);
static Private *create(KSelectionOwner *owner, xcb_atom_t selection_P, xcb_connection_t *c, xcb_window_t root);
static Private *create(KSelectionOwner *owner, const char *selection_P, xcb_connection_t *c, xcb_window_t root);
protected:
bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *) override
{
if (eventType != "xcb_generic_event_t") {
return false;
}
return owner->filterEvent(message);
}
private:
KSelectionOwner *owner;
};
KSelectionOwner::Private *KSelectionOwner::Private::create(KSelectionOwner *owner, xcb_atom_t selection_P, int screen_P)
{
if (KWindowSystem::isPlatformX11()) {
return create(owner, selection_P, QX11Info::connection(), QX11Info::appRootWindow(screen_P));
}
qWarning() << "Trying to use KSelectionOwner on a non-X11 platform! This is an application bug.";
return nullptr;
}
KSelectionOwner::Private *KSelectionOwner::Private::create(KSelectionOwner *owner, xcb_atom_t selection_P, xcb_connection_t *c, xcb_window_t root)
{
return new Private(owner, selection_P, c, root);
}
KSelectionOwner::Private *KSelectionOwner::Private::create(KSelectionOwner *owner, const char *selection_P, int screen_P)
{
if (KWindowSystem::isPlatformX11()) {
return create(owner, selection_P, QX11Info::connection(), QX11Info::appRootWindow(screen_P));
}
qWarning() << "Trying to use KSelectionOwner on a non-X11 platform! This is an application bug.";
return nullptr;
}
KSelectionOwner::Private *KSelectionOwner::Private::create(KSelectionOwner *owner, const char *selection_P, xcb_connection_t *c, xcb_window_t root)
{
return new Private(owner, intern_atom(c, selection_P), c, root);
}
KSelectionOwner::KSelectionOwner(xcb_atom_t selection_P, int screen_P, QObject *parent_P)
: QObject(parent_P)
, d(Private::create(this, selection_P, screen_P))
{
}
KSelectionOwner::KSelectionOwner(const char *selection_P, int screen_P, QObject *parent_P)
: QObject(parent_P)
, d(Private::create(this, selection_P, screen_P))
{
}
KSelectionOwner::KSelectionOwner(xcb_atom_t selection, xcb_connection_t *c, xcb_window_t root, QObject *parent)
: QObject(parent)
, d(Private::create(this, selection, c, root))
{
}
KSelectionOwner::KSelectionOwner(const char *selection, xcb_connection_t *c, xcb_window_t root, QObject *parent)
: QObject(parent)
, d(Private::create(this, selection, c, root))
{
}
KSelectionOwner::~KSelectionOwner()
{
if (d) {
release();
if (d->window != XCB_WINDOW_NONE) {
xcb_destroy_window(d->connection, d->window);
}
delete d;
}
}
void KSelectionOwner::Private::claimSucceeded()
{
state = Idle;
KXcbEvent<xcb_client_message_event_t> ev;
ev.response_type = XCB_CLIENT_MESSAGE;
ev.format = 32;
ev.window = root;
ev.type = Private::manager_atom;
ev.data.data32[0] = timestamp;
ev.data.data32[1] = selection;
ev.data.data32[2] = window;
ev.data.data32[3] = extra1;
ev.data.data32[4] = extra2;
xcb_send_event(connection, false, root, XCB_EVENT_MASK_STRUCTURE_NOTIFY, ev.buffer());
Q_EMIT owner->claimedOwnership();
}
void KSelectionOwner::Private::gotTimestamp()
{
Q_ASSERT(state == WaitingForTimestamp);
state = Idle;
xcb_connection_t *c = connection;
xcb_set_selection_owner(c, window, selection, timestamp);
xcb_window_t new_owner = get_selection_owner(c, selection);
if (new_owner != window) {
xcb_destroy_window(c, window);
timestamp = XCB_CURRENT_TIME;
window = XCB_NONE;
Q_EMIT owner->failedToClaimOwnership();
return;
}
if (prev_owner != XCB_NONE && force_kill) {
timer.start(1000, owner);
state = WaitingForPreviousOwner;
} else {
claimSucceeded();
}
}
void KSelectionOwner::Private::timeout()
{
Q_ASSERT(state == WaitingForPreviousOwner);
state = Idle;
if (force_kill) {
xcb_connection_t *c = connection;
xcb_generic_error_t *err = xcb_request_check(c, xcb_kill_client_checked(c, prev_owner));
free(err);
claimSucceeded();
} else {
Q_EMIT owner->failedToClaimOwnership();
}
}
void KSelectionOwner::claim(bool force_P, bool force_kill_P)
{
if (!d) {
return;
}
Q_ASSERT(d->state == Private::Idle);
if (Private::manager_atom == XCB_NONE) {
getAtoms();
}
if (d->timestamp != XCB_CURRENT_TIME) {
release();
}
xcb_connection_t *c = d->connection;
d->prev_owner = get_selection_owner(c, d->selection);
if (d->prev_owner != XCB_NONE) {
if (!force_P) {
Q_EMIT failedToClaimOwnership();
return;
}
uint32_t mask = XCB_EVENT_MASK_STRUCTURE_NOTIFY;
xcb_change_window_attributes(c, d->prev_owner, XCB_CW_EVENT_MASK, &mask);
}
uint32_t values[] = {true, XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_STRUCTURE_NOTIFY};
d->window = xcb_generate_id(c);
xcb_create_window(c,
XCB_COPY_FROM_PARENT,
d->window,
d->root,
0,
0,
1,
1,
0,
XCB_WINDOW_CLASS_INPUT_ONLY,
XCB_COPY_FROM_PARENT,
XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK,
values);
xcb_atom_t tmp = XCB_ATOM_ATOM;
xcb_change_property(c, XCB_PROP_MODE_REPLACE, d->window, XCB_ATOM_ATOM, XCB_ATOM_ATOM, 32, 1, (const void *)&tmp);
d->force_kill = force_kill_P;
d->state = Private::WaitingForTimestamp;
}
void KSelectionOwner::release()
{
if (!d) {
return;
}
if (d->timestamp == XCB_CURRENT_TIME) {
return;
}
xcb_destroy_window(d->connection, d->window);
d->window = XCB_NONE;
d->timestamp = XCB_CURRENT_TIME;
}
xcb_window_t KSelectionOwner::ownerWindow() const
{
if (!d) {
return XCB_WINDOW_NONE;
}
if (d->timestamp == XCB_CURRENT_TIME) {
return XCB_NONE;
}
return d->window;
}
void KSelectionOwner::setData(uint32_t extra1_P, uint32_t extra2_P)
{
if (!d) {
return;
}
d->extra1 = extra1_P;
d->extra2 = extra2_P;
}
bool KSelectionOwner::filterEvent(void *ev_P)
{
if (!d) {
return false;
}
xcb_generic_event_t *event = reinterpret_cast<xcb_generic_event_t *>(ev_P);
const uint response_type = event->response_type & ~0x80;
#if 0
if (d->timestamp != CurrentTime && ev_P->xany.window == d->window) {
if (handleMessage(ev_P)) {
return true;
}
}
#endif
switch (response_type) {
case XCB_SELECTION_CLEAR: {
xcb_selection_clear_event_t *ev = reinterpret_cast<xcb_selection_clear_event_t *>(event);
if (d->timestamp == XCB_CURRENT_TIME || ev->selection != d->selection) {
return false;
}
d->timestamp = XCB_CURRENT_TIME;
xcb_window_t window = d->window;
Q_EMIT lostOwnership();
uint32_t event_mask = XCB_NONE;
xcb_change_window_attributes(d->connection, window, XCB_CW_EVENT_MASK, &event_mask);
xcb_destroy_window(d->connection, window);
return true;
}
case XCB_DESTROY_NOTIFY: {
xcb_destroy_notify_event_t *ev = reinterpret_cast<xcb_destroy_notify_event_t *>(event);
if (ev->window == d->prev_owner) {
if (d->state == Private::WaitingForPreviousOwner) {
d->timer.stop();
d->claimSucceeded();
return true;
}
d->prev_owner = XCB_NONE;
}
if (d->timestamp == XCB_CURRENT_TIME || ev->window != d->window) {
return false;
}
d->timestamp = XCB_CURRENT_TIME;
Q_EMIT lostOwnership();
return true;
}
case XCB_SELECTION_NOTIFY: {
xcb_selection_notify_event_t *ev = reinterpret_cast<xcb_selection_notify_event_t *>(event);
if (d->timestamp == XCB_CURRENT_TIME || ev->selection != d->selection) {
return false;
}
return false;
}
case XCB_SELECTION_REQUEST:
filter_selection_request(event);
return false;
case XCB_PROPERTY_NOTIFY: {
xcb_property_notify_event_t *ev = reinterpret_cast<xcb_property_notify_event_t *>(event);
if (ev->window == d->window && d->state == Private::WaitingForTimestamp) {
d->timestamp = ev->time;
d->gotTimestamp();
return true;
}
return false;
}
default:
return false;
}
}
void KSelectionOwner::timerEvent(QTimerEvent *event)
{
if (!d) {
QObject::timerEvent(event);
return;
}
if (event->timerId() == d->timer.timerId()) {
d->timer.stop();
d->timeout();
return;
}
QObject::timerEvent(event);
}
#if 0
bool KSelectionOwner::handleMessage(XEvent *)
{
return false;
}
#endif
void KSelectionOwner::filter_selection_request(void *event)
{
if (!d) {
return;
}
xcb_selection_request_event_t *ev = reinterpret_cast<xcb_selection_request_event_t *>(event);
if (d->timestamp == XCB_CURRENT_TIME || ev->selection != d->selection) {
return;
}
if (ev->time != XCB_CURRENT_TIME && ev->time - d->timestamp > 1U << 31) {
return;
}
xcb_connection_t *c = d->connection;
bool handled = false;
if (ev->target == Private::xa_multiple) {
if (ev->property != XCB_NONE) {
const int MAX_ATOMS = 100;
xcb_get_property_cookie_t cookie = xcb_get_property(c, false, ev->requestor, ev->property, XCB_GET_PROPERTY_TYPE_ANY, 0, MAX_ATOMS);
xcb_get_property_reply_t *reply = xcb_get_property_reply(c, cookie, nullptr);
if (reply && reply->format == 32 && reply->value_len % 2 == 0) {
xcb_atom_t *atoms = reinterpret_cast<xcb_atom_t *>(xcb_get_property_value(reply));
bool handled_array[MAX_ATOMS];
for (uint i = 0; i < reply->value_len / 2; i++) {
handled_array[i] = handle_selection(atoms[i * 2], atoms[i * 2 + 1], ev->requestor);
}
bool all_handled = true;
for (uint i = 0; i < reply->value_len / 2; i++) {
if (!handled_array[i]) {
all_handled = false;
atoms[i * 2 + 1] = XCB_NONE;
}
}
if (!all_handled) {
xcb_change_property(c,
ev->requestor,
ev->property,
XCB_ATOM_ATOM,
32,
XCB_PROP_MODE_REPLACE,
reply->value_len,
reinterpret_cast<const void *>(atoms));
}
handled = true;
}
if (reply) {
free(reply);
}
}
} else {
if (ev->property == XCB_NONE) {
ev->property = ev->target;
}
handled = handle_selection(ev->target, ev->property, ev->requestor);
}
KXcbEvent<xcb_selection_notify_event_t> xev;
xev.response_type = XCB_SELECTION_NOTIFY;
xev.selection = ev->selection;
xev.requestor = ev->requestor;
xev.target = ev->target;
xev.property = handled ? ev->property : XCB_NONE;
xcb_send_event(c, false, ev->requestor, 0, xev.buffer());
}
bool KSelectionOwner::handle_selection(xcb_atom_t target_P, xcb_atom_t property_P, xcb_window_t requestor_P)
{
if (!d) {
return false;
}
if (target_P == Private::xa_timestamp) {
xcb_change_property(d->connection,
requestor_P,
property_P,
XCB_ATOM_INTEGER,
32,
XCB_PROP_MODE_REPLACE,
1,
reinterpret_cast<const void *>(&d->timestamp));
} else if (target_P == Private::xa_targets) {
replyTargets(property_P, requestor_P);
} else if (genericReply(target_P, property_P, requestor_P)) {
} else {
return false;
}
return true;
}
void KSelectionOwner::replyTargets(xcb_atom_t property_P, xcb_window_t requestor_P)
{
if (!d) {
return;
}
xcb_atom_t atoms[3] = {Private::xa_multiple, Private::xa_timestamp, Private::xa_targets};
xcb_change_property(d->connection,
requestor_P,
property_P,
XCB_ATOM_ATOM,
32,
XCB_PROP_MODE_REPLACE,
sizeof(atoms) / sizeof(atoms[0]),
reinterpret_cast<const void *>(atoms));
}
bool KSelectionOwner::genericReply(xcb_atom_t, xcb_atom_t, xcb_window_t)
{
return false;
}
void KSelectionOwner::getAtoms()
{
if (!d) {
return;
}
if (Private::manager_atom != XCB_NONE) {
return;
}
xcb_connection_t *c = d->connection;
struct {
const char *name;
xcb_atom_t *atom;
} atoms[] = {{"MANAGER", &Private::manager_atom},
{"MULTIPLE", &Private::xa_multiple},
{"TARGETS", &Private::xa_targets},
{"TIMESTAMP", &Private::xa_timestamp}};
const int count = sizeof(atoms) / sizeof(atoms[0]);
xcb_intern_atom_cookie_t cookies[count];
for (int i = 0; i < count; i++) {
cookies[i] = xcb_intern_atom(c, false, strlen(atoms[i].name), atoms[i].name);
}
for (int i = 0; i < count; i++) {
if (xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(c, cookies[i], nullptr)) {
*atoms[i].atom = reply->atom;
free(reply);
}
}
}
xcb_atom_t KSelectionOwner::Private::manager_atom = XCB_NONE;
xcb_atom_t KSelectionOwner::Private::xa_multiple = XCB_NONE;
xcb_atom_t KSelectionOwner::Private::xa_targets = XCB_NONE;
xcb_atom_t KSelectionOwner::Private::xa_timestamp = XCB_NONE;
#include "moc_kselectionowner.cpp"