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