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

source code of qtbase/src/plugins/platforms/xcb/qxcbconnection_basic.cpp