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