mirror of
https://invent.kde.org/network/krfb
synced 2024-07-05 09:28:35 +00:00
692 lines
26 KiB
C++
692 lines
26 KiB
C++
/* This file is part of the KDE project
|
|
Copyright (C) 2017 Alexey Min <alexey.min@gmail.com>
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2 of the License, or (at your option) any later version.
|
|
*/
|
|
|
|
#include "xcb_framebuffer.h"
|
|
#include "krfb_fb_xcb_debug.h"
|
|
|
|
#include <xcb/xcb.h>
|
|
#include <xcb/xproto.h>
|
|
#include <xcb/damage.h>
|
|
#include <xcb/shm.h>
|
|
#include <xcb/xcb_image.h>
|
|
|
|
#include <sys/ipc.h>
|
|
#include <sys/shm.h>
|
|
|
|
#include <QX11Info>
|
|
#include <QCoreApplication>
|
|
#include <QGuiApplication>
|
|
#include <QScreen>
|
|
#include <QAbstractNativeEventFilter>
|
|
|
|
|
|
class KrfbXCBEventFilter: public QAbstractNativeEventFilter
|
|
{
|
|
public:
|
|
KrfbXCBEventFilter(XCBFrameBuffer *owner);
|
|
|
|
public:
|
|
bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) override;
|
|
|
|
public:
|
|
int xdamageBaseEvent;
|
|
int xdamageBaseError;
|
|
int xshmBaseEvent;
|
|
int xshmBaseError;
|
|
bool xshmAvail;
|
|
XCBFrameBuffer *fb_owner;
|
|
};
|
|
|
|
|
|
|
|
KrfbXCBEventFilter::KrfbXCBEventFilter(XCBFrameBuffer *owner):
|
|
xdamageBaseEvent(0), xdamageBaseError(0),
|
|
xshmBaseEvent(0), xshmBaseError(0), xshmAvail(false),
|
|
fb_owner(owner)
|
|
{
|
|
const xcb_query_extension_reply_t *xdamage_data = xcb_get_extension_data(
|
|
QX11Info::connection(), &xcb_damage_id);
|
|
if (xdamage_data) {
|
|
// also query extension version!
|
|
// ATTENTION: if we don't do that, xcb_damage_create() will always FAIL!
|
|
xcb_damage_query_version_reply_t *xdamage_version = xcb_damage_query_version_reply(
|
|
QX11Info::connection(),
|
|
xcb_damage_query_version(
|
|
QX11Info::connection(),
|
|
XCB_DAMAGE_MAJOR_VERSION,
|
|
XCB_DAMAGE_MINOR_VERSION),
|
|
nullptr);
|
|
if (!xdamage_version) {
|
|
qWarning() << "xcb framebuffer: ERROR: Failed to get XDamage extension version!\n";
|
|
return;
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
qCDebug(KRFB_FB_XCB) << "xcb framebuffer: XDamage extension version:" <<
|
|
xdamage_version->major_version << "." << xdamage_version->minor_version;
|
|
#endif
|
|
|
|
free(xdamage_version);
|
|
|
|
xdamageBaseEvent = xdamage_data->first_event;
|
|
xdamageBaseError = xdamage_data->first_error;
|
|
|
|
// XShm presence is optional. If it is present, all image getting
|
|
// operations will be faster, without XShm it will only be slower.
|
|
const xcb_query_extension_reply_t *xshm_data = xcb_get_extension_data(
|
|
QX11Info::connection(), &xcb_shm_id);
|
|
if (xshm_data) {
|
|
xshmAvail = true;
|
|
xshmBaseEvent = xshm_data->first_event;
|
|
xshmBaseError = xshm_data->first_error;
|
|
} else {
|
|
xshmAvail = false;
|
|
qWarning() << "xcb framebuffer: WARNING: XSHM extension not available!";
|
|
}
|
|
} else {
|
|
// if there is no xdamage available, this plugin can be considered useless anyway.
|
|
// you can use just qt framebuffer plugin instead...
|
|
qWarning() << "xcb framebuffer: ERROR: no XDamage extension available. I am useless.";
|
|
qWarning() << "xcb framebuffer: use qt framebuffer plugin instead.";
|
|
}
|
|
}
|
|
|
|
|
|
bool KrfbXCBEventFilter::nativeEventFilter(const QByteArray &eventType,
|
|
void *message, long *result) {
|
|
Q_UNUSED(result); // "result" is only used on windows
|
|
|
|
if (xdamageBaseEvent == 0) return false; // no xdamage extension
|
|
|
|
if (eventType == "xcb_generic_event_t") {
|
|
auto ev = static_cast<xcb_generic_event_t *>(message);
|
|
if ((ev->response_type & 0x7F) == (xdamageBaseEvent + XCB_DAMAGE_NOTIFY)) {
|
|
// this is xdamage notification
|
|
this->fb_owner->handleXDamageNotify(ev);
|
|
return true; // filter out this event, stop its processing
|
|
}
|
|
}
|
|
|
|
// continue event processing
|
|
return false;
|
|
}
|
|
|
|
|
|
class XCBFrameBuffer::P {
|
|
public:
|
|
xcb_damage_damage_t damage;
|
|
xcb_shm_segment_info_t shminfo;
|
|
xcb_screen_t *rootScreen; // X screen info (all monitors)
|
|
xcb_image_t *framebufferImage;
|
|
xcb_image_t *updateTile;
|
|
|
|
KrfbXCBEventFilter *x11EvtFilter;
|
|
|
|
bool running;
|
|
|
|
QRect area; // capture area, primary monitor coordinates
|
|
};
|
|
|
|
|
|
static xcb_screen_t *get_xcb_screen(xcb_connection_t *conn, int screen_num) {
|
|
xcb_screen_t *screen = nullptr;
|
|
xcb_screen_iterator_t screens_iter = xcb_setup_roots_iterator(xcb_get_setup(conn));
|
|
for (; screens_iter.rem; --screen_num, xcb_screen_next(&screens_iter))
|
|
if (screen_num == 0)
|
|
screen = screens_iter.data;
|
|
return screen;
|
|
}
|
|
|
|
|
|
|
|
XCBFrameBuffer::XCBFrameBuffer(WId winid, QObject *parent):
|
|
FrameBuffer(winid, parent), d(new XCBFrameBuffer::P)
|
|
{
|
|
d->running = false;
|
|
d->damage = XCB_NONE;
|
|
d->framebufferImage = nullptr;
|
|
d->shminfo.shmaddr = nullptr;
|
|
d->shminfo.shmid = XCB_NONE;
|
|
d->shminfo.shmseg = XCB_NONE;
|
|
d->updateTile = nullptr;
|
|
d->area.setRect(0, 0, 0, 0);
|
|
d->x11EvtFilter = new KrfbXCBEventFilter(this);
|
|
d->rootScreen = get_xcb_screen(QX11Info::connection(), QX11Info::appScreen());
|
|
|
|
this->fb = nullptr;
|
|
|
|
QScreen *primaryScreen = QGuiApplication::primaryScreen();
|
|
if (primaryScreen) {
|
|
qreal scaleFactor = primaryScreen->devicePixelRatio();
|
|
d->area = { primaryScreen->geometry().topLeft() * scaleFactor,
|
|
primaryScreen->geometry().bottomRight() * scaleFactor };
|
|
|
|
qCDebug(KRFB_FB_XCB) << "xcb framebuffer: Primary screen: " << primaryScreen->name()
|
|
<< ", geometry: " << primaryScreen->geometry()
|
|
<< ", device scaling: " << scaleFactor
|
|
<< ", native size: " << d->area
|
|
<< ", depth: " << primaryScreen->depth();
|
|
} else {
|
|
qWarning() << "xcb framebuffer: ERROR: Failed to get application's primary screen info!";
|
|
return;
|
|
}
|
|
|
|
d->framebufferImage = xcb_image_get(QX11Info::connection(),
|
|
this->win,
|
|
d->area.left(),
|
|
d->area.top(),
|
|
d->area.width(),
|
|
d->area.height(),
|
|
0xFFFFFFFF, // == Xlib: AllPlanes ((unsigned long)~0L)
|
|
XCB_IMAGE_FORMAT_Z_PIXMAP);
|
|
if (d->framebufferImage) {
|
|
#ifdef _DEBUG
|
|
qCDebug(KRFB_FB_XCB) << "xcb framebuffer: Got primary screen image. bpp: " << d->framebufferImage->bpp
|
|
<< ", size (" << d->framebufferImage->width << d->framebufferImage->height << ")"
|
|
<< ", depth: " << d->framebufferImage->depth
|
|
<< ", padded width: " << d->framebufferImage->stride;
|
|
#endif
|
|
this->fb = (char *)d->framebufferImage->data;
|
|
} else {
|
|
qWarning() << "xcb framebuffer: ERROR: Failed to get primary screen image!";
|
|
return;
|
|
}
|
|
|
|
// all XShm operations should take place only if Xshm extension was loaded
|
|
if (d->x11EvtFilter->xshmAvail) {
|
|
// Create xcb_image_t structure, but do not automatically allocate memory
|
|
// for image data storage - it will be allocated as shared memory.
|
|
// "If base == 0 and bytes == ~0 and data == 0, no storage will be auto-allocated."
|
|
// Width and height of the image = size of the capture area.
|
|
d->updateTile = xcb_image_create_native(
|
|
QX11Info::connection(),
|
|
d->area.width(), // width
|
|
d->area.height(), // height
|
|
XCB_IMAGE_FORMAT_Z_PIXMAP, // image format
|
|
d->rootScreen->root_depth, // depth
|
|
nullptr, // base address = 0
|
|
(uint32_t)~0, // bytes = 0xffffffff
|
|
nullptr); // data = 0
|
|
if (d->updateTile) {
|
|
#ifdef _DEBUG
|
|
qCDebug(KRFB_FB_XCB) << "xcb framebuffer: Successfully created new empty image in native format"
|
|
<< "\n size: " << d->updateTile->width << "x" << d->updateTile->height
|
|
<< "(stride: " << d->updateTile->stride << ")"
|
|
<< "\n bpp, depth: " << d->updateTile->bpp << d->updateTile->depth // 32, 24
|
|
<< "\n addr of base, data: " << d->updateTile->base << (void *)d->updateTile->data
|
|
<< "\n size: " << d->updateTile->size
|
|
<< "\n image byte order = " << d->updateTile->byte_order // == 0 .._LSB_FIRST
|
|
<< "\n image bit order = " << d->updateTile->bit_order // == 1 .._MSB_FIRST
|
|
<< "\n image plane_mask = " << d->updateTile->plane_mask; // == 16777215 == 0x00FFFFFF
|
|
#endif
|
|
|
|
// allocate shared memory block only once, make its size large enough
|
|
// to fit whole capture area (d->area rect)
|
|
// so, we get as many bytes as needed for image (updateTile->size)
|
|
d->shminfo.shmid = shmget(IPC_PRIVATE, d->updateTile->size, IPC_CREAT | 0777);
|
|
// attached shared memory address is stored both in shminfo structure and in xcb_image_t->data
|
|
d->shminfo.shmaddr = (uint8_t *)shmat(d->shminfo.shmid, nullptr, 0);
|
|
d->updateTile->data = d->shminfo.shmaddr;
|
|
// we keep updateTile->base == NULL here, so xcb_image_destroy() will not attempt
|
|
// to free this block, just in case.
|
|
|
|
// attach this shm segment also to X server
|
|
d->shminfo.shmseg = xcb_generate_id(QX11Info::connection());
|
|
xcb_shm_attach(QX11Info::connection(), d->shminfo.shmseg, d->shminfo.shmid, 0);
|
|
|
|
#ifdef _DEBUG
|
|
qCDebug(KRFB_FB_XCB) << " shm id: " << d->shminfo.shmseg << ", addr: " << (void *)d->shminfo.shmaddr;
|
|
#endif
|
|
|
|
// will return 1 on success (yes!)
|
|
int shmget_res = xcb_image_shm_get(
|
|
QX11Info::connection(),
|
|
this->win,
|
|
d->updateTile,
|
|
d->shminfo,
|
|
d->area.left(), // x
|
|
d->area.top(), // y (size taken from image structure itself)?
|
|
0xFFFFFFFF);
|
|
|
|
if (shmget_res == 0) {
|
|
// error! shared mem not working?
|
|
// will not use shared mem! detach and cleanup
|
|
xcb_shm_detach(QX11Info::connection(), d->shminfo.shmseg);
|
|
shmdt(d->shminfo.shmaddr);
|
|
shmctl(d->shminfo.shmid, IPC_RMID, nullptr); // mark shm segment as removed
|
|
d->x11EvtFilter->xshmAvail = false;
|
|
d->shminfo.shmaddr = nullptr;
|
|
d->shminfo.shmid = XCB_NONE;
|
|
d->shminfo.shmseg = XCB_NONE;
|
|
qWarning() << "xcb framebuffer: ERROR: xcb_image_shm_get() result: " << shmget_res;
|
|
}
|
|
|
|
// image is freed, and recreated again for every new damage rectangle
|
|
// data was allocated manually and points to shared mem;
|
|
// tell xcb_image_destroy() do not free image data
|
|
d->updateTile->data = nullptr;
|
|
xcb_image_destroy(d->updateTile);
|
|
d->updateTile = nullptr;
|
|
}
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
qCDebug(KRFB_FB_XCB) << "xcb framebuffer: XCBFrameBuffer(), xshm base event = " << d->x11EvtFilter->xshmBaseEvent
|
|
<< ", xshm base error = " << d->x11EvtFilter->xdamageBaseError
|
|
<< ", xdamage base event = " << d->x11EvtFilter->xdamageBaseEvent
|
|
<< ", xdamage base error = " << d->x11EvtFilter->xdamageBaseError;
|
|
#endif
|
|
|
|
QCoreApplication::instance()->installNativeEventFilter(d->x11EvtFilter);
|
|
}
|
|
|
|
|
|
XCBFrameBuffer::~XCBFrameBuffer() {
|
|
// first - uninstall x11 event filter
|
|
QCoreApplication::instance()->removeNativeEventFilter(d->x11EvtFilter);
|
|
//
|
|
if (d->framebufferImage) {
|
|
xcb_image_destroy(d->framebufferImage);
|
|
fb = nullptr; // image data was already destroyed by above call
|
|
}
|
|
if (d->x11EvtFilter->xshmAvail) {
|
|
// detach shared memory
|
|
if (d->shminfo.shmseg != XCB_NONE)
|
|
xcb_shm_detach(QX11Info::connection(), d->shminfo.shmseg); // detach from X server
|
|
if (d->shminfo.shmaddr)
|
|
shmdt(d->shminfo.shmaddr); // detach addr from our address space
|
|
if (d->shminfo.shmid != XCB_NONE)
|
|
shmctl(d->shminfo.shmid, IPC_RMID, nullptr); // mark shm segment as removed
|
|
}
|
|
// and delete image used for shared mem
|
|
if (d->updateTile) {
|
|
d->updateTile->base = nullptr;
|
|
d->updateTile->data = nullptr;
|
|
xcb_image_destroy(d->updateTile);
|
|
}
|
|
// we don't use d->x11EvtFilter anymore, can delete it now
|
|
if (d->x11EvtFilter) {
|
|
delete d->x11EvtFilter;
|
|
}
|
|
delete d;
|
|
}
|
|
|
|
|
|
int XCBFrameBuffer::depth() {
|
|
if (d->framebufferImage) {
|
|
return d->framebufferImage->depth;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
int XCBFrameBuffer::height() {
|
|
if (d->framebufferImage) {
|
|
return d->framebufferImage->height;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
int XCBFrameBuffer::width() {
|
|
if (d->framebufferImage) {
|
|
return d->framebufferImage->width;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int XCBFrameBuffer::paddedWidth() {
|
|
if (d->framebufferImage) {
|
|
return d->framebufferImage->stride;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
void XCBFrameBuffer::getServerFormat(rfbPixelFormat &format) {
|
|
if (!d->framebufferImage) return;
|
|
|
|
// get information about XCB visual params
|
|
xcb_visualtype_t *root_visualtype = nullptr; // visual info
|
|
if (d->rootScreen) {
|
|
xcb_visualid_t root_visual = d->rootScreen->root_visual;
|
|
xcb_depth_iterator_t depth_iter;
|
|
// To get the xcb_visualtype_t structure, it's a bit less easy.
|
|
// You have to get the xcb_screen_t structure that you want, get its
|
|
// root_visual member, then iterate over the xcb_depth_t's and the
|
|
// xcb_visualtype_t's, and compare the xcb_visualid_t of these
|
|
// xcb_visualtype_ts: with root_visual
|
|
depth_iter = xcb_screen_allowed_depths_iterator(d->rootScreen);
|
|
for (; depth_iter.rem; xcb_depth_next(&depth_iter)) {
|
|
xcb_visualtype_iterator_t visual_iter;
|
|
visual_iter = xcb_depth_visuals_iterator(depth_iter.data);
|
|
for (; visual_iter.rem; xcb_visualtype_next(&visual_iter)) {
|
|
if (root_visual == visual_iter.data->visual_id) {
|
|
root_visualtype = visual_iter.data;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// fill in format common info
|
|
format.bitsPerPixel = d->framebufferImage->bpp;
|
|
format.depth = d->framebufferImage->depth;
|
|
format.trueColour = true; // not using color palettes
|
|
format.bigEndian = false; // always false for ZPIXMAP format!
|
|
|
|
// information about pixels layout
|
|
|
|
if (root_visualtype) {
|
|
#ifdef _DEBUG
|
|
qDebug("xcb framebuffer: Got info about root visual:\n"
|
|
" bits per rgb value: %d\n"
|
|
" red mask: %08x\n"
|
|
" green mask: %08x\n"
|
|
" blue mask: %08x\n",
|
|
(int)root_visualtype->bits_per_rgb_value,
|
|
root_visualtype->red_mask,
|
|
root_visualtype->green_mask,
|
|
root_visualtype->blue_mask);
|
|
#endif
|
|
|
|
// calculate shifts
|
|
format.redShift = 0;
|
|
if (root_visualtype->red_mask) {
|
|
while (!(root_visualtype->red_mask & (1 << format.redShift))) {
|
|
format.redShift++;
|
|
}
|
|
}
|
|
format.greenShift = 0;
|
|
if (root_visualtype->green_mask) {
|
|
while (!(root_visualtype->green_mask & (1 << format.greenShift))) {
|
|
format.greenShift++;
|
|
}
|
|
}
|
|
format.blueShift = 0;
|
|
if (root_visualtype->blue_mask) {
|
|
while (!(root_visualtype->blue_mask & (1 << format.blueShift))) {
|
|
format.blueShift++;
|
|
}
|
|
}
|
|
|
|
// calculate pixel max value.
|
|
// NOTE: bits_per_rgb_value is unreliable, thus should be avoided.
|
|
format.redMax = root_visualtype->red_mask >> format.redShift;
|
|
format.greenMax = root_visualtype->green_mask >> format.greenShift;
|
|
format.blueMax = root_visualtype->blue_mask >> format.blueShift;
|
|
|
|
#ifdef _DEBUG
|
|
qCDebug(KRFB_FB_XCB,
|
|
" Calculated redShift = %d\n"
|
|
" Calculated greenShift = %d\n"
|
|
" Calculated blueShift = %d\n"
|
|
" Calculated max values: R%d G%d B%d",
|
|
format.redShift, format.greenShift, format.blueShift
|
|
format.redMax, format.greenMax, format.blueMax);
|
|
#endif
|
|
} else {
|
|
// some kind of fallback (unlikely code execution will go this way)
|
|
// idea taken from qt framefuffer sources
|
|
if (format.bitsPerPixel == 8) {
|
|
format.redShift = 0;
|
|
format.greenShift = 3;
|
|
format.blueShift = 6;
|
|
format.redMax = 7;
|
|
format.greenMax = 7;
|
|
format.blueMax = 3;
|
|
} else if (format.bitsPerPixel == 16) {
|
|
// TODO: 16 bits per pixel format ??
|
|
// what format of pixels does X server use for 16-bpp?
|
|
} else if (format.bitsPerPixel == 32) {
|
|
format.redMax = 0xff;
|
|
format.greenMax = 0xff;
|
|
format.blueMax = 0xff;
|
|
if (format.bigEndian) {
|
|
format.redShift = 0;
|
|
format.greenShift = 8;
|
|
format.blueShift = 16;
|
|
} else {
|
|
format.redShift = 16;
|
|
format.greenShift = 8;
|
|
format.blueShift = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* This function contents was taken from X11 framebuffer source code.
|
|
* It simply several intersecting rectangles into one bigger rect.
|
|
* Non-intersecting rects are treated as different rects and exist
|
|
* separately in this->tiles QList.
|
|
*/
|
|
void XCBFrameBuffer::cleanupRects() {
|
|
QList<QRect> cpy = tiles;
|
|
bool inserted = false;
|
|
tiles.clear();
|
|
|
|
QListIterator<QRect> iter(cpy);
|
|
while (iter.hasNext()) {
|
|
const QRect &r = iter.next();
|
|
// skip rects not intersecting with primary monitor
|
|
if (!r.intersects(d->area)) continue;
|
|
// only take intersection of this rect with primary monitor rect
|
|
QRect ri = r.intersected(d->area);
|
|
|
|
if (tiles.size() > 0) {
|
|
for (auto &tile : tiles) {
|
|
// if current rect has intersection with tile, unite them
|
|
if (ri.intersects(tile)) {
|
|
tile |= ri;
|
|
inserted = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!inserted) {
|
|
// else, append to list as different rect
|
|
tiles.append(ri);
|
|
}
|
|
} else {
|
|
// tiles list is empty, append first item
|
|
tiles.append(ri);
|
|
}
|
|
}
|
|
|
|
// increase all rectangles size by 30 pixels each side.
|
|
// limit coordinates to primary monitor boundaries.
|
|
for (auto &tile : tiles) {
|
|
tile.adjust(-30, -30, 30, 30);
|
|
if (tile.top() < d->area.top()) {
|
|
tile.setTop(d->area.top());
|
|
}
|
|
if (tile.bottom() > d->area.bottom()) {
|
|
tile.setBottom(d->area.bottom());
|
|
}
|
|
//
|
|
if (tile.left() < d->area.left()) {
|
|
tile.setLeft(d->area.left());
|
|
}
|
|
if (tile.right() > d->area.right()) {
|
|
tile.setRight(d->area.right());
|
|
}
|
|
// move update rects so that they are positioned relative to
|
|
// framebuffer image, not whole screen
|
|
tile.moveTo(tile.left() - d->area.left(),
|
|
tile.top() - d->area.top());
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* This function is called by RfbServerManager::updateScreens()
|
|
* approximately every 50ms (!!), driven by QTimer to get all
|
|
* modified rectangles on the screen
|
|
*/
|
|
QList<QRect> XCBFrameBuffer::modifiedTiles() {
|
|
QList<QRect> ret;
|
|
if (!d->running) {
|
|
return ret;
|
|
}
|
|
|
|
cleanupRects();
|
|
|
|
if (tiles.size() > 0) {
|
|
if (d->x11EvtFilter->xshmAvail) {
|
|
|
|
// loop over all damage rectangles gathered up to this time
|
|
QListIterator<QRect> iter(tiles);
|
|
//foreach(const QRect &r, tiles) {
|
|
while (iter.hasNext()) {
|
|
const QRect &r = iter.next();
|
|
|
|
// get image data into shared memory segment
|
|
// now rects are positioned relative to framebufferImage,
|
|
// but we need to get image from the whole screen, so
|
|
// translate whe coordinates
|
|
xcb_shm_get_image_cookie_t sgi_cookie = xcb_shm_get_image(
|
|
QX11Info::connection(),
|
|
this->win,
|
|
d->area.left() + r.left(),
|
|
d->area.top() + r.top(),
|
|
r.width(),
|
|
r.height(),
|
|
0xFFFFFFFF,
|
|
XCB_IMAGE_FORMAT_Z_PIXMAP,
|
|
d->shminfo.shmseg,
|
|
0);
|
|
|
|
xcb_shm_get_image_reply_t *sgi_reply = xcb_shm_get_image_reply(
|
|
QX11Info::connection(), sgi_cookie, nullptr);
|
|
if (sgi_reply) {
|
|
// create temporary image to get update rect contents into
|
|
d->updateTile = xcb_image_create_native(
|
|
QX11Info::connection(),
|
|
r.width(),
|
|
r.height(),
|
|
XCB_IMAGE_FORMAT_Z_PIXMAP,
|
|
d->rootScreen->root_depth,
|
|
nullptr, // base == 0
|
|
(uint32_t)~0, // bytes == ~0
|
|
nullptr);
|
|
|
|
if (d->updateTile) {
|
|
d->updateTile->data = d->shminfo.shmaddr;
|
|
|
|
// copy pixels from this damage rectangle image
|
|
// to our total framebuffer image
|
|
int pxsize = d->framebufferImage->bpp / 8;
|
|
char *dest = fb + ((d->framebufferImage->stride * r.top()) + (r.left() * pxsize));
|
|
char *src = (char *)d->updateTile->data;
|
|
for (int i = 0; i < d->updateTile->height; i++) {
|
|
memcpy(dest, src, d->updateTile->stride); // copy whole row of pixels
|
|
dest += d->framebufferImage->stride;
|
|
src += d->updateTile->stride;
|
|
}
|
|
|
|
// delete temporary image
|
|
d->updateTile->data = nullptr;
|
|
xcb_image_destroy(d->updateTile);
|
|
d->updateTile = nullptr;
|
|
}
|
|
|
|
free(sgi_reply);
|
|
}
|
|
} // foreach
|
|
} else {
|
|
// not using shared memory
|
|
// will use just xcb_image_get() and copy pixels
|
|
for (const QRect& r : std::as_const(tiles)) {
|
|
// I did not find XGetSubImage() analog in XCB!!
|
|
// need function that copies pixels from one image to another
|
|
xcb_image_t *damagedImage = xcb_image_get(
|
|
QX11Info::connection(),
|
|
this->win,
|
|
r.left(),
|
|
r.top(),
|
|
r.width(),
|
|
r.height(),
|
|
0xFFFFFFFF, // AllPlanes
|
|
XCB_IMAGE_FORMAT_Z_PIXMAP);
|
|
// manually copy pixels
|
|
int pxsize = d->framebufferImage->bpp / 8;
|
|
char *dest = fb + ((d->framebufferImage->stride * r.top()) + (r.left() * pxsize));
|
|
char *src = (char *)damagedImage->data;
|
|
// loop every row in damaged image
|
|
for (int i = 0; i < damagedImage->height; i++) {
|
|
// copy whole row of pixels from src image to dest
|
|
memcpy(dest, src, damagedImage->stride);
|
|
dest += d->framebufferImage->stride; // move 1 row down in dest
|
|
src += damagedImage->stride; // move 1 row down in src
|
|
}
|
|
//
|
|
xcb_image_destroy(damagedImage);
|
|
}
|
|
}
|
|
} // if (tiles.size() > 0)
|
|
|
|
ret = tiles;
|
|
tiles.clear();
|
|
// ^^ If we clear here all our known "damage areas", then we can also clear
|
|
// damaged area for xdamage? No, we don't need to in our case
|
|
// (XCB_DAMAGE_REPORT_LEVEL_RAW_RECTANGLES report mode)
|
|
//xcb_damage_subtract(QX11Info::connection(), d->damage, XCB_NONE, XCB_NONE);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
void XCBFrameBuffer::startMonitor() {
|
|
if (d->running) return;
|
|
|
|
d->running = true;
|
|
d->damage = xcb_generate_id(QX11Info::connection());
|
|
xcb_damage_create(QX11Info::connection(), d->damage, this->win,
|
|
XCB_DAMAGE_REPORT_LEVEL_RAW_RECTANGLES);
|
|
|
|
// (currently) we do not call xcb_damage_subtract() EVER, because
|
|
// RAW rectangles are reported. every time some area of the screen
|
|
// was changed, we get only that rectangle
|
|
//xcb_damage_subtract(QX11Info::connection(), d->damage, XCB_NONE, XCB_NONE);
|
|
}
|
|
|
|
|
|
void XCBFrameBuffer::stopMonitor() {
|
|
if (!d->running) return;
|
|
d->running = false;
|
|
xcb_damage_destroy(QX11Info::connection(), d->damage);
|
|
}
|
|
|
|
|
|
// void XCBFrameBuffer::acquireEvents() {} // this function was totally unused
|
|
// in X11 framebuffer, but it was the only function where XDamageSubtract() was called?
|
|
// Also it had a blocking event loop like:
|
|
//
|
|
// XEvent ev;
|
|
// while (XCheckTypedEvent(QX11Info::display(), d->xdamageBaseEvent + XDamageNotify, &ev)) {
|
|
// handleXDamage(&ev);
|
|
// }
|
|
// XDamageSubtract(QX11Info::display(), d->damage, None, None);
|
|
//
|
|
// This loop takes all available Xdamage events from queue, and ends if there are no
|
|
// more such events in input queue.
|
|
|
|
|
|
void XCBFrameBuffer::handleXDamageNotify(xcb_generic_event_t *xevent) {
|
|
auto xdevt = (xcb_damage_notify_event_t *)xevent;
|
|
|
|
QRect r((int)xdevt->area.x, (int)xdevt->area.y,
|
|
(int)xdevt->area.width, (int)xdevt->area.height);
|
|
this->tiles.append(r);
|
|
}
|
|
|