1118 lines
38 KiB
C++
1118 lines
38 KiB
C++
/****************************************************************************
|
|
**
|
|
** Copyright (C) 2016 The Qt Company Ltd.
|
|
** Contact: https://www.qt.io/licensing/
|
|
**
|
|
** This file is part of the plugins of the Qt Toolkit.
|
|
**
|
|
** $QT_BEGIN_LICENSE:LGPL$
|
|
** Commercial License Usage
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
** accordance with the commercial license agreement provided with the
|
|
** Software or, alternatively, in accordance with the terms contained in
|
|
** a written agreement between you and The Qt Company. For licensing terms
|
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
** information use the contact form at https://www.qt.io/contact-us.
|
|
**
|
|
** GNU Lesser General Public License Usage
|
|
** Alternatively, this file may be used under the terms of the GNU Lesser
|
|
** General Public License version 3 as published by the Free Software
|
|
** Foundation and appearing in the file LICENSE.LGPL3 included in the
|
|
** packaging of this file. Please review the following information to
|
|
** ensure the GNU Lesser General Public License version 3 requirements
|
|
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
|
|
**
|
|
** GNU General Public License Usage
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
** General Public License version 2.0 or (at your option) the GNU General
|
|
** Public license version 3 or any later version approved by the KDE Free
|
|
** Qt Foundation. The licenses are as published by the Free Software
|
|
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
|
|
** included in the packaging of this file. Please review the following
|
|
** information to ensure the GNU General Public License requirements will
|
|
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
|
|
** https://www.gnu.org/licenses/gpl-3.0.html.
|
|
**
|
|
** $QT_END_LICENSE$
|
|
**
|
|
****************************************************************************/
|
|
|
|
#include "qxcbbackingstore.h"
|
|
|
|
#include "qxcbconnection.h"
|
|
#include "qxcbscreen.h"
|
|
#include "qxcbwindow.h"
|
|
|
|
#include <xcb/shm.h>
|
|
#include <xcb/xcb_image.h>
|
|
#include <xcb/render.h>
|
|
#include <xcb/xcb_renderutil.h>
|
|
|
|
#include <sys/ipc.h>
|
|
#include <sys/shm.h>
|
|
#include <sys/mman.h>
|
|
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
|
|
#include <qdebug.h>
|
|
#include <qpainter.h>
|
|
#include <qscreen.h>
|
|
#include <QtGui/private/qhighdpiscaling_p.h>
|
|
#include <qpa/qplatformgraphicsbuffer.h>
|
|
#include <private/qimage_p.h>
|
|
#include <qendian.h>
|
|
|
|
#include <algorithm>
|
|
|
|
#if (XCB_SHM_MAJOR_VERSION == 1 && XCB_SHM_MINOR_VERSION >= 2) || XCB_SHM_MAJOR_VERSION > 1
|
|
#define XCB_USE_SHM_FD
|
|
#endif
|
|
|
|
QT_BEGIN_NAMESPACE
|
|
|
|
class QXcbBackingStore;
|
|
|
|
class QXcbBackingStoreImage : public QXcbObject
|
|
{
|
|
public:
|
|
QXcbBackingStoreImage(QXcbBackingStore *backingStore, const QSize &size);
|
|
QXcbBackingStoreImage(QXcbBackingStore *backingStore, const QSize &size, uint depth, QImage::Format format);
|
|
~QXcbBackingStoreImage() { destroy(true); }
|
|
|
|
void resize(const QSize &size);
|
|
|
|
void flushScrolledRegion(bool clientSideScroll);
|
|
|
|
bool scroll(const QRegion &area, int dx, int dy);
|
|
|
|
QImage *image() { return &m_qimage; }
|
|
QPlatformGraphicsBuffer *graphicsBuffer() { return m_graphics_buffer; }
|
|
|
|
QSize size() const { return m_qimage.size(); }
|
|
|
|
bool hasAlpha() const { return m_hasAlpha; }
|
|
bool hasShm() const { return m_shm_info.shmaddr != nullptr; }
|
|
|
|
void put(xcb_drawable_t dst, const QRegion ®ion, const QPoint &offset);
|
|
void preparePaint(const QRegion ®ion);
|
|
|
|
static bool createSystemVShmSegment(xcb_connection_t *c, size_t segmentSize = 1,
|
|
xcb_shm_segment_info_t *shm_info = nullptr);
|
|
|
|
private:
|
|
void init(const QSize &size, uint depth, QImage::Format format);
|
|
|
|
void createShmSegment(size_t segmentSize);
|
|
void destroyShmSegment();
|
|
void destroy(bool destroyShm);
|
|
|
|
void ensureGC(xcb_drawable_t dst);
|
|
void shmPutImage(xcb_drawable_t drawable, const QRegion ®ion, const QPoint &offset = QPoint());
|
|
void flushPixmap(const QRegion ®ion, bool fullRegion = false);
|
|
void setClip(const QRegion ®ion);
|
|
|
|
xcb_shm_segment_info_t m_shm_info;
|
|
size_t m_segmentSize = 0;
|
|
QXcbBackingStore *m_backingStore = nullptr;
|
|
|
|
xcb_image_t *m_xcb_image = nullptr;
|
|
|
|
QImage m_qimage;
|
|
QPlatformGraphicsBuffer *m_graphics_buffer = nullptr;
|
|
|
|
xcb_gcontext_t m_gc = 0;
|
|
xcb_drawable_t m_gc_drawable = 0;
|
|
|
|
// When using shared memory these variables are used only for server-side scrolling.
|
|
// When not using shared memory, we maintain a server-side pixmap with the backing
|
|
// store as well as repainted content not yet flushed to the pixmap. We only flush
|
|
// the regions we need and only when these are marked dirty. This way we can just
|
|
// do a server-side copy on expose instead of sending the pixels every time
|
|
xcb_pixmap_t m_xcb_pixmap = 0;
|
|
QRegion m_pendingFlush;
|
|
|
|
// This is the scrolled region which is stored in server-side pixmap
|
|
QRegion m_scrolledRegion;
|
|
|
|
// When using shared memory this is the region currently shared with the server
|
|
QRegion m_dirtyShm;
|
|
|
|
// When not using shared memory this is a temporary buffer which is uploaded
|
|
// as a pixmap region to server
|
|
QByteArray m_flushBuffer;
|
|
|
|
bool m_hasAlpha = false;
|
|
bool m_clientSideScroll = false;
|
|
|
|
const xcb_format_t *m_xcb_format = nullptr;
|
|
QImage::Format m_qimage_format = QImage::Format_Invalid;
|
|
};
|
|
|
|
class QXcbGraphicsBuffer : public QPlatformGraphicsBuffer
|
|
{
|
|
public:
|
|
QXcbGraphicsBuffer(QImage *image)
|
|
: QPlatformGraphicsBuffer(image->size(), QImage::toPixelFormat(image->format()))
|
|
, m_image(image)
|
|
{ }
|
|
|
|
bool doLock(AccessTypes access, const QRect &rect) override
|
|
{
|
|
Q_UNUSED(rect);
|
|
if (access & ~(QPlatformGraphicsBuffer::SWReadAccess | QPlatformGraphicsBuffer::SWWriteAccess))
|
|
return false;
|
|
|
|
m_access_lock |= access;
|
|
return true;
|
|
}
|
|
void doUnlock() override { m_access_lock = None; }
|
|
|
|
const uchar *data() const override { return m_image->bits(); }
|
|
uchar *data() override { return m_image->bits(); }
|
|
int bytesPerLine() const override { return m_image->bytesPerLine(); }
|
|
|
|
Origin origin() const override { return QPlatformGraphicsBuffer::OriginTopLeft; }
|
|
|
|
private:
|
|
AccessTypes m_access_lock = QPlatformGraphicsBuffer::None;
|
|
QImage *m_image = nullptr;
|
|
};
|
|
|
|
static inline size_t imageDataSize(const xcb_image_t *image)
|
|
{
|
|
return static_cast<size_t>(image->stride) * image->height;
|
|
}
|
|
|
|
QXcbBackingStoreImage::QXcbBackingStoreImage(QXcbBackingStore *backingStore, const QSize &size)
|
|
: QXcbObject(backingStore->connection())
|
|
, m_backingStore(backingStore)
|
|
{
|
|
auto window = static_cast<QXcbWindow *>(m_backingStore->window()->handle());
|
|
init(size, window->depth(), window->imageFormat());
|
|
}
|
|
|
|
QXcbBackingStoreImage::QXcbBackingStoreImage(QXcbBackingStore *backingStore, const QSize &size,
|
|
uint depth, QImage::Format format)
|
|
: QXcbObject(backingStore->connection())
|
|
, m_backingStore(backingStore)
|
|
{
|
|
init(size, depth, format);
|
|
}
|
|
|
|
void QXcbBackingStoreImage::init(const QSize &size, uint depth, QImage::Format format)
|
|
{
|
|
m_xcb_format = connection()->formatForDepth(depth);
|
|
Q_ASSERT(m_xcb_format);
|
|
|
|
m_qimage_format = format;
|
|
m_hasAlpha = QImage::toPixelFormat(m_qimage_format).alphaUsage() == QPixelFormat::UsesAlpha;
|
|
if (!m_hasAlpha)
|
|
m_qimage_format = qt_maybeAlphaVersionWithSameDepth(m_qimage_format);
|
|
|
|
memset(&m_shm_info, 0, sizeof m_shm_info);
|
|
|
|
resize(size);
|
|
}
|
|
|
|
void QXcbBackingStoreImage::resize(const QSize &size)
|
|
{
|
|
destroy(false);
|
|
|
|
auto byteOrder = QSysInfo::ByteOrder == QSysInfo::BigEndian ? XCB_IMAGE_ORDER_MSB_FIRST
|
|
: XCB_IMAGE_ORDER_LSB_FIRST;
|
|
m_xcb_image = xcb_image_create(size.width(), size.height(),
|
|
XCB_IMAGE_FORMAT_Z_PIXMAP,
|
|
m_xcb_format->scanline_pad,
|
|
m_xcb_format->depth,
|
|
m_xcb_format->bits_per_pixel,
|
|
0, byteOrder,
|
|
XCB_IMAGE_ORDER_MSB_FIRST,
|
|
nullptr, ~0, nullptr);
|
|
|
|
const size_t segmentSize = imageDataSize(m_xcb_image);
|
|
|
|
if (connection()->hasShm()) {
|
|
if (segmentSize == 0) {
|
|
if (m_segmentSize > 0) {
|
|
destroyShmSegment();
|
|
qCDebug(lcQpaXcb) << "[" << m_backingStore->window()
|
|
<< "] destroyed SHM segment due to resize to" << size;
|
|
}
|
|
} else {
|
|
// Destroy shared memory segment if it is double (or more) of what we actually
|
|
// need with new window size. Or if the new size is bigger than what we currently
|
|
// have allocated.
|
|
if (m_shm_info.shmaddr && (m_segmentSize < segmentSize || m_segmentSize / 2 >= segmentSize))
|
|
destroyShmSegment();
|
|
if (!m_shm_info.shmaddr) {
|
|
qCDebug(lcQpaXcb) << "[" << m_backingStore->window()
|
|
<< "] creating shared memory" << segmentSize << "bytes for"
|
|
<< size << "depth" << m_xcb_format->depth << "bits"
|
|
<< m_xcb_format->bits_per_pixel;
|
|
createShmSegment(segmentSize);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (segmentSize == 0)
|
|
return;
|
|
|
|
m_xcb_image->data = m_shm_info.shmaddr ? m_shm_info.shmaddr : (uint8_t *)malloc(segmentSize);
|
|
m_qimage = QImage(static_cast<uchar *>(m_xcb_image->data), m_xcb_image->width,
|
|
m_xcb_image->height, m_xcb_image->stride, m_qimage_format);
|
|
m_graphics_buffer = new QXcbGraphicsBuffer(&m_qimage);
|
|
|
|
m_xcb_pixmap = xcb_generate_id(xcb_connection());
|
|
auto xcbScreen = static_cast<QXcbScreen *>(m_backingStore->window()->screen()->handle());
|
|
xcb_create_pixmap(xcb_connection(),
|
|
m_xcb_image->depth,
|
|
m_xcb_pixmap,
|
|
xcbScreen->root(),
|
|
m_xcb_image->width, m_xcb_image->height);
|
|
}
|
|
|
|
void QXcbBackingStoreImage::destroy(bool destroyShm)
|
|
{
|
|
if (m_xcb_image) {
|
|
if (m_xcb_image->data) {
|
|
if (m_shm_info.shmaddr) {
|
|
if (destroyShm)
|
|
destroyShmSegment();
|
|
} else {
|
|
free(m_xcb_image->data);
|
|
}
|
|
}
|
|
xcb_image_destroy(m_xcb_image);
|
|
}
|
|
|
|
if (m_gc) {
|
|
xcb_free_gc(xcb_connection(), m_gc);
|
|
m_gc = 0;
|
|
}
|
|
m_gc_drawable = 0;
|
|
|
|
delete m_graphics_buffer;
|
|
m_graphics_buffer = nullptr;
|
|
|
|
if (m_xcb_pixmap) {
|
|
xcb_free_pixmap(xcb_connection(), m_xcb_pixmap);
|
|
m_xcb_pixmap = 0;
|
|
}
|
|
|
|
m_qimage = QImage();
|
|
}
|
|
|
|
void QXcbBackingStoreImage::flushScrolledRegion(bool clientSideScroll)
|
|
{
|
|
if (m_clientSideScroll == clientSideScroll)
|
|
return;
|
|
|
|
m_clientSideScroll = clientSideScroll;
|
|
|
|
if (m_scrolledRegion.isNull())
|
|
return;
|
|
|
|
if (hasShm() && m_dirtyShm.intersects(m_scrolledRegion)) {
|
|
connection()->sync();
|
|
m_dirtyShm = QRegion();
|
|
}
|
|
|
|
if (m_clientSideScroll) {
|
|
// Copy scrolled image region from server-side pixmap to client-side memory
|
|
for (const QRect &rect : m_scrolledRegion) {
|
|
const int w = rect.width();
|
|
const int h = rect.height();
|
|
|
|
auto reply = Q_XCB_REPLY_UNCHECKED(xcb_get_image,
|
|
xcb_connection(),
|
|
m_xcb_image->format,
|
|
m_xcb_pixmap,
|
|
rect.x(), rect.y(),
|
|
w, h,
|
|
~0u);
|
|
|
|
if (reply && reply->depth == m_xcb_image->depth) {
|
|
const QImage img(xcb_get_image_data(reply.get()), w, h, m_qimage.format());
|
|
|
|
QPainter p(&m_qimage);
|
|
p.setCompositionMode(QPainter::CompositionMode_Source);
|
|
p.drawImage(rect.topLeft(), img);
|
|
}
|
|
}
|
|
m_scrolledRegion = QRegion();
|
|
} else {
|
|
// Copy scrolled image region from client-side memory to server-side pixmap
|
|
ensureGC(m_xcb_pixmap);
|
|
if (hasShm())
|
|
shmPutImage(m_xcb_pixmap, m_scrolledRegion);
|
|
else
|
|
flushPixmap(m_scrolledRegion, true);
|
|
}
|
|
}
|
|
|
|
void QXcbBackingStoreImage::createShmSegment(size_t segmentSize)
|
|
{
|
|
Q_ASSERT(connection()->hasShm());
|
|
Q_ASSERT(m_segmentSize == 0);
|
|
|
|
#ifdef XCB_USE_SHM_FD
|
|
if (connection()->hasShmFd()) {
|
|
if (Q_UNLIKELY(segmentSize > std::numeric_limits<uint32_t>::max())) {
|
|
qCWarning(lcQpaXcb, "xcb_shm_create_segment() can't be called for size %zu, maximum"
|
|
"allowed size is %u", segmentSize, std::numeric_limits<uint32_t>::max());
|
|
return;
|
|
}
|
|
|
|
const auto seg = xcb_generate_id(xcb_connection());
|
|
auto reply = Q_XCB_REPLY(xcb_shm_create_segment,
|
|
xcb_connection(), seg, segmentSize, false);
|
|
if (!reply) {
|
|
qCWarning(lcQpaXcb, "xcb_shm_create_segment() failed for size %zu", segmentSize);
|
|
return;
|
|
}
|
|
|
|
int *fds = xcb_shm_create_segment_reply_fds(xcb_connection(), reply.get());
|
|
if (reply->nfd != 1) {
|
|
for (int i = 0; i < reply->nfd; i++)
|
|
close(fds[i]);
|
|
|
|
qCWarning(lcQpaXcb, "failed to get file descriptor for shm segment of size %zu", segmentSize);
|
|
return;
|
|
}
|
|
|
|
void *addr = mmap(nullptr, segmentSize, PROT_READ|PROT_WRITE, MAP_SHARED, fds[0], 0);
|
|
if (addr == MAP_FAILED) {
|
|
qCWarning(lcQpaXcb, "failed to mmap segment from X server (%d: %s) for size %zu",
|
|
errno, strerror(errno), segmentSize);
|
|
close(fds[0]);
|
|
xcb_shm_detach(xcb_connection(), seg);
|
|
return;
|
|
}
|
|
|
|
close(fds[0]);
|
|
m_shm_info.shmseg = seg;
|
|
m_shm_info.shmaddr = static_cast<quint8 *>(addr);
|
|
m_segmentSize = segmentSize;
|
|
} else
|
|
#endif
|
|
{
|
|
if (createSystemVShmSegment(xcb_connection(), segmentSize, &m_shm_info))
|
|
m_segmentSize = segmentSize;
|
|
}
|
|
}
|
|
|
|
bool QXcbBackingStoreImage::createSystemVShmSegment(xcb_connection_t *c, size_t segmentSize,
|
|
xcb_shm_segment_info_t *shmInfo)
|
|
{
|
|
const int id = shmget(IPC_PRIVATE, segmentSize, IPC_CREAT | 0600);
|
|
if (id == -1) {
|
|
qCWarning(lcQpaXcb, "shmget() failed (%d: %s) for size %zu", errno, strerror(errno), segmentSize);
|
|
return false;
|
|
}
|
|
|
|
void *addr = shmat(id, nullptr, 0);
|
|
if (addr == (void *)-1) {
|
|
qCWarning(lcQpaXcb, "shmat() failed (%d: %s) for id %d", errno, strerror(errno), id);
|
|
return false;
|
|
}
|
|
|
|
if (shmctl(id, IPC_RMID, nullptr) == -1)
|
|
qCWarning(lcQpaXcb, "Error while marking the shared memory segment to be destroyed");
|
|
|
|
const auto seg = xcb_generate_id(c);
|
|
auto cookie = xcb_shm_attach_checked(c, seg, id, false);
|
|
auto *error = xcb_request_check(c, cookie);
|
|
if (error) {
|
|
qCWarning(lcQpaXcb(), "xcb_shm_attach() failed");
|
|
free(error);
|
|
if (shmdt(addr) == -1)
|
|
qCWarning(lcQpaXcb, "shmdt() failed (%d: %s) for %p", errno, strerror(errno), addr);
|
|
return false;
|
|
} else if (!shmInfo) { // this was a test run, free the allocated test segment
|
|
xcb_shm_detach(c, seg);
|
|
auto shmaddr = static_cast<quint8 *>(addr);
|
|
if (shmdt(shmaddr) == -1)
|
|
qCWarning(lcQpaXcb, "shmdt() failed (%d: %s) for %p", errno, strerror(errno), shmaddr);
|
|
}
|
|
if (shmInfo) {
|
|
shmInfo->shmseg = seg;
|
|
shmInfo->shmid = id; // unused
|
|
shmInfo->shmaddr = static_cast<quint8 *>(addr);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void QXcbBackingStoreImage::destroyShmSegment()
|
|
{
|
|
auto cookie = xcb_shm_detach_checked(xcb_connection(), m_shm_info.shmseg);
|
|
xcb_generic_error_t *error = xcb_request_check(xcb_connection(), cookie);
|
|
if (error)
|
|
connection()->printXcbError("xcb_shm_detach() failed with error", error);
|
|
m_shm_info.shmseg = 0;
|
|
|
|
#ifdef XCB_USE_SHM_FD
|
|
if (connection()->hasShmFd()) {
|
|
if (munmap(m_shm_info.shmaddr, m_segmentSize) == -1) {
|
|
qCWarning(lcQpaXcb, "munmap() failed (%d: %s) for %p with size %zu",
|
|
errno, strerror(errno), m_shm_info.shmaddr, m_segmentSize);
|
|
}
|
|
} else
|
|
#endif
|
|
{
|
|
if (shmdt(m_shm_info.shmaddr) == -1) {
|
|
qCWarning(lcQpaXcb, "shmdt() failed (%d: %s) for %p",
|
|
errno, strerror(errno), m_shm_info.shmaddr);
|
|
}
|
|
m_shm_info.shmid = 0; // unused
|
|
}
|
|
m_shm_info.shmaddr = nullptr;
|
|
|
|
m_segmentSize = 0;
|
|
}
|
|
|
|
extern void qt_scrollRectInImage(QImage &img, const QRect &rect, const QPoint &offset);
|
|
|
|
bool QXcbBackingStoreImage::scroll(const QRegion &area, int dx, int dy)
|
|
{
|
|
const QRect bounds(QPoint(), size());
|
|
const QRegion scrollArea(area & bounds);
|
|
const QPoint delta(dx, dy);
|
|
const QRegion destinationRegion = scrollArea.translated(delta).intersected(bounds);
|
|
|
|
if (m_clientSideScroll) {
|
|
if (m_qimage.isNull())
|
|
return false;
|
|
|
|
if (hasShm())
|
|
preparePaint(destinationRegion);
|
|
|
|
for (const QRect &rect : scrollArea)
|
|
qt_scrollRectInImage(m_qimage, rect, delta);
|
|
} else {
|
|
ensureGC(m_xcb_pixmap);
|
|
|
|
if (hasShm()) {
|
|
QRegion partialFlushRegion = m_pendingFlush.intersected(scrollArea);
|
|
shmPutImage(m_xcb_pixmap, partialFlushRegion);
|
|
m_pendingFlush -= partialFlushRegion;
|
|
} else {
|
|
flushPixmap(scrollArea);
|
|
}
|
|
|
|
for (const QRect &src : scrollArea) {
|
|
const QRect dst = src.translated(delta).intersected(bounds);
|
|
xcb_copy_area(xcb_connection(),
|
|
m_xcb_pixmap,
|
|
m_xcb_pixmap,
|
|
m_gc,
|
|
src.x(), src.y(),
|
|
dst.x(), dst.y(),
|
|
dst.width(), dst.height());
|
|
}
|
|
|
|
if (hasShm())
|
|
m_pendingFlush -= destinationRegion;
|
|
}
|
|
|
|
m_scrolledRegion |= destinationRegion;
|
|
|
|
return true;
|
|
}
|
|
|
|
void QXcbBackingStoreImage::ensureGC(xcb_drawable_t dst)
|
|
{
|
|
if (m_gc_drawable != dst) {
|
|
if (m_gc)
|
|
xcb_free_gc(xcb_connection(), m_gc);
|
|
|
|
static const uint32_t mask = XCB_GC_GRAPHICS_EXPOSURES;
|
|
static const uint32_t values[] = { 0 };
|
|
|
|
m_gc = xcb_generate_id(xcb_connection());
|
|
xcb_create_gc(xcb_connection(), m_gc, dst, mask, values);
|
|
|
|
m_gc_drawable = dst;
|
|
}
|
|
}
|
|
|
|
static inline void copy_unswapped(char *dst, int dstBytesPerLine, const QImage &img, const QRect &rect)
|
|
{
|
|
const uchar *srcData = img.constBits();
|
|
const qsizetype srcBytesPerLine = img.bytesPerLine();
|
|
|
|
const int leftOffset = rect.left() * img.depth() >> 3;
|
|
const int bottom = rect.bottom() + 1;
|
|
|
|
for (int yy = rect.top(); yy < bottom; ++yy) {
|
|
const uchar *src = srcData + yy * srcBytesPerLine + leftOffset;
|
|
::memmove(dst, src, dstBytesPerLine);
|
|
dst += dstBytesPerLine;
|
|
}
|
|
}
|
|
|
|
template <class Pixel>
|
|
static inline void copy_swapped(char *dst, const int dstStride, const QImage &img, const QRect &rect)
|
|
{
|
|
const uchar *srcData = img.constBits();
|
|
const qsizetype srcBytesPerLine = img.bytesPerLine();
|
|
|
|
const int left = rect.left();
|
|
const int width = rect.width();
|
|
const int bottom = rect.bottom() + 1;
|
|
|
|
for (int yy = rect.top(); yy < bottom; ++yy) {
|
|
Pixel *dstPixels = reinterpret_cast<Pixel *>(dst);
|
|
const Pixel *srcPixels = reinterpret_cast<const Pixel *>(srcData + yy * srcBytesPerLine) + left;
|
|
|
|
for (int i = 0; i < width; ++i)
|
|
dstPixels[i] = qbswap<Pixel>(*srcPixels++);
|
|
|
|
dst += dstStride;
|
|
}
|
|
}
|
|
|
|
static QImage native_sub_image(QByteArray *buffer, const int dstStride, const QImage &src, const QRect &rect, bool swap)
|
|
{
|
|
if (!swap && src.rect() == rect && src.bytesPerLine() == dstStride)
|
|
return src;
|
|
|
|
buffer->resize(rect.height() * dstStride);
|
|
|
|
if (swap) {
|
|
switch (src.depth()) {
|
|
case 32:
|
|
copy_swapped<quint32>(buffer->data(), dstStride, src, rect);
|
|
break;
|
|
case 16:
|
|
copy_swapped<quint16>(buffer->data(), dstStride, src, rect);
|
|
break;
|
|
}
|
|
} else {
|
|
copy_unswapped(buffer->data(), dstStride, src, rect);
|
|
}
|
|
|
|
return QImage(reinterpret_cast<const uchar *>(buffer->constData()), rect.width(), rect.height(), dstStride, src.format());
|
|
}
|
|
|
|
static inline quint32 round_up_scanline(quint32 base, quint32 pad)
|
|
{
|
|
return (base + pad - 1) & -pad;
|
|
}
|
|
|
|
void QXcbBackingStoreImage::shmPutImage(xcb_drawable_t drawable, const QRegion ®ion, const QPoint &offset)
|
|
{
|
|
for (const QRect &rect : region) {
|
|
const QPoint source = rect.translated(offset).topLeft();
|
|
xcb_shm_put_image(xcb_connection(),
|
|
drawable,
|
|
m_gc,
|
|
m_xcb_image->width,
|
|
m_xcb_image->height,
|
|
source.x(), source.y(),
|
|
rect.width(), rect.height(),
|
|
rect.x(), rect.y(),
|
|
m_xcb_image->depth,
|
|
m_xcb_image->format,
|
|
0, // send event?
|
|
m_shm_info.shmseg,
|
|
m_xcb_image->data - m_shm_info.shmaddr);
|
|
}
|
|
m_dirtyShm |= region.translated(offset);
|
|
}
|
|
|
|
void QXcbBackingStoreImage::flushPixmap(const QRegion ®ion, bool fullRegion)
|
|
{
|
|
if (!fullRegion) {
|
|
auto actualRegion = m_pendingFlush.intersected(region);
|
|
m_pendingFlush -= region;
|
|
flushPixmap(actualRegion, true);
|
|
return;
|
|
}
|
|
|
|
xcb_image_t xcb_subimage;
|
|
memset(&xcb_subimage, 0, sizeof(xcb_image_t));
|
|
|
|
xcb_subimage.format = m_xcb_image->format;
|
|
xcb_subimage.scanline_pad = m_xcb_image->scanline_pad;
|
|
xcb_subimage.depth = m_xcb_image->depth;
|
|
xcb_subimage.bpp = m_xcb_image->bpp;
|
|
xcb_subimage.unit = m_xcb_image->unit;
|
|
xcb_subimage.plane_mask = m_xcb_image->plane_mask;
|
|
xcb_subimage.byte_order = (xcb_image_order_t) connection()->setup()->image_byte_order;
|
|
xcb_subimage.bit_order = m_xcb_image->bit_order;
|
|
|
|
const bool needsByteSwap = xcb_subimage.byte_order != m_xcb_image->byte_order;
|
|
// Ensure that we don't send more than maxPutImageRequestDataBytes per request.
|
|
const auto maxPutImageRequestDataBytes = connection()->maxRequestDataBytes(sizeof(xcb_put_image_request_t));
|
|
|
|
for (const QRect &rect : region) {
|
|
const quint32 stride = round_up_scanline(rect.width() * m_qimage.depth(), xcb_subimage.scanline_pad) >> 3;
|
|
const int rows_per_put = maxPutImageRequestDataBytes / stride;
|
|
|
|
// This assert could trigger if a single row has more pixels than fit in
|
|
// a single PutImage request. In the absence of the BIG-REQUESTS extension
|
|
// the theoretical maximum lengths of maxPutImageRequestDataBytes can be
|
|
// roughly 256kB.
|
|
Q_ASSERT(rows_per_put > 0);
|
|
|
|
// If we upload the whole image in a single chunk, the result might be
|
|
// larger than the server's maximum request size and stuff breaks.
|
|
// To work around that, we upload the image in chunks where each chunk
|
|
// is small enough for a single request.
|
|
const int x = rect.x();
|
|
int y = rect.y();
|
|
const int width = rect.width();
|
|
int height = rect.height();
|
|
|
|
while (height > 0) {
|
|
const int rows = std::min(height, rows_per_put);
|
|
const QRect subRect(x, y, width, rows);
|
|
const QImage subImage = native_sub_image(&m_flushBuffer, stride, m_qimage, subRect, needsByteSwap);
|
|
|
|
Q_ASSERT(static_cast<size_t>(subImage.sizeInBytes()) <= maxPutImageRequestDataBytes);
|
|
|
|
xcb_subimage.width = width;
|
|
xcb_subimage.height = rows;
|
|
xcb_subimage.data = const_cast<uint8_t *>(subImage.constBits());
|
|
xcb_image_annotate(&xcb_subimage);
|
|
|
|
xcb_image_put(xcb_connection(),
|
|
m_xcb_pixmap,
|
|
m_gc,
|
|
&xcb_subimage,
|
|
x,
|
|
y,
|
|
0);
|
|
|
|
y += rows;
|
|
height -= rows;
|
|
}
|
|
}
|
|
}
|
|
|
|
void QXcbBackingStoreImage::setClip(const QRegion ®ion)
|
|
{
|
|
if (region.isEmpty()) {
|
|
static const uint32_t mask = XCB_GC_CLIP_MASK;
|
|
static const uint32_t values[] = { XCB_NONE };
|
|
xcb_change_gc(xcb_connection(), m_gc, mask, values);
|
|
} else {
|
|
const auto xcb_rects = qRegionToXcbRectangleList(region);
|
|
xcb_set_clip_rectangles(xcb_connection(),
|
|
XCB_CLIP_ORDERING_YX_BANDED,
|
|
m_gc,
|
|
0, 0,
|
|
xcb_rects.size(), xcb_rects.constData());
|
|
}
|
|
}
|
|
|
|
void QXcbBackingStoreImage::put(xcb_drawable_t dst, const QRegion ®ion, const QPoint &offset)
|
|
{
|
|
Q_ASSERT(!m_clientSideScroll);
|
|
|
|
ensureGC(dst);
|
|
|
|
if (hasShm()) {
|
|
setClip(region); // Clip in window local coordinates
|
|
|
|
// Copy scrolled area on server-side from pixmap to window
|
|
const QRegion scrolledRegion = m_scrolledRegion.translated(-offset);
|
|
for (const QRect &rect : scrolledRegion) {
|
|
const QPoint source = rect.translated(offset).topLeft();
|
|
xcb_copy_area(xcb_connection(),
|
|
m_xcb_pixmap,
|
|
dst,
|
|
m_gc,
|
|
source.x(), source.y(),
|
|
rect.x(), rect.y(),
|
|
rect.width(), rect.height());
|
|
}
|
|
|
|
// Copy non-scrolled image from client-side memory to server-side window
|
|
const QRegion notScrolledArea = region - scrolledRegion;
|
|
shmPutImage(dst, notScrolledArea, offset);
|
|
} else {
|
|
const QRect bounds = region.boundingRect();
|
|
const QPoint target = bounds.topLeft();
|
|
const QRect source = bounds.translated(offset);
|
|
|
|
// First clip in backingstore-local coordinates, and upload
|
|
// the changed parts of the backingstore to the server.
|
|
setClip(source);
|
|
flushPixmap(source);
|
|
|
|
// Then clip in window local coordinates, and copy the updated
|
|
// parts of the backingstore image server-side to the window.
|
|
setClip(region);
|
|
xcb_copy_area(xcb_connection(),
|
|
m_xcb_pixmap,
|
|
dst,
|
|
m_gc,
|
|
source.x(), source.y(),
|
|
target.x(), target.y(),
|
|
source.width(), source.height());
|
|
}
|
|
|
|
setClip(QRegion());
|
|
}
|
|
|
|
void QXcbBackingStoreImage::preparePaint(const QRegion ®ion)
|
|
{
|
|
if (hasShm()) {
|
|
// to prevent X from reading from the image region while we're writing to it
|
|
if (m_dirtyShm.intersects(region)) {
|
|
connection()->sync();
|
|
m_dirtyShm = QRegion();
|
|
}
|
|
}
|
|
m_scrolledRegion -= region;
|
|
m_pendingFlush |= region;
|
|
}
|
|
|
|
bool QXcbBackingStore::createSystemVShmSegment(xcb_connection_t *c, size_t segmentSize, void *shmInfo)
|
|
{
|
|
auto info = reinterpret_cast<xcb_shm_segment_info_t *>(shmInfo);
|
|
return QXcbBackingStoreImage::createSystemVShmSegment(c, segmentSize, info);
|
|
}
|
|
|
|
QXcbBackingStore::QXcbBackingStore(QWindow *window)
|
|
: QPlatformBackingStore(window)
|
|
{
|
|
QXcbScreen *screen = static_cast<QXcbScreen *>(window->screen()->handle());
|
|
setConnection(screen->connection());
|
|
}
|
|
|
|
QXcbBackingStore::~QXcbBackingStore()
|
|
{
|
|
delete m_image;
|
|
}
|
|
|
|
QPaintDevice *QXcbBackingStore::paintDevice()
|
|
{
|
|
if (!m_image)
|
|
return nullptr;
|
|
return m_rgbImage.isNull() ? m_image->image() : &m_rgbImage;
|
|
}
|
|
|
|
void QXcbBackingStore::beginPaint(const QRegion ®ion)
|
|
{
|
|
if (!m_image)
|
|
return;
|
|
|
|
m_paintRegions.push(region);
|
|
m_image->preparePaint(region);
|
|
|
|
if (m_image->hasAlpha()) {
|
|
QPainter p(paintDevice());
|
|
p.setCompositionMode(QPainter::CompositionMode_Source);
|
|
const QColor blank = Qt::transparent;
|
|
for (const QRect &rect : region)
|
|
p.fillRect(rect, blank);
|
|
}
|
|
}
|
|
|
|
void QXcbBackingStore::endPaint()
|
|
{
|
|
if (Q_UNLIKELY(m_paintRegions.isEmpty())) {
|
|
qCWarning(lcQpaXcb, "%s: paint regions empty!", Q_FUNC_INFO);
|
|
return;
|
|
}
|
|
|
|
const QRegion region = m_paintRegions.pop();
|
|
m_image->preparePaint(region);
|
|
|
|
QXcbWindow *platformWindow = static_cast<QXcbWindow *>(window()->handle());
|
|
if (!platformWindow || !platformWindow->imageNeedsRgbSwap())
|
|
return;
|
|
|
|
// Slow path: the paint device was m_rgbImage. Now copy with swapping red
|
|
// and blue into m_image.
|
|
auto it = region.begin();
|
|
const auto end = region.end();
|
|
if (it == end)
|
|
return;
|
|
QPainter p(m_image->image());
|
|
while (it != end) {
|
|
const QRect rect = *(it++);
|
|
p.drawImage(rect.topLeft(), m_rgbImage.copy(rect).rgbSwapped());
|
|
}
|
|
}
|
|
|
|
QImage QXcbBackingStore::toImage() const
|
|
{
|
|
// If the backingstore is rgbSwapped, return the internal image type here.
|
|
if (!m_rgbImage.isNull())
|
|
return m_rgbImage;
|
|
|
|
if (!m_image || !m_image->image())
|
|
return QImage();
|
|
|
|
m_image->flushScrolledRegion(true);
|
|
|
|
QImage image = *m_image->image();
|
|
|
|
// Return an image that does not share QImageData with the original image,
|
|
// even if they both point to the same data of the m_xcb_image, otherwise
|
|
// painting to m_qimage would detach it from the m_xcb_image data.
|
|
return QImage(image.constBits(), image.width(), image.height(), image.format());
|
|
}
|
|
|
|
QPlatformGraphicsBuffer *QXcbBackingStore::graphicsBuffer() const
|
|
{
|
|
return m_image ? m_image->graphicsBuffer() : nullptr;
|
|
}
|
|
|
|
void QXcbBackingStore::flush(QWindow *window, const QRegion ®ion, const QPoint &offset)
|
|
{
|
|
if (!m_image || m_image->size().isEmpty())
|
|
return;
|
|
|
|
m_image->flushScrolledRegion(false);
|
|
|
|
QSize imageSize = m_image->size();
|
|
|
|
QRegion clipped = region;
|
|
clipped &= QRect(QPoint(), QHighDpi::toNativePixels(window->size(), window));
|
|
clipped &= QRect(0, 0, imageSize.width(), imageSize.height()).translated(-offset);
|
|
|
|
QRect bounds = clipped.boundingRect();
|
|
|
|
if (bounds.isNull())
|
|
return;
|
|
|
|
QXcbWindow *platformWindow = static_cast<QXcbWindow *>(window->handle());
|
|
if (!platformWindow) {
|
|
qCWarning(lcQpaXcb, "%s QWindow has no platform window, see QTBUG-32681", Q_FUNC_INFO);
|
|
return;
|
|
}
|
|
|
|
render(platformWindow->xcb_window(), clipped, offset);
|
|
|
|
if (platformWindow->needsSync())
|
|
platformWindow->updateSyncRequestCounter();
|
|
else
|
|
xcb_flush(xcb_connection());
|
|
}
|
|
|
|
void QXcbBackingStore::render(xcb_window_t window, const QRegion ®ion, const QPoint &offset)
|
|
{
|
|
m_image->put(window, region, offset);
|
|
}
|
|
|
|
QPlatformBackingStore::FlushResult QXcbBackingStore::rhiFlush(QWindow *window,
|
|
const QRegion ®ion,
|
|
const QPoint &offset,
|
|
QPlatformTextureList *textures,
|
|
bool translucentBackground)
|
|
{
|
|
if (!m_image || m_image->size().isEmpty())
|
|
return FlushFailed;
|
|
|
|
m_image->flushScrolledRegion(true);
|
|
|
|
QPlatformBackingStore::rhiFlush(window, region, offset, textures, translucentBackground);
|
|
|
|
QXcbWindow *platformWindow = static_cast<QXcbWindow *>(window->handle());
|
|
if (platformWindow->needsSync()) {
|
|
platformWindow->updateSyncRequestCounter();
|
|
} else {
|
|
xcb_flush(xcb_connection());
|
|
}
|
|
|
|
return FlushSuccess;
|
|
}
|
|
|
|
void QXcbBackingStore::resize(const QSize &size, const QRegion &)
|
|
{
|
|
if (m_image && size == m_image->size())
|
|
return;
|
|
|
|
QPlatformWindow *pw = window()->handle();
|
|
if (!pw) {
|
|
window()->create();
|
|
pw = window()->handle();
|
|
}
|
|
QXcbWindow* win = static_cast<QXcbWindow *>(pw);
|
|
|
|
recreateImage(win, size);
|
|
}
|
|
|
|
void QXcbBackingStore::recreateImage(QXcbWindow *win, const QSize &size)
|
|
{
|
|
if (m_image)
|
|
m_image->resize(size);
|
|
else
|
|
m_image = new QXcbBackingStoreImage(this, size);
|
|
|
|
// Slow path for bgr888 VNC: Create an additional image, paint into that and
|
|
// swap R and B while copying to m_image after each paint.
|
|
if (win->imageNeedsRgbSwap()) {
|
|
m_rgbImage = QImage(size, win->imageFormat());
|
|
}
|
|
}
|
|
|
|
bool QXcbBackingStore::scroll(const QRegion &area, int dx, int dy)
|
|
{
|
|
if (m_image)
|
|
return m_image->scroll(area, dx, dy);
|
|
|
|
return false;
|
|
}
|
|
|
|
QXcbSystemTrayBackingStore::QXcbSystemTrayBackingStore(QWindow *window)
|
|
: QXcbBackingStore(window)
|
|
{
|
|
// We need three different behaviors depending on whether the X11 visual
|
|
// for the system tray supports an alpha channel, i.e. is 32 bits, and
|
|
// whether XRender can be used:
|
|
// 1) if the visual has an alpha channel, then render the window's buffer
|
|
// directly to the X11 window as usual
|
|
// 2) else if XRender can be used, then render the window's buffer to Pixmap,
|
|
// then render Pixmap's contents to the cleared X11 window with
|
|
// xcb_render_composite()
|
|
// 3) else grab the X11 window's content and paint it first each time as a
|
|
// background before rendering the window's buffer to the X11 window
|
|
|
|
auto *platformWindow = static_cast<QXcbWindow *>(window->handle());
|
|
quint8 depth = connection()->primaryScreen()->depthOfVisual(platformWindow->visualId());
|
|
|
|
if (depth != 32) {
|
|
platformWindow->setParentRelativeBackPixmap();
|
|
initXRenderMode();
|
|
m_useGrabbedBackgound = !m_usingXRenderMode;
|
|
}
|
|
}
|
|
|
|
QXcbSystemTrayBackingStore::~QXcbSystemTrayBackingStore()
|
|
{
|
|
if (m_xrenderPicture) {
|
|
xcb_render_free_picture(xcb_connection(), m_xrenderPicture);
|
|
m_xrenderPicture = XCB_NONE;
|
|
}
|
|
if (m_xrenderPixmap) {
|
|
xcb_free_pixmap(xcb_connection(), m_xrenderPixmap);
|
|
m_xrenderPixmap = XCB_NONE;
|
|
}
|
|
if (m_windowPicture) {
|
|
xcb_render_free_picture(xcb_connection(), m_windowPicture);
|
|
m_windowPicture = XCB_NONE;
|
|
}
|
|
}
|
|
|
|
void QXcbSystemTrayBackingStore::beginPaint(const QRegion ®ion)
|
|
{
|
|
QXcbBackingStore::beginPaint(region);
|
|
|
|
if (m_useGrabbedBackgound) {
|
|
QPainter p(paintDevice());
|
|
p.setCompositionMode(QPainter::CompositionMode_Source);
|
|
for (const QRect &rect: region)
|
|
p.drawPixmap(rect, m_grabbedBackground, rect);
|
|
}
|
|
}
|
|
|
|
void QXcbSystemTrayBackingStore::render(xcb_window_t window, const QRegion ®ion, const QPoint &offset)
|
|
{
|
|
if (!m_usingXRenderMode) {
|
|
QXcbBackingStore::render(window, region, offset);
|
|
return;
|
|
}
|
|
|
|
m_image->put(m_xrenderPixmap, region, offset);
|
|
const QRect bounds = region.boundingRect();
|
|
const QPoint target = bounds.topLeft();
|
|
const QRect source = bounds.translated(offset);
|
|
xcb_clear_area(xcb_connection(), false, window,
|
|
target.x(), target.y(), source.width(), source.height());
|
|
xcb_render_composite(xcb_connection(), XCB_RENDER_PICT_OP_OVER,
|
|
m_xrenderPicture, 0, m_windowPicture,
|
|
target.x(), target.y(), 0, 0, target.x(), target.y(),
|
|
source.width(), source.height());
|
|
}
|
|
|
|
void QXcbSystemTrayBackingStore::recreateImage(QXcbWindow *win, const QSize &size)
|
|
{
|
|
if (!m_usingXRenderMode) {
|
|
QXcbBackingStore::recreateImage(win, size);
|
|
|
|
if (m_useGrabbedBackgound) {
|
|
xcb_clear_area(xcb_connection(), false, win->xcb_window(),
|
|
0, 0, size.width(), size.height());
|
|
m_grabbedBackground = win->xcbScreen()->grabWindow(win->winId(), 0, 0,
|
|
size.width(), size.height());
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (m_xrenderPicture) {
|
|
xcb_render_free_picture(xcb_connection(), m_xrenderPicture);
|
|
m_xrenderPicture = XCB_NONE;
|
|
}
|
|
if (m_xrenderPixmap) {
|
|
xcb_free_pixmap(xcb_connection(), m_xrenderPixmap);
|
|
m_xrenderPixmap = XCB_NONE;
|
|
}
|
|
|
|
QXcbScreen *screen = win->xcbScreen();
|
|
|
|
m_xrenderPixmap = xcb_generate_id(xcb_connection());
|
|
xcb_create_pixmap(xcb_connection(), 32, m_xrenderPixmap, screen->root(), size.width(), size.height());
|
|
|
|
m_xrenderPicture = xcb_generate_id(xcb_connection());
|
|
xcb_render_create_picture(xcb_connection(), m_xrenderPicture, m_xrenderPixmap, m_xrenderPictFormat, 0, nullptr);
|
|
|
|
// XRender expects premultiplied alpha
|
|
if (m_image)
|
|
m_image->resize(size);
|
|
else
|
|
m_image = new QXcbBackingStoreImage(this, size, 32, QImage::Format_ARGB32_Premultiplied);
|
|
}
|
|
|
|
void QXcbSystemTrayBackingStore::initXRenderMode()
|
|
{
|
|
if (!connection()->hasXRender())
|
|
return;
|
|
|
|
xcb_connection_t *conn = xcb_connection();
|
|
auto formatsReply = Q_XCB_REPLY(xcb_render_query_pict_formats, conn);
|
|
|
|
if (!formatsReply) {
|
|
qWarning("QXcbSystemTrayBackingStore: xcb_render_query_pict_formats() failed");
|
|
return;
|
|
}
|
|
|
|
xcb_render_pictforminfo_t *fmt = xcb_render_util_find_standard_format(formatsReply.get(),
|
|
XCB_PICT_STANDARD_ARGB_32);
|
|
if (!fmt) {
|
|
qWarning("QXcbSystemTrayBackingStore: Failed to find format PICT_STANDARD_ARGB_32");
|
|
return;
|
|
}
|
|
|
|
m_xrenderPictFormat = fmt->id;
|
|
|
|
auto *platformWindow = static_cast<QXcbWindow *>(window()->handle());
|
|
xcb_render_pictvisual_t *vfmt = xcb_render_util_find_visual_format(formatsReply.get(), platformWindow->visualId());
|
|
|
|
if (!vfmt) {
|
|
qWarning("QXcbSystemTrayBackingStore: Failed to find format for visual %x", platformWindow->visualId());
|
|
return;
|
|
}
|
|
|
|
m_windowPicture = xcb_generate_id(conn);
|
|
xcb_void_cookie_t cookie =
|
|
xcb_render_create_picture_checked(conn, m_windowPicture, platformWindow->xcb_window(), vfmt->format, 0, nullptr);
|
|
xcb_generic_error_t *error = xcb_request_check(conn, cookie);
|
|
if (error) {
|
|
qWarning("QXcbSystemTrayBackingStore: Failed to create Picture with format %x for window %x, error code %d",
|
|
vfmt->format, platformWindow->xcb_window(), error->error_code);
|
|
free(error);
|
|
return;
|
|
}
|
|
|
|
m_usingXRenderMode = true;
|
|
}
|
|
|
|
QT_END_NAMESPACE
|