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(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(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, window->depth(), 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(m_qimage_format).alphaUsage() == QPixelFormat::UsesAlpha;
211 if (!m_hasAlpha)
212 m_qimage_format = qt_maybeAlphaVersionWithSameDepth(m_qimage_format);
213
214 memset(&m_shm_info, 0, sizeof m_shm_info);
215
216 resize(size);
217}
218
219void QXcbBackingStoreImage::resize(const QSize &size)
220{
221 destroy(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(size.width(), size.height(),
226 XCB_IMAGE_FORMAT_Z_PIXMAP,
227 m_xcb_format->scanline_pad,
228 m_xcb_format->depth,
229 m_xcb_format->bits_per_pixel,
230 0, byteOrder,
231 XCB_IMAGE_ORDER_MSB_FIRST,
232 0, ~0, 0);
233
234 const size_t segmentSize = imageDataSize(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(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(xcb_connection());
268 auto xcbScreen = static_cast<QXcbScreen *>(m_backingStore->window()->screen()->handle());
269 xcb_create_pixmap(xcb_connection(),
270 m_xcb_image->depth,
271 m_xcb_pixmap,
272 xcbScreen->root(),
273 m_xcb_image->width, 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(m_xcb_image->data);
285 }
286 }
287 xcb_image_destroy(m_xcb_image);
288 }
289
290 if (m_gc) {
291 xcb_free_gc(xcb_connection(), 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(xcb_connection(), 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(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(reply.get()), w, h, m_qimage.format());
338
339 QPainter p(&m_qimage);
340 p.setCompositionMode(QPainter::CompositionMode_Source);
341 p.drawImage(rect.topLeft(), img);
342 }
343 }
344 m_scrolledRegion = QRegion();
345 } else {
346 // Copy scrolled image region from client-side memory to server-side pixmap
347 ensureGC(m_xcb_pixmap);
348 if (hasShm())
349 shmPutImage(m_xcb_pixmap, m_scrolledRegion);
350 else
351 flushPixmap(m_scrolledRegion, 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(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(xcb_connection(), reply.get());
377 if (reply->nfd != 1) {
378 for (int i = 0; i < reply->nfd; i++)
379 close(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(nullptr, segmentSize, PROT_READ|PROT_WRITE, MAP_SHARED, fds[0], 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(fds[0]);
390 xcb_shm_detach(xcb_connection(), seg);
391 return;
392 }
393
394 close(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(xcb_connection(), segmentSize, &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, 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(id, 0, 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(id, IPC_RMID, 0) == -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, seg, id, false);
426 auto *error = xcb_request_check(c, cookie);
427 if (error) {
428 qCWarning(lcQpaXcb(), "xcb_shm_attach() failed");
429 free(error);
430 if (shmdt(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, seg);
435 auto shmaddr = static_cast<quint8 *>(addr);
436 if (shmdt(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(xcb_connection(), m_shm_info.shmseg);
450 xcb_generic_error_t *error = xcb_request_check(xcb_connection(), cookie);
451 if (error)
452 connection()->printXcbError("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(m_shm_info.shmaddr, 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(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(scrollArea);
489
490 for (const QRect &rect : scrollArea)
491 qt_scrollRectInImage(m_qimage, rect, delta);
492 } else {
493 if (hasShm())
494 shmPutImage(m_xcb_pixmap, m_pendingFlush.intersected(scrollArea));
495 else
496 flushPixmap(scrollArea);
497
498 ensureGC(m_xcb_pixmap);
499
500 for (const QRect &src : scrollArea) {
501 const QRect dst = src.translated(delta).intersected(bounds);
502 xcb_copy_area(xcb_connection(),
503 m_xcb_pixmap,
504 m_xcb_pixmap,
505 m_gc,
506 src.x(), src.y(),
507 dst.x(), dst.y(),
508 dst.width(), dst.height());
509 }
510 }
511
512 m_scrolledRegion |= scrollArea.translated(delta).intersected(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(xcb_connection(), 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(xcb_connection());
531 xcb_create_gc(xcb_connection(), m_gc, dst, mask, 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(dst, src, 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(rect.height() * dstStride);
579
580 if (swap) {
581 switch (src.depth()) {
582 case 32:
583 copy_swapped<quint32>(buffer->data(), dstStride, src, rect);
584 break;
585 case 16:
586 copy_swapped<quint16>(buffer->data(), dstStride, src, rect);
587 break;
588 }
589 } else {
590 copy_unswapped(buffer->data(), dstStride, 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(offset).topLeft();
605 xcb_shm_put_image(xcb_connection(),
606 drawable,
607 m_gc,
608 m_xcb_image->width,
609 m_xcb_image->height,
610 source.x(), source.y(),
611 rect.width(), rect.height(),
612 rect.x(), rect.y(),
613 m_xcb_image->depth,
614 m_xcb_image->format,
615 0, // send event?
616 m_shm_info.shmseg,
617 m_xcb_image->data - m_shm_info.shmaddr);
618 }
619 m_dirtyShm |= region.translated(offset);
620}
621
622void QXcbBackingStoreImage::flushPixmap(const QRegion &region, bool fullRegion)
623{
624 if (!fullRegion) {
625 auto actualRegion = m_pendingFlush.intersected(region);
626 m_pendingFlush -= region;
627 flushPixmap(actualRegion, true);
628 return;
629 }
630
631 xcb_image_t xcb_subimage;
632 memset(&xcb_subimage, 0, 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(sizeof(xcb_put_image_request_t));
646
647 for (const QRect &rect : region) {
648 const quint32 stride = round_up_scanline(rect.width() * m_qimage.depth(), 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(height, rows_per_put);
668 const QRect subRect(x, y, width, rows);
669 const QImage subImage = native_sub_image(&m_flushBuffer, stride, m_qimage, subRect, 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(&xcb_subimage);
677
678 xcb_image_put(xcb_connection(),
679 m_xcb_pixmap,
680 m_gc,
681 &xcb_subimage,
682 x,
683 y,
684 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(xcb_connection(), m_gc, mask, values);
698 } else {
699 const auto xcb_rects = qRegionToXcbRectangleList(region);
700 xcb_set_clip_rectangles(xcb_connection(),
701 XCB_CLIP_ORDERING_YX_BANDED,
702 m_gc,
703 0, 0,
704 xcb_rects.size(), 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 setClip(region);
714
715 if (hasShm()) {
716 // Copy scrolled area on server-side from pixmap to window
717 const QRegion scrolledRegion = m_scrolledRegion.translated(-offset);
718 for (const QRect &rect : scrolledRegion) {
719 const QPoint source = rect.translated(offset).topLeft();
720 xcb_copy_area(xcb_connection(),
721 m_xcb_pixmap,
722 dst,
723 m_gc,
724 source.x(), source.y(),
725 rect.x(), rect.y(),
726 rect.width(), rect.height());
727 }
728
729 // Copy non-scrolled image from client-side memory to server-side window
730 const QRegion notScrolledArea = region - scrolledRegion;
731 shmPutImage(dst, notScrolledArea, offset);
732 } else {
733 const QRect bounds = region.boundingRect();
734 const QPoint target = bounds.topLeft();
735 const QRect source = bounds.translated(offset);
736 flushPixmap(region);
737 xcb_copy_area(xcb_connection(),
738 m_xcb_pixmap,
739 dst,
740 m_gc,
741 source.x(), source.y(),
742 target.x(), target.y(),
743 source.width(), source.height());
744 }
745
746 setClip(QRegion());
747}
748
749void QXcbBackingStoreImage::preparePaint(const QRegion &region)
750{
751 if (hasShm()) {
752 // to prevent X from reading from the image region while we're writing to it
753 if (m_dirtyShm.intersects(region)) {
754 connection()->sync();
755 m_dirtyShm = QRegion();
756 }
757 }
758 m_scrolledRegion -= region;
759 m_pendingFlush |= region;
760}
761
762bool QXcbBackingStore::createSystemVShmSegment(xcb_connection_t *c, size_t segmentSize, void *shmInfo)
763{
764 auto info = reinterpret_cast<xcb_shm_segment_info_t *>(shmInfo);
765 return QXcbBackingStoreImage::createSystemVShmSegment(c, segmentSize, info);
766}
767
768QXcbBackingStore::QXcbBackingStore(QWindow *window)
769 : QPlatformBackingStore(window)
770{
771 QXcbScreen *screen = static_cast<QXcbScreen *>(window->screen()->handle());
772 setConnection(screen->connection());
773}
774
775QXcbBackingStore::~QXcbBackingStore()
776{
777 delete m_image;
778}
779
780QPaintDevice *QXcbBackingStore::paintDevice()
781{
782 if (!m_image)
783 return 0;
784 return m_rgbImage.isNull() ? m_image->image() : &m_rgbImage;
785}
786
787void QXcbBackingStore::beginPaint(const QRegion &region)
788{
789 if (!m_image)
790 return;
791
792 m_paintRegions.push(region);
793 m_image->preparePaint(region);
794
795 if (m_image->hasAlpha()) {
796 QPainter p(paintDevice());
797 p.setCompositionMode(QPainter::CompositionMode_Source);
798 const QColor blank = Qt::transparent;
799 for (const QRect &rect : region)
800 p.fillRect(rect, blank);
801 }
802}
803
804void QXcbBackingStore::endPaint()
805{
806 if (Q_UNLIKELY(m_paintRegions.isEmpty())) {
807 qCWarning(lcQpaXcb, "%s: paint regions empty!", Q_FUNC_INFO);
808 return;
809 }
810
811 const QRegion region = m_paintRegions.pop();
812 m_image->preparePaint(region);
813
814 QXcbWindow *platformWindow = static_cast<QXcbWindow *>(window()->handle());
815 if (!platformWindow || !platformWindow->imageNeedsRgbSwap())
816 return;
817
818 // Slow path: the paint device was m_rgbImage. Now copy with swapping red
819 // and blue into m_image.
820 auto it = region.begin();
821 const auto end = region.end();
822 if (it == end)
823 return;
824 QPainter p(m_image->image());
825 while (it != end) {
826 const QRect rect = *(it++);
827 p.drawImage(rect.topLeft(), m_rgbImage.copy(rect).rgbSwapped());
828 }
829}
830
831QImage QXcbBackingStore::toImage() const
832{
833 // If the backingstore is rgbSwapped, return the internal image type here.
834 if (!m_rgbImage.isNull())
835 return m_rgbImage;
836 return m_image && m_image->image() ? *m_image->image() : QImage();
837}
838
839QPlatformGraphicsBuffer *QXcbBackingStore::graphicsBuffer() const
840{
841 return m_image ? m_image->graphicsBuffer() : nullptr;
842}
843
844void QXcbBackingStore::flush(QWindow *window, const QRegion &region, const QPoint &offset)
845{
846 if (!m_image || m_image->size().isEmpty())
847 return;
848
849 m_image->flushScrolledRegion(false);
850
851 QSize imageSize = m_image->size();
852
853 QRegion clipped = region;
854 clipped &= QRect(QPoint(), QHighDpi::toNativePixels(window->size(), window));
855 clipped &= QRect(0, 0, imageSize.width(), imageSize.height()).translated(-offset);
856
857 QRect bounds = clipped.boundingRect();
858
859 if (bounds.isNull())
860 return;
861
862 QXcbWindow *platformWindow = static_cast<QXcbWindow *>(window->handle());
863 if (!platformWindow) {
864 qCWarning(lcQpaXcb, "%s QWindow has no platform window, see QTBUG-32681", Q_FUNC_INFO);
865 return;
866 }
867
868 render(platformWindow->xcb_window(), clipped, offset);
869
870 if (platformWindow->needsSync())
871 platformWindow->updateSyncRequestCounter();
872 else
873 xcb_flush(xcb_connection());
874}
875
876void QXcbBackingStore::render(xcb_window_t window, const QRegion &region, const QPoint &offset)
877{
878 m_image->put(window, region, offset);
879}
880
881#ifndef QT_NO_OPENGL
882void QXcbBackingStore::composeAndFlush(QWindow *window, const QRegion &region, const QPoint &offset,
883 QPlatformTextureList *textures,
884 bool translucentBackground)
885{
886 if (!m_image || m_image->size().isEmpty())
887 return;
888
889 m_image->flushScrolledRegion(true);
890
891 QPlatformBackingStore::composeAndFlush(window, region, offset, textures, translucentBackground);
892
893 QXcbWindow *platformWindow = static_cast<QXcbWindow *>(window->handle());
894 if (platformWindow->needsSync()) {
895 platformWindow->updateSyncRequestCounter();
896 } else {
897 xcb_flush(xcb_connection());
898 }
899}
900#endif // QT_NO_OPENGL
901
902void QXcbBackingStore::resize(const QSize &size, const QRegion &)
903{
904 if (m_image && size == m_image->size())
905 return;
906
907 QPlatformWindow *pw = window()->handle();
908 if (!pw) {
909 window()->create();
910 pw = window()->handle();
911 }
912 QXcbWindow* win = static_cast<QXcbWindow *>(pw);
913
914 recreateImage(win, size);
915}
916
917void QXcbBackingStore::recreateImage(QXcbWindow *win, const QSize &size)
918{
919 if (m_image)
920 m_image->resize(size);
921 else
922 m_image = new QXcbBackingStoreImage(this, size);
923
924 // Slow path for bgr888 VNC: Create an additional image, paint into that and
925 // swap R and B while copying to m_image after each paint.
926 if (win->imageNeedsRgbSwap()) {
927 m_rgbImage = QImage(size, win->imageFormat());
928 }
929}
930
931bool QXcbBackingStore::scroll(const QRegion &area, int dx, int dy)
932{
933 if (m_image)
934 return m_image->scroll(area, dx, dy);
935
936 return false;
937}
938
939QXcbSystemTrayBackingStore::QXcbSystemTrayBackingStore(QWindow *window)
940 : QXcbBackingStore(window)
941{
942 // We need three different behaviors depending on whether the X11 visual
943 // for the system tray supports an alpha channel, i.e. is 32 bits, and
944 // whether XRender can be used:
945 // 1) if the visual has an alpha channel, then render the window's buffer
946 // directly to the X11 window as usual
947 // 2) else if XRender can be used, then render the window's buffer to Pixmap,
948 // then render Pixmap's contents to the cleared X11 window with
949 // xcb_render_composite()
950 // 3) else grab the X11 window's content and paint it first each time as a
951 // background before rendering the window's buffer to the X11 window
952
953 auto *platformWindow = static_cast<QXcbWindow *>(window->handle());
954 quint8 depth = connection()->primaryScreen()->depthOfVisual(platformWindow->visualId());
955
956 if (depth != 32) {
957 platformWindow->setParentRelativeBackPixmap();
958 initXRenderMode();
959 m_useGrabbedBackgound = !m_usingXRenderMode;
960 }
961}
962
963QXcbSystemTrayBackingStore::~QXcbSystemTrayBackingStore()
964{
965 if (m_xrenderPicture) {
966 xcb_render_free_picture(xcb_connection(), m_xrenderPicture);
967 m_xrenderPicture = XCB_NONE;
968 }
969 if (m_xrenderPixmap) {
970 xcb_free_pixmap(xcb_connection(), m_xrenderPixmap);
971 m_xrenderPixmap = XCB_NONE;
972 }
973 if (m_windowPicture) {
974 xcb_render_free_picture(xcb_connection(), m_windowPicture);
975 m_windowPicture = XCB_NONE;
976 }
977}
978
979void QXcbSystemTrayBackingStore::beginPaint(const QRegion &region)
980{
981 QXcbBackingStore::beginPaint(region);
982
983 if (m_useGrabbedBackgound) {
984 QPainter p(paintDevice());
985 p.setCompositionMode(QPainter::CompositionMode_Source);
986 for (const QRect &rect: region)
987 p.drawPixmap(rect, m_grabbedBackground, rect);
988 }
989}
990
991void QXcbSystemTrayBackingStore::render(xcb_window_t window, const QRegion &region, const QPoint &offset)
992{
993 if (!m_usingXRenderMode) {
994 QXcbBackingStore::render(window, region, offset);
995 return;
996 }
997
998 m_image->put(m_xrenderPixmap, region, offset);
999 const QRect bounds = region.boundingRect();
1000 const QPoint target = bounds.topLeft();
1001 const QRect source = bounds.translated(offset);
1002 xcb_clear_area(xcb_connection(), false, window,
1003 target.x(), target.y(), source.width(), source.height());
1004 xcb_render_composite(xcb_connection(), XCB_RENDER_PICT_OP_OVER,
1005 m_xrenderPicture, 0, m_windowPicture,
1006 target.x(), target.y(), 0, 0, target.x(), target.y(),
1007 source.width(), source.height());
1008}
1009
1010void QXcbSystemTrayBackingStore::recreateImage(QXcbWindow *win, const QSize &size)
1011{
1012 if (!m_usingXRenderMode) {
1013 QXcbBackingStore::recreateImage(win, size);
1014
1015 if (m_useGrabbedBackgound) {
1016 xcb_clear_area(xcb_connection(), false, win->xcb_window(),
1017 0, 0, size.width(), size.height());
1018 m_grabbedBackground = win->xcbScreen()->grabWindow(win->winId(), 0, 0,
1019 size.width(), size.height());
1020 }
1021 return;
1022 }
1023
1024 if (m_xrenderPicture) {
1025 xcb_render_free_picture(xcb_connection(), m_xrenderPicture);
1026 m_xrenderPicture = XCB_NONE;
1027 }
1028 if (m_xrenderPixmap) {
1029 xcb_free_pixmap(xcb_connection(), m_xrenderPixmap);
1030 m_xrenderPixmap = XCB_NONE;
1031 }
1032
1033 QXcbScreen *screen = win->xcbScreen();
1034
1035 m_xrenderPixmap = xcb_generate_id(xcb_connection());
1036 xcb_create_pixmap(xcb_connection(), 32, m_xrenderPixmap, screen->root(), size.width(), size.height());
1037
1038 m_xrenderPicture = xcb_generate_id(xcb_connection());
1039 xcb_render_create_picture(xcb_connection(), m_xrenderPicture, m_xrenderPixmap, m_xrenderPictFormat, 0, 0);
1040
1041 // XRender expects premultiplied alpha
1042 if (m_image)
1043 m_image->resize(size);
1044 else
1045 m_image = new QXcbBackingStoreImage(this, size, 32, QImage::Format_ARGB32_Premultiplied);
1046}
1047
1048void QXcbSystemTrayBackingStore::initXRenderMode()
1049{
1050 if (!connection()->hasXRender())
1051 return;
1052
1053 xcb_connection_t *conn = xcb_connection();
1054 auto formatsReply = Q_XCB_REPLY(xcb_render_query_pict_formats, conn);
1055
1056 if (!formatsReply) {
1057 qWarning("QXcbSystemTrayBackingStore: xcb_render_query_pict_formats() failed");
1058 return;
1059 }
1060
1061 xcb_render_pictforminfo_t *fmt = xcb_render_util_find_standard_format(formatsReply.get(),
1062 XCB_PICT_STANDARD_ARGB_32);
1063 if (!fmt) {
1064 qWarning("QXcbSystemTrayBackingStore: Failed to find format PICT_STANDARD_ARGB_32");
1065 return;
1066 }
1067
1068 m_xrenderPictFormat = fmt->id;
1069
1070 auto *platformWindow = static_cast<QXcbWindow *>(window()->handle());
1071 xcb_render_pictvisual_t *vfmt = xcb_render_util_find_visual_format(formatsReply.get(), platformWindow->visualId());
1072
1073 if (!vfmt) {
1074 qWarning("QXcbSystemTrayBackingStore: Failed to find format for visual %x", platformWindow->visualId());
1075 return;
1076 }
1077
1078 m_windowPicture = xcb_generate_id(conn);
1079 xcb_void_cookie_t cookie =
1080 xcb_render_create_picture_checked(conn, m_windowPicture, platformWindow->xcb_window(), vfmt->format, 0, 0);
1081 xcb_generic_error_t *error = xcb_request_check(conn, cookie);
1082 if (error) {
1083 qWarning("QXcbSystemTrayBackingStore: Failed to create Picture with format %x for window %x, error code %d",
1084 vfmt->format, platformWindow->xcb_window(), error->error_code);
1085 free(error);
1086 return;
1087 }
1088
1089 m_usingXRenderMode = true;
1090}
1091
1092QT_END_NAMESPACE
1093