1/*
2 SPDX-FileCopyrightText: 2003 Lubos Lunak <l.lunak@kde.org>
3
4 SPDX-License-Identifier: MIT
5*/
6
7#include "kselectionwatcher.h"
8
9#include "kwindowsystem.h"
10#include <config-kwindowsystem.h>
11
12#include <QAbstractNativeEventFilter>
13#include <QCoreApplication>
14
15#include <private/qtx11extras_p.h>
16
17static xcb_window_t get_selection_owner(xcb_connection_t *c, xcb_atom_t selection)
18{
19 xcb_window_t owner = XCB_NONE;
20 xcb_get_selection_owner_reply_t *reply = xcb_get_selection_owner_reply(c, cookie: xcb_get_selection_owner(c, selection), e: nullptr);
21
22 if (reply) {
23 owner = reply->owner;
24 free(ptr: reply);
25 }
26
27 return owner;
28}
29
30static xcb_atom_t intern_atom(xcb_connection_t *c, const char *name)
31{
32 xcb_atom_t atom = XCB_NONE;
33 xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(c, cookie: xcb_intern_atom(c, only_if_exists: false, name_len: strlen(s: name), name), e: nullptr);
34
35 if (reply) {
36 atom = reply->atom;
37 free(ptr: reply);
38 }
39
40 return atom;
41}
42
43//*******************************************
44// KSelectionWatcher
45//*******************************************
46
47class Q_DECL_HIDDEN KSelectionWatcher::Private : public QAbstractNativeEventFilter
48{
49public:
50 Private(KSelectionWatcher *watcher_P, xcb_atom_t selection_P, xcb_connection_t *c, xcb_window_t root)
51 : connection(c)
52 , root(root)
53 , selection(selection_P)
54 , selection_owner(XCB_NONE)
55 , watcher(watcher_P)
56 {
57 QCoreApplication::instance()->installNativeEventFilter(filterObj: this);
58 }
59
60 xcb_connection_t *connection;
61 xcb_window_t root;
62 const xcb_atom_t selection;
63 xcb_window_t selection_owner;
64 static xcb_atom_t manager_atom;
65
66 static Private *create(KSelectionWatcher *watcher, xcb_atom_t selection_P, int screen_P);
67 static Private *create(KSelectionWatcher *watcher, const char *selection_P, int screen_P);
68 static Private *create(KSelectionWatcher *watcher, xcb_atom_t selection_P, xcb_connection_t *c, xcb_window_t root);
69 static Private *create(KSelectionWatcher *watcher, const char *selection_P, xcb_connection_t *c, xcb_window_t root);
70
71protected:
72 bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *) override
73 {
74 if (eventType != "xcb_generic_event_t") {
75 return false;
76 }
77 watcher->filterEvent(ev_P: message);
78 return false;
79 }
80
81private:
82 KSelectionWatcher *watcher;
83};
84
85KSelectionWatcher::Private *KSelectionWatcher::Private::create(KSelectionWatcher *watcher, xcb_atom_t selection_P, int screen_P)
86{
87 if (KWindowSystem::isPlatformX11()) {
88 return create(watcher, selection_P, c: QX11Info::connection(), root: QX11Info::appRootWindow(screen: screen_P));
89 }
90 return nullptr;
91}
92
93KSelectionWatcher::Private *KSelectionWatcher::Private::create(KSelectionWatcher *watcher, xcb_atom_t selection_P, xcb_connection_t *c, xcb_window_t root)
94{
95 return new Private(watcher, selection_P, c, root);
96}
97
98KSelectionWatcher::Private *KSelectionWatcher::Private::create(KSelectionWatcher *watcher, const char *selection_P, int screen_P)
99{
100 if (KWindowSystem::isPlatformX11()) {
101 return create(watcher, selection_P, c: QX11Info::connection(), root: QX11Info::appRootWindow(screen: screen_P));
102 }
103 return nullptr;
104}
105
106KSelectionWatcher::Private *KSelectionWatcher::Private::create(KSelectionWatcher *watcher, const char *selection_P, xcb_connection_t *c, xcb_window_t root)
107{
108 return new Private(watcher, intern_atom(c, name: selection_P), c, root);
109}
110
111KSelectionWatcher::KSelectionWatcher(xcb_atom_t selection_P, int screen_P, QObject *parent_P)
112 : QObject(parent_P)
113 , d(Private::create(watcher: this, selection_P, screen_P))
114{
115 init();
116}
117
118KSelectionWatcher::KSelectionWatcher(const char *selection_P, int screen_P, QObject *parent_P)
119 : QObject(parent_P)
120 , d(Private::create(watcher: this, selection_P, screen_P))
121{
122 init();
123}
124
125KSelectionWatcher::KSelectionWatcher(xcb_atom_t selection, xcb_connection_t *c, xcb_window_t root, QObject *parent)
126 : QObject(parent)
127 , d(Private::create(watcher: this, selection_P: selection, c, root))
128{
129 init();
130}
131
132KSelectionWatcher::KSelectionWatcher(const char *selection, xcb_connection_t *c, xcb_window_t root, QObject *parent)
133 : QObject(parent)
134 , d(Private::create(watcher: this, selection_P: selection, c, root))
135{
136 init();
137}
138
139KSelectionWatcher::~KSelectionWatcher()
140{
141 delete d;
142}
143
144void KSelectionWatcher::init()
145{
146 if (!d) {
147 return;
148 }
149 if (Private::manager_atom == XCB_NONE) {
150 xcb_connection_t *c = d->connection;
151
152 xcb_intern_atom_cookie_t atom_cookie = xcb_intern_atom(c, only_if_exists: false, name_len: strlen(s: "MANAGER"), name: "MANAGER");
153 xcb_get_window_attributes_cookie_t attr_cookie = xcb_get_window_attributes(c, window: d->root);
154
155 xcb_intern_atom_reply_t *atom_reply = xcb_intern_atom_reply(c, cookie: atom_cookie, e: nullptr);
156 Private::manager_atom = atom_reply->atom;
157 free(ptr: atom_reply);
158
159 xcb_get_window_attributes_reply_t *attr = xcb_get_window_attributes_reply(c, cookie: attr_cookie, e: nullptr);
160 uint32_t event_mask = attr->your_event_mask;
161 free(ptr: attr);
162
163 if (!(event_mask & XCB_EVENT_MASK_STRUCTURE_NOTIFY)) {
164 // We need XCB_EVENT_MASK_STRUCTURE_NORITY on the root window
165 event_mask |= XCB_EVENT_MASK_STRUCTURE_NOTIFY;
166 xcb_change_window_attributes(c, window: d->root, value_mask: XCB_CW_EVENT_MASK, value_list: &event_mask);
167 }
168 }
169
170 owner(); // trigger reading of current selection status
171}
172
173xcb_window_t KSelectionWatcher::owner()
174{
175 if (!d) {
176 return XCB_WINDOW_NONE;
177 }
178 xcb_connection_t *c = d->connection;
179
180 xcb_window_t current_owner = get_selection_owner(c, selection: d->selection);
181 if (current_owner == XCB_NONE) {
182 return XCB_NONE;
183 }
184
185 if (current_owner == d->selection_owner) {
186 return d->selection_owner;
187 }
188
189 // We have a new selection owner - select for structure notify events
190 uint32_t mask = XCB_EVENT_MASK_STRUCTURE_NOTIFY;
191 xcb_void_cookie_t cookie = xcb_change_window_attributes_checked(c, window: current_owner, value_mask: XCB_CW_EVENT_MASK, value_list: &mask);
192
193 // Verify that the owner didn't change again while selecting for events
194 xcb_window_t new_owner = get_selection_owner(c, selection: d->selection);
195 xcb_generic_error_t *err = xcb_request_check(c, cookie);
196
197 if (!err && current_owner == new_owner) {
198 d->selection_owner = current_owner;
199 Q_EMIT newOwner(owner: d->selection_owner);
200 } else {
201 // ### This doesn't look right - the selection could have an owner
202 d->selection_owner = XCB_NONE;
203 }
204
205 if (err) {
206 free(ptr: err);
207 }
208
209 return d->selection_owner;
210}
211
212void KSelectionWatcher::filterEvent(void *ev_P)
213{
214 if (!d) {
215 return;
216 }
217 xcb_generic_event_t *event = reinterpret_cast<xcb_generic_event_t *>(ev_P);
218 const uint response_type = event->response_type & ~0x80;
219 if (response_type == XCB_CLIENT_MESSAGE) {
220 xcb_client_message_event_t *cm_event = reinterpret_cast<xcb_client_message_event_t *>(event);
221
222 if (cm_event->type != Private::manager_atom || cm_event->data.data32[1] != d->selection) {
223 return;
224 }
225 // owner() checks whether the owner changed and emits newOwner()
226 owner();
227 return;
228 }
229 if (response_type == XCB_DESTROY_NOTIFY) {
230 xcb_destroy_notify_event_t *ev = reinterpret_cast<xcb_destroy_notify_event_t *>(event);
231 if (d->selection_owner == XCB_NONE || ev->window != d->selection_owner) {
232 return;
233 }
234
235 d->selection_owner = XCB_NONE; // in case the exactly same ID gets reused as the owner
236
237 if (owner() == XCB_NONE) {
238 Q_EMIT lostOwner(); // it must be safe to delete 'this' in a slot
239 }
240 return;
241 }
242}
243
244xcb_atom_t KSelectionWatcher::Private::manager_atom = XCB_NONE;
245
246#include "moc_kselectionwatcher.cpp"
247

source code of kwindowsystem/src/platforms/xcb/kselectionwatcher.cpp