1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2018 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtCore module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | #include "qxcbconnection_basic.h" |
40 | #include "qxcbbackingstore.h" // for createSystemVShmSegment() |
41 | |
42 | #include <xcb/randr.h> |
43 | #include <xcb/shm.h> |
44 | #include <xcb/sync.h> |
45 | #include <xcb/xfixes.h> |
46 | #include <xcb/xinerama.h> |
47 | #include <xcb/render.h> |
48 | #if QT_CONFIG(xcb_xinput) |
49 | #include <xcb/xinput.h> |
50 | #endif |
51 | #if QT_CONFIG(xkb) |
52 | #define explicit dont_use_cxx_explicit |
53 | #include <xcb/xkb.h> |
54 | #undef explicit |
55 | #endif |
56 | |
57 | #if QT_CONFIG(xcb_xlib) |
58 | #define register /* C++17 deprecated register */ |
59 | #include <X11/Xlib.h> |
60 | #include <X11/Xlib-xcb.h> |
61 | #include <X11/Xlibint.h> |
62 | #include <X11/Xutil.h> |
63 | #undef register |
64 | #endif |
65 | |
66 | QT_BEGIN_NAMESPACE |
67 | |
68 | Q_LOGGING_CATEGORY(lcQpaXcb, "qt.qpa.xcb" ) |
69 | |
70 | #if QT_CONFIG(xcb_xlib) |
71 | static const char * const xcbConnectionErrors[] = { |
72 | "No error" , /* Error 0 */ |
73 | "I/O error" , /* XCB_CONN_ERROR */ |
74 | "Unsupported extension used" , /* XCB_CONN_CLOSED_EXT_NOTSUPPORTED */ |
75 | "Out of memory" , /* XCB_CONN_CLOSED_MEM_INSUFFICIENT */ |
76 | "Maximum allowed requested length exceeded" , /* XCB_CONN_CLOSED_REQ_LEN_EXCEED */ |
77 | "Failed to parse display string" , /* XCB_CONN_CLOSED_PARSE_ERR */ |
78 | "No such screen on display" , /* XCB_CONN_CLOSED_INVALID_SCREEN */ |
79 | "Error during FD passing" /* XCB_CONN_CLOSED_FDPASSING_FAILED */ |
80 | }; |
81 | |
82 | static int nullErrorHandler(Display *dpy, XErrorEvent *err) |
83 | { |
84 | #ifndef Q_XCB_DEBUG |
85 | Q_UNUSED(dpy); |
86 | Q_UNUSED(err); |
87 | #else |
88 | const int buflen = 1024; |
89 | char buf[buflen]; |
90 | |
91 | XGetErrorText(dpy, err->error_code, buf, buflen); |
92 | fprintf(stderr, "X Error: serial %lu error %d %s\n" , err->serial, (int) err->error_code, buf); |
93 | #endif |
94 | return 0; |
95 | } |
96 | |
97 | static int ioErrorHandler(Display *dpy) |
98 | { |
99 | xcb_connection_t *conn = XGetXCBConnection(dpy); |
100 | if (conn != NULL) { |
101 | /* Print a message with a textual description of the error */ |
102 | int code = xcb_connection_has_error(conn); |
103 | const char *str = "Unknown error" ; |
104 | int arrayLength = sizeof(xcbConnectionErrors) / sizeof(xcbConnectionErrors[0]); |
105 | if (code >= 0 && code < arrayLength) |
106 | str = xcbConnectionErrors[code]; |
107 | |
108 | qWarning("The X11 connection broke: %s (code %d)" , str, code); |
109 | } |
110 | return _XDefaultIOError(dpy); |
111 | } |
112 | #endif |
113 | |
114 | QXcbBasicConnection::QXcbBasicConnection(const char *displayName) |
115 | : m_displayName(displayName ? QByteArray(displayName) : qgetenv("DISPLAY" )) |
116 | { |
117 | #if QT_CONFIG(xcb_xlib) |
118 | Display *dpy = XOpenDisplay(m_displayName.constData()); |
119 | if (dpy) { |
120 | m_primaryScreenNumber = DefaultScreen(dpy); |
121 | m_xcbConnection = XGetXCBConnection(dpy); |
122 | XSetEventQueueOwner(dpy, XCBOwnsEventQueue); |
123 | XSetErrorHandler(nullErrorHandler); |
124 | XSetIOErrorHandler(ioErrorHandler); |
125 | m_xlibDisplay = dpy; |
126 | } |
127 | #else |
128 | m_xcbConnection = xcb_connect(m_displayName.constData(), &m_primaryScreenNumber); |
129 | #endif |
130 | if (Q_UNLIKELY(!isConnected())) { |
131 | qCWarning(lcQpaXcb, "could not connect to display %s" , m_displayName.constData()); |
132 | return; |
133 | } |
134 | |
135 | m_setup = xcb_get_setup(m_xcbConnection); |
136 | m_xcbAtom.initialize(m_xcbConnection); |
137 | m_maximumRequestLength = xcb_get_maximum_request_length(m_xcbConnection); |
138 | |
139 | xcb_extension_t *extensions[] = { |
140 | &xcb_shm_id, &xcb_xfixes_id, &xcb_randr_id, &xcb_shape_id, &xcb_sync_id, |
141 | &xcb_render_id, |
142 | #if QT_CONFIG(xkb) |
143 | &xcb_xkb_id, |
144 | #endif |
145 | #if QT_CONFIG(xcb_xinput) |
146 | &xcb_input_id, |
147 | #endif |
148 | 0 |
149 | }; |
150 | |
151 | for (xcb_extension_t **ext_it = extensions; *ext_it; ++ext_it) |
152 | xcb_prefetch_extension_data (m_xcbConnection, *ext_it); |
153 | |
154 | initializeXSync(); |
155 | if (!qEnvironmentVariableIsSet("QT_XCB_NO_MITSHM" )) |
156 | initializeShm(); |
157 | if (!qEnvironmentVariableIsSet("QT_XCB_NO_XRANDR" )) |
158 | initializeXRandr(); |
159 | if (!m_hasXRandr) |
160 | initializeXinerama(); |
161 | initializeXFixes(); |
162 | initializeXRender(); |
163 | #if QT_CONFIG(xcb_xinput) |
164 | if (!qEnvironmentVariableIsSet("QT_XCB_NO_XI2" )) |
165 | initializeXInput2(); |
166 | #endif |
167 | initializeXShape(); |
168 | initializeXKB(); |
169 | } |
170 | |
171 | QXcbBasicConnection::~QXcbBasicConnection() |
172 | { |
173 | if (isConnected()) { |
174 | #if QT_CONFIG(xcb_xlib) |
175 | XCloseDisplay(static_cast<Display *>(m_xlibDisplay)); |
176 | #else |
177 | xcb_disconnect(m_xcbConnection); |
178 | #endif |
179 | } |
180 | } |
181 | |
182 | size_t QXcbBasicConnection::maxRequestDataBytes(size_t requestSize) const |
183 | { |
184 | if (hasBigRequest()) |
185 | requestSize += 4; // big-request encoding adds 4 bytes |
186 | |
187 | return m_maximumRequestLength * 4 - requestSize; |
188 | } |
189 | |
190 | xcb_atom_t QXcbBasicConnection::internAtom(const char *name) |
191 | { |
192 | if (!name || *name == 0) |
193 | return XCB_NONE; |
194 | |
195 | return Q_XCB_REPLY(xcb_intern_atom, m_xcbConnection, false, strlen(name), name)->atom; |
196 | } |
197 | |
198 | QByteArray QXcbBasicConnection::atomName(xcb_atom_t atom) |
199 | { |
200 | if (!atom) |
201 | return QByteArray(); |
202 | |
203 | auto reply = Q_XCB_REPLY(xcb_get_atom_name, m_xcbConnection, atom); |
204 | if (reply) |
205 | return QByteArray(xcb_get_atom_name_name(reply.get()), xcb_get_atom_name_name_length(reply.get())); |
206 | |
207 | qCWarning(lcQpaXcb) << "atomName: bad atom" << atom; |
208 | return QByteArray(); |
209 | } |
210 | |
211 | bool QXcbBasicConnection::hasBigRequest() const |
212 | { |
213 | return m_maximumRequestLength > m_setup->maximum_request_length; |
214 | } |
215 | |
216 | #if QT_CONFIG(xcb_xinput) |
217 | // Starting from the xcb version 1.9.3 struct xcb_ge_event_t has changed: |
218 | // - "pad0" became "extension" |
219 | // - "pad1" and "pad" became "pad0" |
220 | // New and old version of this struct share the following fields: |
221 | typedef struct qt_xcb_ge_event_t { |
222 | uint8_t response_type; |
223 | uint8_t extension; |
224 | uint16_t sequence; |
225 | uint32_t length; |
226 | uint16_t event_type; |
227 | } qt_xcb_ge_event_t; |
228 | |
229 | bool QXcbBasicConnection::isXIEvent(xcb_generic_event_t *event) const |
230 | { |
231 | qt_xcb_ge_event_t *e = reinterpret_cast<qt_xcb_ge_event_t *>(event); |
232 | return e->extension == m_xiOpCode; |
233 | } |
234 | |
235 | bool QXcbBasicConnection::isXIType(xcb_generic_event_t *event, uint16_t type) const |
236 | { |
237 | if (!isXIEvent(event)) |
238 | return false; |
239 | |
240 | auto *e = reinterpret_cast<qt_xcb_ge_event_t *>(event); |
241 | return e->event_type == type; |
242 | } |
243 | #endif // QT_CONFIG(xcb_xinput) |
244 | |
245 | bool QXcbBasicConnection::isXFixesType(uint responseType, int eventType) const |
246 | { |
247 | return m_hasXFixes && responseType == m_xfixesFirstEvent + eventType; |
248 | } |
249 | |
250 | bool QXcbBasicConnection::isXRandrType(uint responseType, int eventType) const |
251 | { |
252 | return m_hasXRandr && responseType == m_xrandrFirstEvent + eventType; |
253 | } |
254 | |
255 | bool QXcbBasicConnection::isXkbType(uint responseType) const |
256 | { |
257 | return m_hasXkb && responseType == m_xkbFirstEvent; |
258 | } |
259 | |
260 | void QXcbBasicConnection::initializeXSync() |
261 | { |
262 | const xcb_query_extension_reply_t *reply = xcb_get_extension_data(m_xcbConnection, &xcb_sync_id); |
263 | if (!reply || !reply->present) |
264 | return; |
265 | |
266 | m_hasXSync = true; |
267 | } |
268 | |
269 | void QXcbBasicConnection::initializeShm() |
270 | { |
271 | const xcb_query_extension_reply_t *reply = xcb_get_extension_data(m_xcbConnection, &xcb_shm_id); |
272 | if (!reply || !reply->present) { |
273 | qCDebug(lcQpaXcb, "MIT-SHM extension is not present on the X server" ); |
274 | return; |
275 | } |
276 | |
277 | auto shmQuery = Q_XCB_REPLY(xcb_shm_query_version, m_xcbConnection); |
278 | if (!shmQuery) { |
279 | qCWarning(lcQpaXcb, "failed to request MIT-SHM version" ); |
280 | return; |
281 | } |
282 | |
283 | m_hasShm = true; |
284 | m_hasShmFd = (shmQuery->major_version == 1 && shmQuery->minor_version >= 2) || |
285 | shmQuery->major_version > 1; |
286 | |
287 | qCDebug(lcQpaXcb) << "Has MIT-SHM :" << m_hasShm; |
288 | qCDebug(lcQpaXcb) << "Has MIT-SHM FD :" << m_hasShmFd; |
289 | |
290 | // Temporary disable warnings (unless running in debug mode). |
291 | auto logging = const_cast<QLoggingCategory*>(&lcQpaXcb()); |
292 | bool wasEnabled = logging->isEnabled(QtMsgType::QtWarningMsg); |
293 | if (!logging->isEnabled(QtMsgType::QtDebugMsg)) |
294 | logging->setEnabled(QtMsgType::QtWarningMsg, false); |
295 | if (!QXcbBackingStore::createSystemVShmSegment(m_xcbConnection)) { |
296 | qCDebug(lcQpaXcb, "failed to create System V shared memory segment (remote " |
297 | "X11 connection?), disabling SHM" ); |
298 | m_hasShm = m_hasShmFd = false; |
299 | } |
300 | if (wasEnabled) |
301 | logging->setEnabled(QtMsgType::QtWarningMsg, true); |
302 | } |
303 | |
304 | void QXcbBasicConnection::initializeXRender() |
305 | { |
306 | const xcb_query_extension_reply_t *reply = xcb_get_extension_data(m_xcbConnection, &xcb_render_id); |
307 | if (!reply || !reply->present) { |
308 | qCDebug(lcQpaXcb, "XRender extension not present on the X server" ); |
309 | return; |
310 | } |
311 | |
312 | auto xrenderQuery = Q_XCB_REPLY(xcb_render_query_version, m_xcbConnection, |
313 | XCB_RENDER_MAJOR_VERSION, |
314 | XCB_RENDER_MINOR_VERSION); |
315 | if (!xrenderQuery) { |
316 | qCWarning(lcQpaXcb, "xcb_render_query_version failed" ); |
317 | return; |
318 | } |
319 | |
320 | m_hasXRender = true; |
321 | m_xrenderVersion.first = xrenderQuery->major_version; |
322 | m_xrenderVersion.second = xrenderQuery->minor_version; |
323 | } |
324 | |
325 | void QXcbBasicConnection::initializeXinerama() |
326 | { |
327 | const xcb_query_extension_reply_t *reply = xcb_get_extension_data(m_xcbConnection, &xcb_xinerama_id); |
328 | if (!reply || !reply->present) |
329 | return; |
330 | |
331 | auto xineramaActive = Q_XCB_REPLY(xcb_xinerama_is_active, m_xcbConnection); |
332 | if (xineramaActive && xineramaActive->state) |
333 | m_hasXinerama = true; |
334 | } |
335 | |
336 | void QXcbBasicConnection::initializeXFixes() |
337 | { |
338 | const xcb_query_extension_reply_t *reply = xcb_get_extension_data(m_xcbConnection, &xcb_xfixes_id); |
339 | if (!reply || !reply->present) |
340 | return; |
341 | |
342 | auto xfixesQuery = Q_XCB_REPLY(xcb_xfixes_query_version, m_xcbConnection, |
343 | XCB_XFIXES_MAJOR_VERSION, |
344 | XCB_XFIXES_MINOR_VERSION); |
345 | if (!xfixesQuery || xfixesQuery->major_version < 2) { |
346 | qCWarning(lcQpaXcb, "failed to initialize XFixes" ); |
347 | return; |
348 | } |
349 | |
350 | m_hasXFixes = true; |
351 | m_xfixesFirstEvent = reply->first_event; |
352 | } |
353 | |
354 | void QXcbBasicConnection::initializeXRandr() |
355 | { |
356 | const xcb_query_extension_reply_t *reply = xcb_get_extension_data(m_xcbConnection, &xcb_randr_id); |
357 | if (!reply || !reply->present) |
358 | return; |
359 | |
360 | auto xrandrQuery = Q_XCB_REPLY(xcb_randr_query_version, m_xcbConnection, |
361 | XCB_RANDR_MAJOR_VERSION, |
362 | XCB_RANDR_MINOR_VERSION); |
363 | if (!xrandrQuery || (xrandrQuery->major_version < 1 || |
364 | (xrandrQuery->major_version == 1 && xrandrQuery->minor_version < 2))) { |
365 | qCWarning(lcQpaXcb, "failed to initialize XRandr" ); |
366 | return; |
367 | } |
368 | |
369 | m_hasXRandr = true; |
370 | m_xrandrFirstEvent = reply->first_event; |
371 | } |
372 | |
373 | #if QT_CONFIG(xcb_xinput) |
374 | void QXcbBasicConnection::initializeXInput2() |
375 | { |
376 | const xcb_query_extension_reply_t *reply = xcb_get_extension_data(m_xcbConnection, &xcb_input_id); |
377 | if (!reply || !reply->present) { |
378 | qCDebug(lcQpaXcb, "XInput extension is not present on the X server" ); |
379 | return; |
380 | } |
381 | |
382 | auto xinputQuery = Q_XCB_REPLY(xcb_input_xi_query_version, m_xcbConnection, 2, 2); |
383 | if (!xinputQuery || xinputQuery->major_version != 2) { |
384 | qCWarning(lcQpaXcb, "X server does not support XInput 2" ); |
385 | return; |
386 | } |
387 | |
388 | qCDebug(lcQpaXcb, "Using XInput version %d.%d" , |
389 | xinputQuery->major_version, xinputQuery->minor_version); |
390 | |
391 | m_xi2Enabled = true; |
392 | m_xiOpCode = reply->major_opcode; |
393 | m_xinputFirstEvent = reply->first_event; |
394 | m_xi2Minor = xinputQuery->minor_version; |
395 | } |
396 | #endif |
397 | |
398 | void QXcbBasicConnection::initializeXShape() |
399 | { |
400 | const xcb_query_extension_reply_t *reply = xcb_get_extension_data(m_xcbConnection, &xcb_shape_id); |
401 | if (!reply || !reply->present) |
402 | return; |
403 | |
404 | m_hasXhape = true; |
405 | |
406 | auto shapeQuery = Q_XCB_REPLY(xcb_shape_query_version, m_xcbConnection); |
407 | if (!shapeQuery) { |
408 | qCWarning(lcQpaXcb, "failed to initialize XShape extension" ); |
409 | return; |
410 | } |
411 | |
412 | if (shapeQuery->major_version > 1 || (shapeQuery->major_version == 1 && shapeQuery->minor_version >= 1)) { |
413 | // The input shape is the only thing added in SHAPE 1.1 |
414 | m_hasInputShape = true; |
415 | } |
416 | } |
417 | |
418 | void QXcbBasicConnection::initializeXKB() |
419 | { |
420 | #if QT_CONFIG(xkb) |
421 | const xcb_query_extension_reply_t *reply = xcb_get_extension_data(m_xcbConnection, &xcb_xkb_id); |
422 | if (!reply || !reply->present) { |
423 | qCWarning(lcQpaXcb, "XKeyboard extension not present on the X server" ); |
424 | return; |
425 | } |
426 | |
427 | int wantMajor = 1; |
428 | int wantMinor = 0; |
429 | auto xkbQuery = Q_XCB_REPLY(xcb_xkb_use_extension, m_xcbConnection, wantMajor, wantMinor); |
430 | if (!xkbQuery) { |
431 | qCWarning(lcQpaXcb, "failed to initialize XKeyboard extension" ); |
432 | return; |
433 | } |
434 | if (!xkbQuery->supported) { |
435 | qCWarning(lcQpaXcb, "unsupported XKB version (we want %d.%d, but X server has %d.%d)" , |
436 | wantMajor, wantMinor, xkbQuery->serverMajor, xkbQuery->serverMinor); |
437 | return; |
438 | } |
439 | |
440 | m_hasXkb = true; |
441 | m_xkbFirstEvent = reply->first_event; |
442 | #endif |
443 | } |
444 | |
445 | QT_END_NAMESPACE |
446 | |