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