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 | |
17 | static 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 | |
30 | static 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 | |
47 | class Q_DECL_HIDDEN KSelectionWatcher::Private : public QAbstractNativeEventFilter |
48 | { |
49 | public: |
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 | |
71 | protected: |
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 | |
81 | private: |
82 | KSelectionWatcher *watcher; |
83 | }; |
84 | |
85 | KSelectionWatcher::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 | |
93 | KSelectionWatcher::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 | |
98 | KSelectionWatcher::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 | |
106 | KSelectionWatcher::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 | |
111 | KSelectionWatcher::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 | |
118 | KSelectionWatcher::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 | |
125 | KSelectionWatcher::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 | |
132 | KSelectionWatcher::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 | |
139 | KSelectionWatcher::~KSelectionWatcher() |
140 | { |
141 | delete d; |
142 | } |
143 | |
144 | void 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 | |
173 | xcb_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 | |
212 | void 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 | |
244 | xcb_atom_t KSelectionWatcher::Private::manager_atom = XCB_NONE; |
245 | |
246 | #include "moc_kselectionwatcher.cpp" |
247 | |