mirror of
https://github.com/KDE/krfb
synced 2026-07-01 07:31:16 -07:00
The screen geometry is reported in device independent pixels, so it has to be multiplied by the devicePixelRatio to get the size of the underlying framebuffer. Otherwise, only the top left of the screen will be captured. This matches the behavior of QScreen::grabWindow(...), which also returns a QPixmap of the given size scaled by devicePixelRatio. BUG: 419814
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") {
|
|
xcb_generic_event_t* 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 (int i = 0; i < tiles.size(); i++) {
|
|
// if current rect has intersection with tiles[i], unite them
|
|
if (ri.intersects(tiles[i])) {
|
|
tiles[i] |= 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 (int i = 0; i < tiles.size(); i++) {
|
|
tiles[i].adjust(-30, -30, 30, 30);
|
|
if (tiles[i].top() < d->area.top()) {
|
|
tiles[i].setTop(d->area.top());
|
|
}
|
|
if (tiles[i].bottom() > d->area.bottom()) {
|
|
tiles[i].setBottom(d->area.bottom());
|
|
}
|
|
//
|
|
if (tiles[i].left() < d->area.left()) {
|
|
tiles[i].setLeft(d->area.left());
|
|
}
|
|
if (tiles[i].right() > d->area.right()) {
|
|
tiles[i].setRight(d->area.right());
|
|
}
|
|
// move update rects so that they are positioned relative to
|
|
// framebuffer image, not whole screen
|
|
tiles[i].moveTo(tiles[i].left() - d->area.left(),
|
|
tiles[i].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 : qAsConst(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) {
|
|
xcb_damage_notify_event_t *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);
|
|
}
|
|
|