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
73QT_BEGIN_NAMESPACE
74
75class QXcbBackingStore;
76
77class QXcbBackingStoreImage : public QXcbObject
78{
79public:
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 &region, const QPoint &offset);
99 void preparePaint(const QRegion &region);
100
101 static bool createSystemVShmSegment(xcb_connection_t *c, size_t segmentSize = 1,
102 xcb_shm_segment_info_t *shm_info = nullptr);
103
104private:
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 &region, const QPoint &offset = QPoint());
113 void flushPixmap(const QRegion &region, bool fullRegion = false);
114 void setClip(const QRegion &region);
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
153class QXcbGraphicsBuffer : public QPlatformGraphicsBuffer
154{
155public:
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
178private:
179 AccessTypes m_access_lock = QPlatformGraphicsBuffer::None;
180 QImage *m_image = nullptr;
181};
182
183static inline size_t imageDataSize(const xcb_image_t *image)
184{
185 return static_cast<size_t>(image->stride) * image->height;
186}
187
188QXcbBackingStoreImage::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
196QXcbBackingStoreImage::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
204void 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
219void 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
276void 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
307void 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
355void 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
406bool 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
447void 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
475extern void qt_scrollRectInImage(QImage &img, const QRect &rect, const QPoint &offset);
476
477bool 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
521void 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
537static 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
552template <class Pixel>
553static 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
573static 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
596static inline quint32 round_up_scanline(quint32 base, quint32 pad)
597{
598 return (base + pad - 1) & -pad;
599}
600
601void QXcbBackingStoreImage::shmPutImage(xcb_drawable_t drawable, const QRegion &region, 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
622void QXcbBackingStoreImage::flushPixmap(const QRegion &region, 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
692void QXcbBackingStoreImage::setClip(const QRegion &region)
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
708void QXcbBackingStoreImage::put(xcb_drawable_t dst, const QRegion &region, 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
758void QXcbBackingStoreImage::preparePaint(const QRegion &region)
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
771bool 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
777QXcbBackingStore::QXcbBackingStore(QWindow *window)
778 : QPlatformBackingStore(window)
779{
780 QXcbScreen *screen = static_cast<QXcbScreen *>(window->screen()->handle());
781 setConnection(screen->connection());
782}
783
784QXcbBackingStore::~QXcbBackingStore()
785{
786 delete m_image;
787}
788
789QPaintDevice *QXcbBackingStore::paintDevice()
790{
791 if (!m_image)
792 return nullptr;
793 return m_rgbImage.isNull() ? m_image->image() : &m_rgbImage;
794}
795
796void QXcbBackingStore::beginPaint(const QRegion &region)
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
813void 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
840QImage 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
848QPlatformGraphicsBuffer *QXcbBackingStore::graphicsBuffer() const
849{
850 return m_image ? m_image->graphicsBuffer() : nullptr;
851}
852
853void QXcbBackingStore::flush(QWindow *window, const QRegion &region, 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
885void QXcbBackingStore::render(xcb_window_t window, const QRegion &region, const QPoint &offset)
886{
887 m_image->put(dst: window, region, offset);
888}
889
890#ifndef QT_NO_OPENGL
891void QXcbBackingStore::composeAndFlush(QWindow *window, const QRegion &region, 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
911void 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
926void 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
940bool 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
948QXcbSystemTrayBackingStore::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
972QXcbSystemTrayBackingStore::~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
988void QXcbSystemTrayBackingStore::beginPaint(const QRegion &region)
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
1000void QXcbSystemTrayBackingStore::render(xcb_window_t window, const QRegion &region, 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
1019void 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
1057void 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
1101QT_END_NAMESPACE
1102

source code of qtbase/src/plugins/platforms/xcb/qxcbbackingstore.cpp