| 1 | // Copyright (C) 2016 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 | |
| 4 | #include "qxcbbackingstore.h" |
| 5 | |
| 6 | #include "qxcbconnection.h" |
| 7 | #include "qxcbscreen.h" |
| 8 | #include "qxcbwindow.h" |
| 9 | |
| 10 | #include <xcb/shm.h> |
| 11 | #include <xcb/xcb_image.h> |
| 12 | #include <xcb/render.h> |
| 13 | #include <xcb/xcb_renderutil.h> |
| 14 | |
| 15 | #include <sys/ipc.h> |
| 16 | #include <sys/shm.h> |
| 17 | #include <sys/mman.h> |
| 18 | |
| 19 | #include <stdio.h> |
| 20 | #include <errno.h> |
| 21 | #include <unistd.h> |
| 22 | |
| 23 | #include <qdebug.h> |
| 24 | #include <qpainter.h> |
| 25 | #include <qscreen.h> |
| 26 | #include <QtGui/private/qhighdpiscaling_p.h> |
| 27 | #include <qpa/qplatformgraphicsbuffer.h> |
| 28 | #include <private/qimage_p.h> |
| 29 | #include <qendian.h> |
| 30 | |
| 31 | #include <algorithm> |
| 32 | |
| 33 | #if (XCB_SHM_MAJOR_VERSION == 1 && XCB_SHM_MINOR_VERSION >= 2) || XCB_SHM_MAJOR_VERSION > 1 |
| 34 | #define XCB_USE_SHM_FD |
| 35 | #endif |
| 36 | |
| 37 | QT_BEGIN_NAMESPACE |
| 38 | |
| 39 | class QXcbBackingStore; |
| 40 | |
| 41 | class QXcbBackingStoreImage : public QXcbObject |
| 42 | { |
| 43 | public: |
| 44 | QXcbBackingStoreImage(QXcbBackingStore *backingStore, const QSize &size); |
| 45 | QXcbBackingStoreImage(QXcbBackingStore *backingStore, const QSize &size, uint depth, QImage::Format format); |
| 46 | ~QXcbBackingStoreImage() { destroy(destroyShm: true); } |
| 47 | |
| 48 | void resize(const QSize &size); |
| 49 | |
| 50 | void flushScrolledRegion(bool clientSideScroll); |
| 51 | |
| 52 | bool scroll(const QRegion &area, int dx, int dy); |
| 53 | |
| 54 | QImage *image() { return &m_qimage; } |
| 55 | QPlatformGraphicsBuffer *graphicsBuffer() { return m_graphics_buffer; } |
| 56 | |
| 57 | QSize size() const { return m_qimage.size(); } |
| 58 | |
| 59 | bool hasAlpha() const { return m_hasAlpha; } |
| 60 | bool hasShm() const { return m_shm_info.shmaddr != nullptr; } |
| 61 | |
| 62 | void put(xcb_drawable_t dst, const QRegion ®ion, const QPoint &offset); |
| 63 | void preparePaint(const QRegion ®ion); |
| 64 | |
| 65 | static bool createSystemVShmSegment(xcb_connection_t *c, size_t segmentSize = 1, |
| 66 | xcb_shm_segment_info_t *shm_info = nullptr); |
| 67 | |
| 68 | private: |
| 69 | void init(const QSize &size, uint depth, QImage::Format format); |
| 70 | |
| 71 | void createShmSegment(size_t segmentSize); |
| 72 | void destroyShmSegment(); |
| 73 | void destroy(bool destroyShm); |
| 74 | |
| 75 | void ensureGC(xcb_drawable_t dst); |
| 76 | void shmPutImage(xcb_drawable_t drawable, const QRegion ®ion, const QPoint &offset = QPoint()); |
| 77 | void flushPixmap(const QRegion ®ion, bool fullRegion = false); |
| 78 | void setClip(const QRegion ®ion); |
| 79 | |
| 80 | xcb_shm_segment_info_t m_shm_info; |
| 81 | size_t m_segmentSize = 0; |
| 82 | QXcbBackingStore *m_backingStore = nullptr; |
| 83 | |
| 84 | xcb_image_t *m_xcb_image = nullptr; |
| 85 | |
| 86 | QImage m_qimage; |
| 87 | QPlatformGraphicsBuffer *m_graphics_buffer = nullptr; |
| 88 | |
| 89 | xcb_gcontext_t m_gc = 0; |
| 90 | xcb_drawable_t m_gc_drawable = 0; |
| 91 | |
| 92 | // When using shared memory these variables are used only for server-side scrolling. |
| 93 | // When not using shared memory, we maintain a server-side pixmap with the backing |
| 94 | // store as well as repainted content not yet flushed to the pixmap. We only flush |
| 95 | // the regions we need and only when these are marked dirty. This way we can just |
| 96 | // do a server-side copy on expose instead of sending the pixels every time |
| 97 | xcb_pixmap_t m_xcb_pixmap = 0; |
| 98 | QRegion m_pendingFlush; |
| 99 | |
| 100 | // This is the scrolled region which is stored in server-side pixmap |
| 101 | QRegion m_scrolledRegion; |
| 102 | |
| 103 | // When using shared memory this is the region currently shared with the server |
| 104 | QRegion m_dirtyShm; |
| 105 | |
| 106 | // When not using shared memory this is a temporary buffer which is uploaded |
| 107 | // as a pixmap region to server |
| 108 | QByteArray m_flushBuffer; |
| 109 | |
| 110 | bool m_hasAlpha = false; |
| 111 | bool m_clientSideScroll = false; |
| 112 | |
| 113 | const xcb_format_t *m_xcb_format = nullptr; |
| 114 | QImage::Format m_qimage_format = QImage::Format_Invalid; |
| 115 | }; |
| 116 | |
| 117 | class QXcbGraphicsBuffer : public QPlatformGraphicsBuffer |
| 118 | { |
| 119 | public: |
| 120 | QXcbGraphicsBuffer(QImage *image) |
| 121 | : QPlatformGraphicsBuffer(image->size(), QImage::toPixelFormat(format: image->format())) |
| 122 | , m_image(image) |
| 123 | { } |
| 124 | |
| 125 | bool doLock(AccessTypes access, const QRect &rect) override |
| 126 | { |
| 127 | Q_UNUSED(rect); |
| 128 | if (access & ~(QPlatformGraphicsBuffer::SWReadAccess | QPlatformGraphicsBuffer::SWWriteAccess)) |
| 129 | return false; |
| 130 | |
| 131 | m_access_lock |= access; |
| 132 | return true; |
| 133 | } |
| 134 | void doUnlock() override { m_access_lock = None; } |
| 135 | |
| 136 | const uchar *data() const override { return m_image->bits(); } |
| 137 | uchar *data() override { return m_image->bits(); } |
| 138 | int bytesPerLine() const override { return m_image->bytesPerLine(); } |
| 139 | |
| 140 | Origin origin() const override { return QPlatformGraphicsBuffer::OriginTopLeft; } |
| 141 | |
| 142 | private: |
| 143 | AccessTypes m_access_lock = QPlatformGraphicsBuffer::None; |
| 144 | QImage *m_image = nullptr; |
| 145 | }; |
| 146 | |
| 147 | static inline size_t imageDataSize(const xcb_image_t *image) |
| 148 | { |
| 149 | return static_cast<size_t>(image->stride) * image->height; |
| 150 | } |
| 151 | |
| 152 | QXcbBackingStoreImage::QXcbBackingStoreImage(QXcbBackingStore *backingStore, const QSize &size) |
| 153 | : QXcbObject(backingStore->connection()) |
| 154 | , m_backingStore(backingStore) |
| 155 | { |
| 156 | auto window = static_cast<QXcbWindow *>(m_backingStore->window()->handle()); |
| 157 | init(size, depth: window->depth(), format: window->imageFormat()); |
| 158 | } |
| 159 | |
| 160 | QXcbBackingStoreImage::QXcbBackingStoreImage(QXcbBackingStore *backingStore, const QSize &size, |
| 161 | uint depth, QImage::Format format) |
| 162 | : QXcbObject(backingStore->connection()) |
| 163 | , m_backingStore(backingStore) |
| 164 | { |
| 165 | init(size, depth, format); |
| 166 | } |
| 167 | |
| 168 | void QXcbBackingStoreImage::init(const QSize &size, uint depth, QImage::Format format) |
| 169 | { |
| 170 | m_xcb_format = connection()->formatForDepth(depth); |
| 171 | Q_ASSERT(m_xcb_format); |
| 172 | |
| 173 | m_qimage_format = format; |
| 174 | m_hasAlpha = QImage::toPixelFormat(format: m_qimage_format).alphaUsage() == QPixelFormat::UsesAlpha; |
| 175 | if (!m_hasAlpha) |
| 176 | m_qimage_format = qt_maybeDataCompatibleAlphaVersion(format: m_qimage_format); |
| 177 | |
| 178 | memset(s: &m_shm_info, c: 0, n: sizeof m_shm_info); |
| 179 | |
| 180 | resize(size); |
| 181 | } |
| 182 | |
| 183 | void QXcbBackingStoreImage::resize(const QSize &size) |
| 184 | { |
| 185 | destroy(destroyShm: false); |
| 186 | |
| 187 | auto byteOrder = QSysInfo::ByteOrder == QSysInfo::BigEndian ? XCB_IMAGE_ORDER_MSB_FIRST |
| 188 | : XCB_IMAGE_ORDER_LSB_FIRST; |
| 189 | m_xcb_image = xcb_image_create(width: size.width(), height: size.height(), |
| 190 | format: XCB_IMAGE_FORMAT_Z_PIXMAP, |
| 191 | xpad: m_xcb_format->scanline_pad, |
| 192 | depth: m_xcb_format->depth, |
| 193 | bpp: m_xcb_format->bits_per_pixel, |
| 194 | unit: 0, byte_order: byteOrder, |
| 195 | bit_order: XCB_IMAGE_ORDER_MSB_FIRST, |
| 196 | base: nullptr, bytes: ~0, data: nullptr); |
| 197 | |
| 198 | const size_t segmentSize = imageDataSize(image: m_xcb_image); |
| 199 | |
| 200 | if (connection()->hasShm()) { |
| 201 | if (segmentSize == 0) { |
| 202 | if (m_segmentSize > 0) { |
| 203 | destroyShmSegment(); |
| 204 | qCDebug(lcQpaXcb) << "[" << m_backingStore->window() |
| 205 | << "] destroyed SHM segment due to resize to" << size; |
| 206 | } |
| 207 | } else { |
| 208 | // Destroy shared memory segment if it is double (or more) of what we actually |
| 209 | // need with new window size. Or if the new size is bigger than what we currently |
| 210 | // have allocated. |
| 211 | if (m_shm_info.shmaddr && (m_segmentSize < segmentSize || m_segmentSize / 2 >= segmentSize)) |
| 212 | destroyShmSegment(); |
| 213 | if (!m_shm_info.shmaddr) { |
| 214 | qCDebug(lcQpaXcb) << "[" << m_backingStore->window() |
| 215 | << "] creating shared memory" << segmentSize << "bytes for" |
| 216 | << size << "depth" << m_xcb_format->depth << "bits" |
| 217 | << m_xcb_format->bits_per_pixel; |
| 218 | createShmSegment(segmentSize); |
| 219 | } |
| 220 | } |
| 221 | } |
| 222 | |
| 223 | if (segmentSize == 0) |
| 224 | return; |
| 225 | |
| 226 | m_xcb_image->data = m_shm_info.shmaddr ? m_shm_info.shmaddr : (uint8_t *)malloc(size: segmentSize); |
| 227 | m_qimage = QImage(static_cast<uchar *>(m_xcb_image->data), m_xcb_image->width, |
| 228 | m_xcb_image->height, m_xcb_image->stride, m_qimage_format); |
| 229 | m_graphics_buffer = new QXcbGraphicsBuffer(&m_qimage); |
| 230 | |
| 231 | m_xcb_pixmap = xcb_generate_id(c: xcb_connection()); |
| 232 | auto xcbScreen = static_cast<QXcbScreen *>(m_backingStore->window()->screen()->handle()); |
| 233 | xcb_create_pixmap(c: xcb_connection(), |
| 234 | depth: m_xcb_image->depth, |
| 235 | pid: m_xcb_pixmap, |
| 236 | drawable: xcbScreen->root(), |
| 237 | width: m_xcb_image->width, height: m_xcb_image->height); |
| 238 | } |
| 239 | |
| 240 | void QXcbBackingStoreImage::destroy(bool destroyShm) |
| 241 | { |
| 242 | if (m_xcb_image) { |
| 243 | if (m_xcb_image->data) { |
| 244 | if (m_shm_info.shmaddr) { |
| 245 | if (destroyShm) |
| 246 | destroyShmSegment(); |
| 247 | } else { |
| 248 | free(ptr: m_xcb_image->data); |
| 249 | } |
| 250 | } |
| 251 | xcb_image_destroy(image: m_xcb_image); |
| 252 | } |
| 253 | |
| 254 | if (m_gc) { |
| 255 | xcb_free_gc(c: xcb_connection(), gc: m_gc); |
| 256 | m_gc = 0; |
| 257 | } |
| 258 | m_gc_drawable = 0; |
| 259 | |
| 260 | delete m_graphics_buffer; |
| 261 | m_graphics_buffer = nullptr; |
| 262 | |
| 263 | if (m_xcb_pixmap) { |
| 264 | xcb_free_pixmap(c: xcb_connection(), pixmap: m_xcb_pixmap); |
| 265 | m_xcb_pixmap = 0; |
| 266 | } |
| 267 | |
| 268 | m_qimage = QImage(); |
| 269 | } |
| 270 | |
| 271 | void QXcbBackingStoreImage::flushScrolledRegion(bool clientSideScroll) |
| 272 | { |
| 273 | if (m_clientSideScroll == clientSideScroll) |
| 274 | return; |
| 275 | |
| 276 | m_clientSideScroll = clientSideScroll; |
| 277 | |
| 278 | if (m_scrolledRegion.isNull()) |
| 279 | return; |
| 280 | |
| 281 | if (hasShm() && m_dirtyShm.intersects(r: m_scrolledRegion)) { |
| 282 | connection()->sync(); |
| 283 | m_dirtyShm = QRegion(); |
| 284 | } |
| 285 | |
| 286 | if (m_clientSideScroll) { |
| 287 | // Copy scrolled image region from server-side pixmap to client-side memory |
| 288 | for (const QRect &rect : m_scrolledRegion) { |
| 289 | const int w = rect.width(); |
| 290 | const int h = rect.height(); |
| 291 | |
| 292 | auto reply = Q_XCB_REPLY_UNCHECKED(xcb_get_image, |
| 293 | xcb_connection(), |
| 294 | m_xcb_image->format, |
| 295 | m_xcb_pixmap, |
| 296 | rect.x(), rect.y(), |
| 297 | w, h, |
| 298 | ~0u); |
| 299 | |
| 300 | if (reply && reply->depth == m_xcb_image->depth) { |
| 301 | const QImage img(xcb_get_image_data(R: reply.get()), w, h, m_qimage.format()); |
| 302 | |
| 303 | QPainter p(&m_qimage); |
| 304 | p.setCompositionMode(QPainter::CompositionMode_Source); |
| 305 | p.drawImage(p: rect.topLeft(), image: img); |
| 306 | } |
| 307 | } |
| 308 | m_scrolledRegion = QRegion(); |
| 309 | } else { |
| 310 | // Copy scrolled image region from client-side memory to server-side pixmap |
| 311 | ensureGC(dst: m_xcb_pixmap); |
| 312 | if (hasShm()) |
| 313 | shmPutImage(drawable: m_xcb_pixmap, region: m_scrolledRegion); |
| 314 | else |
| 315 | flushPixmap(region: m_scrolledRegion, fullRegion: true); |
| 316 | } |
| 317 | } |
| 318 | |
| 319 | void QXcbBackingStoreImage::createShmSegment(size_t segmentSize) |
| 320 | { |
| 321 | Q_ASSERT(connection()->hasShm()); |
| 322 | Q_ASSERT(m_segmentSize == 0); |
| 323 | |
| 324 | #ifdef XCB_USE_SHM_FD |
| 325 | if (connection()->hasShmFd()) { |
| 326 | if (Q_UNLIKELY(segmentSize > std::numeric_limits<uint32_t>::max())) { |
| 327 | qCWarning(lcQpaXcb, "xcb_shm_create_segment() can't be called for size %zu, maximum" |
| 328 | "allowed size is %u" , segmentSize, std::numeric_limits<uint32_t>::max()); |
| 329 | return; |
| 330 | } |
| 331 | |
| 332 | const auto seg = xcb_generate_id(c: xcb_connection()); |
| 333 | auto reply = Q_XCB_REPLY(xcb_shm_create_segment, |
| 334 | xcb_connection(), seg, segmentSize, false); |
| 335 | if (!reply) { |
| 336 | qCWarning(lcQpaXcb, "xcb_shm_create_segment() failed for size %zu" , segmentSize); |
| 337 | return; |
| 338 | } |
| 339 | |
| 340 | int *fds = xcb_shm_create_segment_reply_fds(c: xcb_connection(), reply: reply.get()); |
| 341 | if (reply->nfd != 1) { |
| 342 | for (int i = 0; i < reply->nfd; i++) |
| 343 | close(fd: fds[i]); |
| 344 | |
| 345 | qCWarning(lcQpaXcb, "failed to get file descriptor for shm segment of size %zu" , segmentSize); |
| 346 | return; |
| 347 | } |
| 348 | |
| 349 | void *addr = mmap(addr: nullptr, len: segmentSize, PROT_READ|PROT_WRITE, MAP_SHARED, fd: fds[0], offset: 0); |
| 350 | if (addr == MAP_FAILED) { |
| 351 | qCWarning(lcQpaXcb, "failed to mmap segment from X server (%d: %s) for size %zu" , |
| 352 | errno, strerror(errno), segmentSize); |
| 353 | close(fd: fds[0]); |
| 354 | xcb_shm_detach(c: xcb_connection(), shmseg: seg); |
| 355 | return; |
| 356 | } |
| 357 | |
| 358 | close(fd: fds[0]); |
| 359 | m_shm_info.shmseg = seg; |
| 360 | m_shm_info.shmaddr = static_cast<quint8 *>(addr); |
| 361 | m_segmentSize = segmentSize; |
| 362 | } else |
| 363 | #endif |
| 364 | { |
| 365 | if (createSystemVShmSegment(c: xcb_connection(), segmentSize, shm_info: &m_shm_info)) |
| 366 | m_segmentSize = segmentSize; |
| 367 | } |
| 368 | } |
| 369 | |
| 370 | bool QXcbBackingStoreImage::createSystemVShmSegment(xcb_connection_t *c, size_t segmentSize, |
| 371 | xcb_shm_segment_info_t *shmInfo) |
| 372 | { |
| 373 | const int id = shmget(IPC_PRIVATE, size: segmentSize, IPC_CREAT | 0600); |
| 374 | if (id == -1) { |
| 375 | qCWarning(lcQpaXcb, "shmget() failed (%d: %s) for size %zu" , errno, strerror(errno), segmentSize); |
| 376 | return false; |
| 377 | } |
| 378 | |
| 379 | void *addr = shmat(shmid: id, shmaddr: nullptr, shmflg: 0); |
| 380 | if (addr == (void *)-1) { |
| 381 | qCWarning(lcQpaXcb, "shmat() failed (%d: %s) for id %d" , errno, strerror(errno), id); |
| 382 | return false; |
| 383 | } |
| 384 | |
| 385 | if (shmctl(shmid: id, IPC_RMID, buf: nullptr) == -1) |
| 386 | qCWarning(lcQpaXcb, "Error while marking the shared memory segment to be destroyed" ); |
| 387 | |
| 388 | const auto seg = xcb_generate_id(c); |
| 389 | auto cookie = xcb_shm_attach_checked(c, shmseg: seg, shmid: id, read_only: false); |
| 390 | auto *error = xcb_request_check(c, cookie); |
| 391 | if (error) { |
| 392 | qCWarning(lcQpaXcb(), "xcb_shm_attach() failed" ); |
| 393 | free(ptr: error); |
| 394 | if (shmdt(shmaddr: addr) == -1) |
| 395 | qCWarning(lcQpaXcb, "shmdt() failed (%d: %s) for %p" , errno, strerror(errno), addr); |
| 396 | return false; |
| 397 | } else if (!shmInfo) { // this was a test run, free the allocated test segment |
| 398 | xcb_shm_detach(c, shmseg: seg); |
| 399 | auto shmaddr = static_cast<quint8 *>(addr); |
| 400 | if (shmdt(shmaddr: shmaddr) == -1) |
| 401 | qCWarning(lcQpaXcb, "shmdt() failed (%d: %s) for %p" , errno, strerror(errno), shmaddr); |
| 402 | } |
| 403 | if (shmInfo) { |
| 404 | shmInfo->shmseg = seg; |
| 405 | shmInfo->shmid = id; // unused |
| 406 | shmInfo->shmaddr = static_cast<quint8 *>(addr); |
| 407 | } |
| 408 | return true; |
| 409 | } |
| 410 | |
| 411 | void QXcbBackingStoreImage::destroyShmSegment() |
| 412 | { |
| 413 | auto cookie = xcb_shm_detach_checked(c: xcb_connection(), shmseg: m_shm_info.shmseg); |
| 414 | xcb_generic_error_t *error = xcb_request_check(c: xcb_connection(), cookie); |
| 415 | if (error) |
| 416 | connection()->printXcbError(message: "xcb_shm_detach() failed with error" , error); |
| 417 | m_shm_info.shmseg = 0; |
| 418 | |
| 419 | #ifdef XCB_USE_SHM_FD |
| 420 | if (connection()->hasShmFd()) { |
| 421 | if (munmap(addr: m_shm_info.shmaddr, len: m_segmentSize) == -1) { |
| 422 | qCWarning(lcQpaXcb, "munmap() failed (%d: %s) for %p with size %zu" , |
| 423 | errno, strerror(errno), m_shm_info.shmaddr, m_segmentSize); |
| 424 | } |
| 425 | } else |
| 426 | #endif |
| 427 | { |
| 428 | if (shmdt(shmaddr: m_shm_info.shmaddr) == -1) { |
| 429 | qCWarning(lcQpaXcb, "shmdt() failed (%d: %s) for %p" , |
| 430 | errno, strerror(errno), m_shm_info.shmaddr); |
| 431 | } |
| 432 | m_shm_info.shmid = 0; // unused |
| 433 | } |
| 434 | m_shm_info.shmaddr = nullptr; |
| 435 | |
| 436 | m_segmentSize = 0; |
| 437 | } |
| 438 | |
| 439 | extern void qt_scrollRectInImage(QImage &img, const QRect &rect, const QPoint &offset); |
| 440 | |
| 441 | bool QXcbBackingStoreImage::scroll(const QRegion &area, int dx, int dy) |
| 442 | { |
| 443 | const QRect bounds(QPoint(), size()); |
| 444 | const QRegion scrollArea(area & bounds); |
| 445 | const QPoint delta(dx, dy); |
| 446 | const QRegion destinationRegion = scrollArea.translated(p: delta).intersected(r: bounds); |
| 447 | |
| 448 | if (m_clientSideScroll) { |
| 449 | if (m_qimage.isNull()) |
| 450 | return false; |
| 451 | |
| 452 | if (hasShm()) |
| 453 | preparePaint(region: destinationRegion); |
| 454 | |
| 455 | const QRect rect = scrollArea.boundingRect(); |
| 456 | qt_scrollRectInImage(img&: m_qimage, rect, offset: delta); |
| 457 | } else { |
| 458 | ensureGC(dst: m_xcb_pixmap); |
| 459 | |
| 460 | if (hasShm()) { |
| 461 | QRegion partialFlushRegion = m_pendingFlush.intersected(r: scrollArea); |
| 462 | shmPutImage(drawable: m_xcb_pixmap, region: partialFlushRegion); |
| 463 | m_pendingFlush -= partialFlushRegion; |
| 464 | } else { |
| 465 | flushPixmap(region: scrollArea); |
| 466 | } |
| 467 | |
| 468 | for (const QRect &src : scrollArea) { |
| 469 | const QRect dst = src.translated(p: delta).intersected(other: bounds); |
| 470 | xcb_copy_area(c: xcb_connection(), |
| 471 | src_drawable: m_xcb_pixmap, |
| 472 | dst_drawable: m_xcb_pixmap, |
| 473 | gc: m_gc, |
| 474 | src_x: src.x(), src_y: src.y(), |
| 475 | dst_x: dst.x(), dst_y: dst.y(), |
| 476 | width: dst.width(), height: dst.height()); |
| 477 | } |
| 478 | |
| 479 | if (hasShm()) |
| 480 | m_pendingFlush -= destinationRegion; |
| 481 | } |
| 482 | |
| 483 | m_scrolledRegion |= destinationRegion; |
| 484 | |
| 485 | return true; |
| 486 | } |
| 487 | |
| 488 | void QXcbBackingStoreImage::ensureGC(xcb_drawable_t dst) |
| 489 | { |
| 490 | if (m_gc_drawable != dst) { |
| 491 | if (m_gc) |
| 492 | xcb_free_gc(c: xcb_connection(), gc: m_gc); |
| 493 | |
| 494 | static const uint32_t mask = XCB_GC_GRAPHICS_EXPOSURES; |
| 495 | static const uint32_t values[] = { 0 }; |
| 496 | |
| 497 | m_gc = xcb_generate_id(c: xcb_connection()); |
| 498 | xcb_create_gc(c: xcb_connection(), cid: m_gc, drawable: dst, value_mask: mask, value_list: values); |
| 499 | |
| 500 | m_gc_drawable = dst; |
| 501 | } |
| 502 | } |
| 503 | |
| 504 | static inline void copy_unswapped(char *dst, int dstBytesPerLine, const QImage &img, const QRect &rect) |
| 505 | { |
| 506 | const uchar *srcData = img.constBits(); |
| 507 | const qsizetype srcBytesPerLine = img.bytesPerLine(); |
| 508 | |
| 509 | const int leftOffset = rect.left() * img.depth() >> 3; |
| 510 | const int bottom = rect.bottom() + 1; |
| 511 | |
| 512 | for (int yy = rect.top(); yy < bottom; ++yy) { |
| 513 | const uchar *src = srcData + yy * srcBytesPerLine + leftOffset; |
| 514 | ::memmove(dest: dst, src: src, n: dstBytesPerLine); |
| 515 | dst += dstBytesPerLine; |
| 516 | } |
| 517 | } |
| 518 | |
| 519 | template <class Pixel> |
| 520 | static inline void copy_swapped(char *dst, const int dstStride, const QImage &img, const QRect &rect) |
| 521 | { |
| 522 | const uchar *srcData = img.constBits(); |
| 523 | const qsizetype srcBytesPerLine = img.bytesPerLine(); |
| 524 | |
| 525 | const int left = rect.left(); |
| 526 | const int width = rect.width(); |
| 527 | const int bottom = rect.bottom() + 1; |
| 528 | |
| 529 | for (int yy = rect.top(); yy < bottom; ++yy) { |
| 530 | Pixel *dstPixels = reinterpret_cast<Pixel *>(dst); |
| 531 | const Pixel *srcPixels = reinterpret_cast<const Pixel *>(srcData + yy * srcBytesPerLine) + left; |
| 532 | |
| 533 | for (int i = 0; i < width; ++i) |
| 534 | dstPixels[i] = qbswap<Pixel>(*srcPixels++); |
| 535 | |
| 536 | dst += dstStride; |
| 537 | } |
| 538 | } |
| 539 | |
| 540 | static QImage native_sub_image(QByteArray *buffer, const int dstStride, const QImage &src, const QRect &rect, bool swap) |
| 541 | { |
| 542 | if (!swap && src.rect() == rect && src.bytesPerLine() == dstStride) |
| 543 | return src; |
| 544 | |
| 545 | buffer->resize(size: rect.height() * dstStride); |
| 546 | |
| 547 | if (swap) { |
| 548 | switch (src.depth()) { |
| 549 | case 32: |
| 550 | copy_swapped<quint32>(dst: buffer->data(), dstStride, img: src, rect); |
| 551 | break; |
| 552 | case 16: |
| 553 | copy_swapped<quint16>(dst: buffer->data(), dstStride, img: src, rect); |
| 554 | break; |
| 555 | } |
| 556 | } else { |
| 557 | copy_unswapped(dst: buffer->data(), dstBytesPerLine: dstStride, img: src, rect); |
| 558 | } |
| 559 | |
| 560 | return QImage(reinterpret_cast<const uchar *>(buffer->constData()), rect.width(), rect.height(), dstStride, src.format()); |
| 561 | } |
| 562 | |
| 563 | static inline quint32 round_up_scanline(quint32 base, quint32 pad) |
| 564 | { |
| 565 | return (base + pad - 1) & -pad; |
| 566 | } |
| 567 | |
| 568 | void QXcbBackingStoreImage::shmPutImage(xcb_drawable_t drawable, const QRegion ®ion, const QPoint &offset) |
| 569 | { |
| 570 | for (const QRect &rect : region) { |
| 571 | const QPoint source = rect.translated(p: offset).topLeft(); |
| 572 | xcb_shm_put_image(c: xcb_connection(), |
| 573 | drawable, |
| 574 | gc: m_gc, |
| 575 | total_width: m_xcb_image->width, |
| 576 | total_height: m_xcb_image->height, |
| 577 | src_x: source.x(), src_y: source.y(), |
| 578 | src_width: rect.width(), src_height: rect.height(), |
| 579 | dst_x: rect.x(), dst_y: rect.y(), |
| 580 | depth: m_xcb_image->depth, |
| 581 | format: m_xcb_image->format, |
| 582 | send_event: 0, // send event? |
| 583 | shmseg: m_shm_info.shmseg, |
| 584 | offset: m_xcb_image->data - m_shm_info.shmaddr); |
| 585 | } |
| 586 | m_dirtyShm |= region.translated(p: offset); |
| 587 | } |
| 588 | |
| 589 | void QXcbBackingStoreImage::flushPixmap(const QRegion ®ion, bool fullRegion) |
| 590 | { |
| 591 | if (!fullRegion) { |
| 592 | auto actualRegion = m_pendingFlush.intersected(r: region); |
| 593 | m_pendingFlush -= region; |
| 594 | flushPixmap(region: actualRegion, fullRegion: true); |
| 595 | return; |
| 596 | } |
| 597 | |
| 598 | xcb_image_t xcb_subimage; |
| 599 | memset(s: &xcb_subimage, c: 0, n: sizeof(xcb_image_t)); |
| 600 | |
| 601 | xcb_subimage.format = m_xcb_image->format; |
| 602 | xcb_subimage.scanline_pad = m_xcb_image->scanline_pad; |
| 603 | xcb_subimage.depth = m_xcb_image->depth; |
| 604 | xcb_subimage.bpp = m_xcb_image->bpp; |
| 605 | xcb_subimage.unit = m_xcb_image->unit; |
| 606 | xcb_subimage.plane_mask = m_xcb_image->plane_mask; |
| 607 | xcb_subimage.byte_order = (xcb_image_order_t) connection()->setup()->image_byte_order; |
| 608 | xcb_subimage.bit_order = m_xcb_image->bit_order; |
| 609 | |
| 610 | const bool needsByteSwap = xcb_subimage.byte_order != m_xcb_image->byte_order; |
| 611 | // Ensure that we don't send more than maxPutImageRequestDataBytes per request. |
| 612 | const auto maxPutImageRequestDataBytes = connection()->maxRequestDataBytes(requestSize: sizeof(xcb_put_image_request_t)); |
| 613 | |
| 614 | for (const QRect &rect : region) { |
| 615 | const quint32 stride = round_up_scanline(base: rect.width() * m_qimage.depth(), pad: xcb_subimage.scanline_pad) >> 3; |
| 616 | const int rows_per_put = maxPutImageRequestDataBytes / stride; |
| 617 | |
| 618 | // This assert could trigger if a single row has more pixels than fit in |
| 619 | // a single PutImage request. In the absence of the BIG-REQUESTS extension |
| 620 | // the theoretical maximum lengths of maxPutImageRequestDataBytes can be |
| 621 | // roughly 256kB. |
| 622 | Q_ASSERT(rows_per_put > 0); |
| 623 | |
| 624 | // If we upload the whole image in a single chunk, the result might be |
| 625 | // larger than the server's maximum request size and stuff breaks. |
| 626 | // To work around that, we upload the image in chunks where each chunk |
| 627 | // is small enough for a single request. |
| 628 | const int x = rect.x(); |
| 629 | int y = rect.y(); |
| 630 | const int width = rect.width(); |
| 631 | int height = rect.height(); |
| 632 | |
| 633 | while (height > 0) { |
| 634 | const int rows = std::min(a: height, b: rows_per_put); |
| 635 | const QRect subRect(x, y, width, rows); |
| 636 | const QImage subImage = native_sub_image(buffer: &m_flushBuffer, dstStride: stride, src: m_qimage, rect: subRect, swap: needsByteSwap); |
| 637 | |
| 638 | Q_ASSERT(static_cast<size_t>(subImage.sizeInBytes()) <= maxPutImageRequestDataBytes); |
| 639 | |
| 640 | xcb_subimage.width = width; |
| 641 | xcb_subimage.height = rows; |
| 642 | xcb_subimage.data = const_cast<uint8_t *>(subImage.constBits()); |
| 643 | xcb_image_annotate(image: &xcb_subimage); |
| 644 | |
| 645 | xcb_image_put(conn: xcb_connection(), |
| 646 | draw: m_xcb_pixmap, |
| 647 | gc: m_gc, |
| 648 | image: &xcb_subimage, |
| 649 | x, |
| 650 | y, |
| 651 | left_pad: 0); |
| 652 | |
| 653 | y += rows; |
| 654 | height -= rows; |
| 655 | } |
| 656 | } |
| 657 | } |
| 658 | |
| 659 | void QXcbBackingStoreImage::setClip(const QRegion ®ion) |
| 660 | { |
| 661 | if (region.isEmpty()) { |
| 662 | static const uint32_t mask = XCB_GC_CLIP_MASK; |
| 663 | static const uint32_t values[] = { XCB_NONE }; |
| 664 | xcb_change_gc(c: xcb_connection(), gc: m_gc, value_mask: mask, value_list: values); |
| 665 | } else { |
| 666 | const auto xcb_rects = qRegionToXcbRectangleList(region); |
| 667 | xcb_set_clip_rectangles(c: xcb_connection(), |
| 668 | ordering: XCB_CLIP_ORDERING_YX_BANDED, |
| 669 | gc: m_gc, |
| 670 | clip_x_origin: 0, clip_y_origin: 0, |
| 671 | rectangles_len: xcb_rects.size(), rectangles: xcb_rects.constData()); |
| 672 | } |
| 673 | } |
| 674 | |
| 675 | void QXcbBackingStoreImage::put(xcb_drawable_t dst, const QRegion ®ion, const QPoint &offset) |
| 676 | { |
| 677 | Q_ASSERT(!m_clientSideScroll); |
| 678 | |
| 679 | ensureGC(dst); |
| 680 | |
| 681 | if (hasShm()) { |
| 682 | setClip(region); // Clip in window local coordinates |
| 683 | |
| 684 | // Copy scrolled area on server-side from pixmap to window |
| 685 | const QRegion scrolledRegion = m_scrolledRegion.translated(p: -offset); |
| 686 | for (const QRect &rect : scrolledRegion) { |
| 687 | const QPoint source = rect.translated(p: offset).topLeft(); |
| 688 | xcb_copy_area(c: xcb_connection(), |
| 689 | src_drawable: m_xcb_pixmap, |
| 690 | dst_drawable: dst, |
| 691 | gc: m_gc, |
| 692 | src_x: source.x(), src_y: source.y(), |
| 693 | dst_x: rect.x(), dst_y: rect.y(), |
| 694 | width: rect.width(), height: rect.height()); |
| 695 | } |
| 696 | |
| 697 | // Copy non-scrolled image from client-side memory to server-side window |
| 698 | const QRegion notScrolledArea = region - scrolledRegion; |
| 699 | shmPutImage(drawable: dst, region: notScrolledArea, offset); |
| 700 | } else { |
| 701 | const QRect bounds = region.boundingRect(); |
| 702 | const QPoint target = bounds.topLeft(); |
| 703 | const QRect source = bounds.translated(p: offset); |
| 704 | |
| 705 | // First clip in backingstore-local coordinates, and upload |
| 706 | // the changed parts of the backingstore to the server. |
| 707 | setClip(source); |
| 708 | flushPixmap(region: source); |
| 709 | |
| 710 | // Then clip in window local coordinates, and copy the updated |
| 711 | // parts of the backingstore image server-side to the window. |
| 712 | setClip(region); |
| 713 | xcb_copy_area(c: xcb_connection(), |
| 714 | src_drawable: m_xcb_pixmap, |
| 715 | dst_drawable: dst, |
| 716 | gc: m_gc, |
| 717 | src_x: source.x(), src_y: source.y(), |
| 718 | dst_x: target.x(), dst_y: target.y(), |
| 719 | width: source.width(), height: source.height()); |
| 720 | } |
| 721 | |
| 722 | setClip(QRegion()); |
| 723 | } |
| 724 | |
| 725 | void QXcbBackingStoreImage::preparePaint(const QRegion ®ion) |
| 726 | { |
| 727 | if (hasShm()) { |
| 728 | // to prevent X from reading from the image region while we're writing to it |
| 729 | if (m_dirtyShm.intersects(r: region)) { |
| 730 | connection()->sync(); |
| 731 | m_dirtyShm = QRegion(); |
| 732 | } |
| 733 | } |
| 734 | m_scrolledRegion -= region; |
| 735 | m_pendingFlush |= region; |
| 736 | } |
| 737 | |
| 738 | bool QXcbBackingStore::createSystemVShmSegment(xcb_connection_t *c, size_t segmentSize, void *shmInfo) |
| 739 | { |
| 740 | auto info = reinterpret_cast<xcb_shm_segment_info_t *>(shmInfo); |
| 741 | return QXcbBackingStoreImage::createSystemVShmSegment(c, segmentSize, shmInfo: info); |
| 742 | } |
| 743 | |
| 744 | QXcbBackingStore::QXcbBackingStore(QWindow *window) |
| 745 | : QPlatformBackingStore(window) |
| 746 | { |
| 747 | QXcbScreen *screen = static_cast<QXcbScreen *>(window->screen()->handle()); |
| 748 | setConnection(screen->connection()); |
| 749 | } |
| 750 | |
| 751 | QXcbBackingStore::~QXcbBackingStore() |
| 752 | { |
| 753 | delete m_image; |
| 754 | } |
| 755 | |
| 756 | QPaintDevice *QXcbBackingStore::paintDevice() |
| 757 | { |
| 758 | if (!m_image) |
| 759 | return nullptr; |
| 760 | return m_rgbImage.isNull() ? m_image->image() : &m_rgbImage; |
| 761 | } |
| 762 | |
| 763 | void QXcbBackingStore::beginPaint(const QRegion ®ion) |
| 764 | { |
| 765 | if (!m_image) |
| 766 | return; |
| 767 | |
| 768 | m_paintRegions.push(t: region); |
| 769 | m_image->preparePaint(region); |
| 770 | |
| 771 | if (m_image->hasAlpha()) { |
| 772 | QPainter p(paintDevice()); |
| 773 | p.setCompositionMode(QPainter::CompositionMode_Source); |
| 774 | const QColor blank = Qt::transparent; |
| 775 | for (const QRect &rect : region) |
| 776 | p.fillRect(rect, color: blank); |
| 777 | } |
| 778 | } |
| 779 | |
| 780 | void QXcbBackingStore::endPaint() |
| 781 | { |
| 782 | if (Q_UNLIKELY(m_paintRegions.isEmpty())) { |
| 783 | qCWarning(lcQpaXcb, "%s: paint regions empty!" , Q_FUNC_INFO); |
| 784 | return; |
| 785 | } |
| 786 | |
| 787 | const QRegion region = m_paintRegions.pop(); |
| 788 | m_image->preparePaint(region); |
| 789 | |
| 790 | QXcbWindow *platformWindow = static_cast<QXcbWindow *>(window()->handle()); |
| 791 | if (!platformWindow || !platformWindow->imageNeedsRgbSwap()) |
| 792 | return; |
| 793 | |
| 794 | // Slow path: the paint device was m_rgbImage. Now copy with swapping red |
| 795 | // and blue into m_image. |
| 796 | auto it = region.begin(); |
| 797 | const auto end = region.end(); |
| 798 | if (it == end) |
| 799 | return; |
| 800 | QPainter p(m_image->image()); |
| 801 | while (it != end) { |
| 802 | const QRect rect = *(it++); |
| 803 | p.drawImage(p: rect.topLeft(), image: m_rgbImage.copy(rect).rgbSwapped()); |
| 804 | } |
| 805 | } |
| 806 | |
| 807 | QImage QXcbBackingStore::toImage() const |
| 808 | { |
| 809 | // If the backingstore is rgbSwapped, return the internal image type here. |
| 810 | if (!m_rgbImage.isNull()) |
| 811 | return m_rgbImage; |
| 812 | |
| 813 | if (!m_image || !m_image->image()) |
| 814 | return QImage(); |
| 815 | |
| 816 | m_image->flushScrolledRegion(clientSideScroll: true); |
| 817 | |
| 818 | QImage image = *m_image->image(); |
| 819 | |
| 820 | // Return an image that does not share QImageData with the original image, |
| 821 | // even if they both point to the same data of the m_xcb_image, otherwise |
| 822 | // painting to m_qimage would detach it from the m_xcb_image data. |
| 823 | return QImage(image.constBits(), image.width(), image.height(), image.format()); |
| 824 | } |
| 825 | |
| 826 | QPlatformGraphicsBuffer *QXcbBackingStore::graphicsBuffer() const |
| 827 | { |
| 828 | return m_image ? m_image->graphicsBuffer() : nullptr; |
| 829 | } |
| 830 | |
| 831 | void QXcbBackingStore::flush(QWindow *window, const QRegion ®ion, const QPoint &offset) |
| 832 | { |
| 833 | if (!m_image || m_image->size().isEmpty()) |
| 834 | return; |
| 835 | |
| 836 | m_image->flushScrolledRegion(clientSideScroll: false); |
| 837 | |
| 838 | QSize imageSize = m_image->size(); |
| 839 | |
| 840 | QRegion clipped = region; |
| 841 | clipped &= QRect(QPoint(), QHighDpi::toNativePixels(value: window->size(), context: window)); |
| 842 | clipped &= QRect(0, 0, imageSize.width(), imageSize.height()).translated(p: -offset); |
| 843 | |
| 844 | QRect bounds = clipped.boundingRect(); |
| 845 | |
| 846 | if (bounds.isNull()) |
| 847 | return; |
| 848 | |
| 849 | QXcbWindow *platformWindow = static_cast<QXcbWindow *>(window->handle()); |
| 850 | if (!platformWindow) { |
| 851 | qCWarning(lcQpaXcb, "%s QWindow has no platform window, see QTBUG-32681" , Q_FUNC_INFO); |
| 852 | return; |
| 853 | } |
| 854 | |
| 855 | render(window: platformWindow->xcb_window(), region: clipped, offset); |
| 856 | |
| 857 | if (platformWindow->needsSync()) |
| 858 | platformWindow->updateSyncRequestCounter(); |
| 859 | else |
| 860 | xcb_flush(c: xcb_connection()); |
| 861 | } |
| 862 | |
| 863 | void QXcbBackingStore::render(xcb_window_t window, const QRegion ®ion, const QPoint &offset) |
| 864 | { |
| 865 | m_image->put(dst: window, region, offset); |
| 866 | } |
| 867 | |
| 868 | QPlatformBackingStore::FlushResult QXcbBackingStore::rhiFlush(QWindow *window, |
| 869 | qreal sourceDevicePixelRatio, |
| 870 | const QRegion ®ion, |
| 871 | const QPoint &offset, |
| 872 | QPlatformTextureList *textures, |
| 873 | bool translucentBackground) |
| 874 | { |
| 875 | if (!m_image || m_image->size().isEmpty()) |
| 876 | return FlushFailed; |
| 877 | |
| 878 | m_image->flushScrolledRegion(clientSideScroll: true); |
| 879 | |
| 880 | auto result = QPlatformBackingStore::rhiFlush(window, sourceDevicePixelRatio, region, offset, |
| 881 | textures, translucentBackground); |
| 882 | if (result != FlushSuccess) |
| 883 | return result; |
| 884 | QXcbWindow *platformWindow = static_cast<QXcbWindow *>(window->handle()); |
| 885 | if (platformWindow->needsSync()) { |
| 886 | platformWindow->updateSyncRequestCounter(); |
| 887 | } else { |
| 888 | xcb_flush(c: xcb_connection()); |
| 889 | } |
| 890 | |
| 891 | return FlushSuccess; |
| 892 | } |
| 893 | |
| 894 | void QXcbBackingStore::resize(const QSize &size, const QRegion &) |
| 895 | { |
| 896 | if (m_image && size == m_image->size()) |
| 897 | return; |
| 898 | |
| 899 | QPlatformWindow *pw = window()->handle(); |
| 900 | if (!pw) { |
| 901 | window()->create(); |
| 902 | pw = window()->handle(); |
| 903 | } |
| 904 | QXcbWindow* win = static_cast<QXcbWindow *>(pw); |
| 905 | |
| 906 | recreateImage(win, size); |
| 907 | } |
| 908 | |
| 909 | void QXcbBackingStore::recreateImage(QXcbWindow *win, const QSize &size) |
| 910 | { |
| 911 | if (m_image) |
| 912 | m_image->resize(size); |
| 913 | else |
| 914 | m_image = new QXcbBackingStoreImage(this, size); |
| 915 | |
| 916 | // Slow path for bgr888 VNC: Create an additional image, paint into that and |
| 917 | // swap R and B while copying to m_image after each paint. |
| 918 | if (win->imageNeedsRgbSwap()) { |
| 919 | m_rgbImage = QImage(size, win->imageFormat()); |
| 920 | } |
| 921 | } |
| 922 | |
| 923 | bool QXcbBackingStore::scroll(const QRegion &area, int dx, int dy) |
| 924 | { |
| 925 | if (m_image) |
| 926 | return m_image->scroll(area, dx, dy); |
| 927 | |
| 928 | return false; |
| 929 | } |
| 930 | |
| 931 | QXcbSystemTrayBackingStore::QXcbSystemTrayBackingStore(QWindow *window) |
| 932 | : QXcbBackingStore(window) |
| 933 | { |
| 934 | // We need three different behaviors depending on whether the X11 visual |
| 935 | // for the system tray supports an alpha channel, i.e. is 32 bits, and |
| 936 | // whether XRender can be used: |
| 937 | // 1) if the visual has an alpha channel, then render the window's buffer |
| 938 | // directly to the X11 window as usual |
| 939 | // 2) else if XRender can be used, then render the window's buffer to Pixmap, |
| 940 | // then render Pixmap's contents to the cleared X11 window with |
| 941 | // xcb_render_composite() |
| 942 | // 3) else grab the X11 window's content and paint it first each time as a |
| 943 | // background before rendering the window's buffer to the X11 window |
| 944 | |
| 945 | auto *platformWindow = static_cast<QXcbWindow *>(window->handle()); |
| 946 | quint8 depth = connection()->primaryScreen()->depthOfVisual(visualid: platformWindow->visualId()); |
| 947 | |
| 948 | if (depth != 32) { |
| 949 | platformWindow->setParentRelativeBackPixmap(); |
| 950 | initXRenderMode(); |
| 951 | m_useGrabbedBackgound = !m_usingXRenderMode; |
| 952 | } |
| 953 | } |
| 954 | |
| 955 | QXcbSystemTrayBackingStore::~QXcbSystemTrayBackingStore() |
| 956 | { |
| 957 | if (m_xrenderPicture) { |
| 958 | xcb_render_free_picture(c: xcb_connection(), picture: m_xrenderPicture); |
| 959 | m_xrenderPicture = XCB_NONE; |
| 960 | } |
| 961 | if (m_xrenderPixmap) { |
| 962 | xcb_free_pixmap(c: xcb_connection(), pixmap: m_xrenderPixmap); |
| 963 | m_xrenderPixmap = XCB_NONE; |
| 964 | } |
| 965 | if (m_windowPicture) { |
| 966 | xcb_render_free_picture(c: xcb_connection(), picture: m_windowPicture); |
| 967 | m_windowPicture = XCB_NONE; |
| 968 | } |
| 969 | } |
| 970 | |
| 971 | void QXcbSystemTrayBackingStore::beginPaint(const QRegion ®ion) |
| 972 | { |
| 973 | QXcbBackingStore::beginPaint(region); |
| 974 | |
| 975 | if (m_useGrabbedBackgound) { |
| 976 | QPainter p(paintDevice()); |
| 977 | p.setCompositionMode(QPainter::CompositionMode_Source); |
| 978 | for (const QRect &rect: region) |
| 979 | p.drawPixmap(targetRect: rect, pixmap: m_grabbedBackground, sourceRect: rect); |
| 980 | } |
| 981 | } |
| 982 | |
| 983 | void QXcbSystemTrayBackingStore::render(xcb_window_t window, const QRegion ®ion, const QPoint &offset) |
| 984 | { |
| 985 | if (!m_usingXRenderMode) { |
| 986 | QXcbBackingStore::render(window, region, offset); |
| 987 | return; |
| 988 | } |
| 989 | |
| 990 | m_image->put(dst: m_xrenderPixmap, region, offset); |
| 991 | const QRect bounds = region.boundingRect(); |
| 992 | const QPoint target = bounds.topLeft(); |
| 993 | const QRect source = bounds.translated(p: offset); |
| 994 | xcb_clear_area(c: xcb_connection(), exposures: false, window, |
| 995 | x: target.x(), y: target.y(), width: source.width(), height: source.height()); |
| 996 | xcb_render_composite(c: xcb_connection(), op: XCB_RENDER_PICT_OP_OVER, |
| 997 | src: m_xrenderPicture, mask: 0, dst: m_windowPicture, |
| 998 | src_x: target.x(), src_y: target.y(), mask_x: 0, mask_y: 0, dst_x: target.x(), dst_y: target.y(), |
| 999 | width: source.width(), height: source.height()); |
| 1000 | } |
| 1001 | |
| 1002 | void QXcbSystemTrayBackingStore::recreateImage(QXcbWindow *win, const QSize &size) |
| 1003 | { |
| 1004 | if (!m_usingXRenderMode) { |
| 1005 | QXcbBackingStore::recreateImage(win, size); |
| 1006 | |
| 1007 | if (m_useGrabbedBackgound) { |
| 1008 | xcb_clear_area(c: xcb_connection(), exposures: false, window: win->xcb_window(), |
| 1009 | x: 0, y: 0, width: size.width(), height: size.height()); |
| 1010 | m_grabbedBackground = win->xcbScreen()->grabWindow(window: win->winId(), x: 0, y: 0, |
| 1011 | width: size.width(), height: size.height()); |
| 1012 | } |
| 1013 | return; |
| 1014 | } |
| 1015 | |
| 1016 | if (m_xrenderPicture) { |
| 1017 | xcb_render_free_picture(c: xcb_connection(), picture: m_xrenderPicture); |
| 1018 | m_xrenderPicture = XCB_NONE; |
| 1019 | } |
| 1020 | if (m_xrenderPixmap) { |
| 1021 | xcb_free_pixmap(c: xcb_connection(), pixmap: m_xrenderPixmap); |
| 1022 | m_xrenderPixmap = XCB_NONE; |
| 1023 | } |
| 1024 | |
| 1025 | QXcbScreen *screen = win->xcbScreen(); |
| 1026 | |
| 1027 | m_xrenderPixmap = xcb_generate_id(c: xcb_connection()); |
| 1028 | xcb_create_pixmap(c: xcb_connection(), depth: 32, pid: m_xrenderPixmap, drawable: screen->root(), width: size.width(), height: size.height()); |
| 1029 | |
| 1030 | m_xrenderPicture = xcb_generate_id(c: xcb_connection()); |
| 1031 | xcb_render_create_picture(c: xcb_connection(), pid: m_xrenderPicture, drawable: m_xrenderPixmap, format: m_xrenderPictFormat, value_mask: 0, value_list: nullptr); |
| 1032 | |
| 1033 | // XRender expects premultiplied alpha |
| 1034 | if (m_image) |
| 1035 | m_image->resize(size); |
| 1036 | else |
| 1037 | m_image = new QXcbBackingStoreImage(this, size, 32, QImage::Format_ARGB32_Premultiplied); |
| 1038 | } |
| 1039 | |
| 1040 | void QXcbSystemTrayBackingStore::initXRenderMode() |
| 1041 | { |
| 1042 | if (!connection()->hasXRender()) |
| 1043 | return; |
| 1044 | |
| 1045 | xcb_connection_t *conn = xcb_connection(); |
| 1046 | auto formatsReply = Q_XCB_REPLY(xcb_render_query_pict_formats, conn); |
| 1047 | |
| 1048 | if (!formatsReply) { |
| 1049 | qWarning(msg: "QXcbSystemTrayBackingStore: xcb_render_query_pict_formats() failed" ); |
| 1050 | return; |
| 1051 | } |
| 1052 | |
| 1053 | xcb_render_pictforminfo_t *fmt = xcb_render_util_find_standard_format(formats: formatsReply.get(), |
| 1054 | format: XCB_PICT_STANDARD_ARGB_32); |
| 1055 | if (!fmt) { |
| 1056 | qWarning(msg: "QXcbSystemTrayBackingStore: Failed to find format PICT_STANDARD_ARGB_32" ); |
| 1057 | return; |
| 1058 | } |
| 1059 | |
| 1060 | m_xrenderPictFormat = fmt->id; |
| 1061 | |
| 1062 | auto *platformWindow = static_cast<QXcbWindow *>(window()->handle()); |
| 1063 | xcb_render_pictvisual_t *vfmt = xcb_render_util_find_visual_format(formats: formatsReply.get(), visual: platformWindow->visualId()); |
| 1064 | |
| 1065 | if (!vfmt) { |
| 1066 | qWarning(msg: "QXcbSystemTrayBackingStore: Failed to find format for visual %x" , platformWindow->visualId()); |
| 1067 | return; |
| 1068 | } |
| 1069 | |
| 1070 | m_windowPicture = xcb_generate_id(c: conn); |
| 1071 | xcb_void_cookie_t cookie = |
| 1072 | xcb_render_create_picture_checked(c: conn, pid: m_windowPicture, drawable: platformWindow->xcb_window(), format: vfmt->format, value_mask: 0, value_list: nullptr); |
| 1073 | xcb_generic_error_t *error = xcb_request_check(c: conn, cookie); |
| 1074 | if (error) { |
| 1075 | qWarning(msg: "QXcbSystemTrayBackingStore: Failed to create Picture with format %x for window %x, error code %d" , |
| 1076 | vfmt->format, platformWindow->xcb_window(), error->error_code); |
| 1077 | free(ptr: error); |
| 1078 | return; |
| 1079 | } |
| 1080 | |
| 1081 | m_usingXRenderMode = true; |
| 1082 | } |
| 1083 | |
| 1084 | QT_END_NAMESPACE |
| 1085 | |