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 | |
26 | QT_BEGIN_NAMESPACE |
27 | |
28 | Q_LOGGING_CATEGORY(lcQpaXcb, "qt.qpa.xcb" ) |
29 | |
30 | #if QT_CONFIG(xcb_xlib) |
31 | static 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 | |
42 | static 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 | |
57 | static 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 | |
73 | QXcbBasicConnection::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 | |
119 | QXcbBasicConnection::~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 | |
130 | size_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 | |
138 | xcb_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 | |
152 | QByteArray 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 | |
165 | bool 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: |
174 | typedef 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 | |
182 | bool 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 | |
188 | bool 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 | |
197 | bool QXcbBasicConnection::isXFixesType(uint responseType, int eventType) const |
198 | { |
199 | return m_hasXFixes && responseType == m_xfixesFirstEvent + eventType; |
200 | } |
201 | |
202 | bool QXcbBasicConnection::isXRandrType(uint responseType, int eventType) const |
203 | { |
204 | return m_hasXRandr && responseType == m_xrandrFirstEvent + eventType; |
205 | } |
206 | |
207 | bool QXcbBasicConnection::isXkbType(uint responseType) const |
208 | { |
209 | return m_hasXkb && responseType == m_xkbFirstEvent; |
210 | } |
211 | |
212 | void 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 | |
221 | void 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 | |
256 | void 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 | |
277 | void 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 | |
295 | void 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 | |
317 | void 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 | |
342 | void 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 | |
362 | void 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 | |
387 | QT_END_NAMESPACE |
388 | |
389 | #include "moc_qxcbconnection_basic.cpp" |
390 | |