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_maybeAlphaVersionWithSameDepth(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 | |