Skip to content
Permalink
master
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
594 lines (522 sloc) 22.9 KB
/*
Copyright (c) 2012-2020 Maarten Baert <maarten-baert@hotmail.com>
This file contains code from x11grab.c (part of ffmpeg/libav). The copyright information for x11grab.c is:
>> FFmpeg/Libav integration:
>> Copyright (C) 2006 Clemens Fruhwirth <clemens@endorphin.org>
>> Edouard Gomez <ed.gomez@free.fr>
>>
>> This file contains code from grab.c:
>> Copyright (c) 2000-2001 Fabrice Bellard
>>
>> This file contains code from the xvidcap project:
>> Copyright (C) 1997-1998 Rasca, Berlin
>> 2003-2004 Karl H. Beckers, Frankfurt
This file is part of SimpleScreenRecorder.
SimpleScreenRecorder 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 3 of the License, or
(at your option) any later version.
SimpleScreenRecorder is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with SimpleScreenRecorder. If not, see <http://www.gnu.org/licenses/>.
*/
#include "X11Input.h"
#include "Logger.h"
#include "AVWrapper.h"
#include "Synchronizer.h"
#include "VideoEncoder.h"
/*
The code in this file is based on the MIT-SHM example code and the x11grab device in libav/ffmpeg (which is GPL):
http://www.xfree86.org/current/mit-shm.html
https://git.libav.org/?p=libav.git;a=blob;f=libavdevice/x11grab.c
I am doing the recording myself instead of just using x11grab (as I originally planned) because this is more flexible.
*/
// Converts a X11 image format to a format that libav/ffmpeg understands.
static AVPixelFormat X11ImageGetPixelFormat(XImage* image) {
switch(image->bits_per_pixel) {
case 8: return AV_PIX_FMT_PAL8;
case 16: {
if(image->red_mask == 0xf800 && image->green_mask == 0x07e0 && image->blue_mask == 0x001f) return AV_PIX_FMT_RGB565;
if(image->red_mask == 0x7c00 && image->green_mask == 0x03e0 && image->blue_mask == 0x001f) return AV_PIX_FMT_RGB555;
break;
}
case 24: {
if(image->red_mask == 0xff0000 && image->green_mask == 0x00ff00 && image->blue_mask == 0x0000ff) return AV_PIX_FMT_BGR24;
if(image->red_mask == 0x0000ff && image->green_mask == 0x00ff00 && image->blue_mask == 0xff0000) return AV_PIX_FMT_RGB24;
break;
}
case 32: {
if(image->red_mask == 0xff0000 && image->green_mask == 0x00ff00 && image->blue_mask == 0x0000ff) return AV_PIX_FMT_BGRA;
if(image->red_mask == 0x0000ff && image->green_mask == 0x00ff00 && image->blue_mask == 0xff0000) return AV_PIX_FMT_RGBA;
if(image->red_mask == 0xff000000 && image->green_mask == 0x00ff0000 && image->blue_mask == 0x0000ff00) return AV_PIX_FMT_ABGR;
if(image->red_mask == 0x0000ff00 && image->green_mask == 0x00ff0000 && image->blue_mask == 0xff000000) return AV_PIX_FMT_ARGB;
break;
}
}
Logger::LogError("[X11ImageGetPixelFormat] " + Logger::tr("Error: Unsupported X11 image pixel format!") + "\n"
" bits_per_pixel = " + QString::number(image->bits_per_pixel) + ", red_mask = 0x" + QString::number(image->red_mask, 16)
+ ", green_mask = 0x" + QString::number(image->green_mask, 16) + ", blue_mask = 0x" + QString::number(image->blue_mask, 16));
throw X11Exception();
}
// clears a rectangular area of an image (i.e. sets the memory to zero, which will most likely make the image black)
static void X11ImageClearRectangle(XImage* image, unsigned int x, unsigned int y, unsigned int w, unsigned int h) {
// check the image format
if(image->bits_per_pixel % 8 != 0)
return;
unsigned int pixel_bytes = image->bits_per_pixel / 8;
// fill the rectangle with zeros
for(unsigned int j = 0; j < h; ++j) {
uint8_t *image_row = (uint8_t*) image->data + image->bytes_per_line * (y + j);
memset(image_row + pixel_bytes * x, 0, pixel_bytes * w);
}
}
// Draws the current cursor at the current position on the image. Requires XFixes.
// Note: In the original code from x11grab, the variables for red and blue are swapped
// (which doesn't change the result, but it's confusing).
// Note 2: This function assumes little-endianness.
// Note 3: This function only supports 24-bit and 32-bit images (it does nothing for other bit depths).
static void X11ImageDrawCursor(Display* dpy, XImage* image, int recording_area_x, int recording_area_y) {
// check the image format
unsigned int pixel_bytes, r_offset, g_offset, b_offset;
if(image->bits_per_pixel == 24 && image->red_mask == 0xff0000 && image->green_mask == 0x00ff00 && image->blue_mask == 0x0000ff) {
pixel_bytes = 3;
r_offset = 2; g_offset = 1; b_offset = 0;
} else if(image->bits_per_pixel == 24 && image->red_mask == 0x0000ff && image->green_mask == 0x00ff00 && image->blue_mask == 0xff0000) {
pixel_bytes = 3;
r_offset = 0; g_offset = 1; b_offset = 2;
} else if(image->bits_per_pixel == 32 && image->red_mask == 0xff0000 && image->green_mask == 0x00ff00 && image->blue_mask == 0x0000ff) {
pixel_bytes = 4;
r_offset = 2; g_offset = 1; b_offset = 0;
} else if(image->bits_per_pixel == 32 && image->red_mask == 0x0000ff && image->green_mask == 0x00ff00 && image->blue_mask == 0xff0000) {
pixel_bytes = 4;
r_offset = 0; g_offset = 1; b_offset = 2;
} else if(image->bits_per_pixel == 32 && image->red_mask == 0xff000000 && image->green_mask == 0x00ff0000 && image->blue_mask == 0x0000ff00) {
pixel_bytes = 4;
r_offset = 3; g_offset = 2; b_offset = 1;
} else if(image->bits_per_pixel == 32 && image->red_mask == 0x0000ff00 && image->green_mask == 0x00ff0000 && image->blue_mask == 0xff000000) {
pixel_bytes = 4;
r_offset = 1; g_offset = 2; b_offset = 3;
} else {
return;
}
// get the cursor
XFixesCursorImage *xcim = XFixesGetCursorImage(dpy);
if(xcim == NULL)
return;
// calculate the position of the cursor
int x = xcim->x - xcim->xhot - recording_area_x;
int y = xcim->y - xcim->yhot - recording_area_y;
// calculate the part of the cursor that's visible
int cursor_left = std::max(0, -x), cursor_right = std::min((int) xcim->width, image->width - x);
int cursor_top = std::max(0, -y), cursor_bottom = std::min((int) xcim->height, image->height - y);
// draw the cursor
// XFixesCursorImage uses 'long' instead of 'int' to store the cursor images, which is a bit weird since
// 'long' is 64-bit on 64-bit systems and only 32 bits are actually used. The image uses premultiplied alpha.
for(int j = cursor_top; j < cursor_bottom; ++j) {
unsigned long *cursor_row = xcim->pixels + xcim->width * j;
uint8_t *image_row = (uint8_t*) image->data + image->bytes_per_line * (y + j);
for(int i = cursor_left; i < cursor_right; ++i) {
unsigned long cursor_pixel = cursor_row[i];
uint8_t *image_pixel = image_row + pixel_bytes * (x + i);
int cursor_a = (uint8_t) (cursor_pixel >> 24);
int cursor_r = (uint8_t) (cursor_pixel >> 16);
int cursor_g = (uint8_t) (cursor_pixel >> 8);
int cursor_b = (uint8_t) (cursor_pixel >> 0);
if(cursor_a == 255) {
image_pixel[r_offset] = cursor_r;
image_pixel[g_offset] = cursor_g;
image_pixel[b_offset] = cursor_b;
} else {
image_pixel[r_offset] = (image_pixel[r_offset] * (255 - cursor_a) + 127) / 255 + cursor_r;
image_pixel[g_offset] = (image_pixel[g_offset] * (255 - cursor_a) + 127) / 255 + cursor_g;
image_pixel[b_offset] = (image_pixel[b_offset] * (255 - cursor_a) + 127) / 255 + cursor_b;
}
}
}
// free the cursor
XFree(xcim);
}
X11Input::X11Input(unsigned int x, unsigned int y, unsigned int width, unsigned int height, bool record_cursor, bool follow_cursor, bool follow_full_screen) {
m_x = x;
m_y = y;
m_width = width;
m_height = height;
m_record_cursor = record_cursor;
m_follow_cursor = follow_cursor;
m_follow_fullscreen = follow_full_screen;
m_x11_display = NULL;
m_x11_image = NULL;
m_x11_shm_info.shmseg = 0;
m_x11_shm_info.shmid = -1;
m_x11_shm_info.shmaddr = (char*) -1;
m_x11_shm_info.readOnly = false;
m_x11_shm_server_attached = false;
m_screen_bbox = Rect(m_x, m_y, m_x + m_width, m_y + m_height);
{
SharedLock lock(&m_shared_data);
lock->m_current_x = m_x;
lock->m_current_y = m_y;
lock->m_current_width = m_width;
lock->m_current_height = m_height;
}
if(m_width == 0 || m_height == 0) {
Logger::LogError("[X11Input::Init] " + Logger::tr("Error: Width or height is zero!"));
throw X11Exception();
}
if(m_width > SSR_MAX_IMAGE_SIZE || m_height > SSR_MAX_IMAGE_SIZE) {
Logger::LogError("[X11Input::Init] " + Logger::tr("Error: Width or height is too large, the maximum width and height is %1!").arg(SSR_MAX_IMAGE_SIZE));
throw X11Exception();
}
try {
Init();
} catch(...) {
Free();
throw;
}
}
X11Input::~X11Input() {
// tell the thread to stop
if(m_thread.joinable()) {
Logger::LogInfo("[X11Input::~X11Input] " + Logger::tr("Stopping input thread ..."));
m_should_stop = true;
m_thread.join();
}
// free everything
Free();
}
void X11Input::GetCurrentRectangle(unsigned int *x, unsigned int *y, unsigned int *width, unsigned int *height) {
SharedLock lock(&m_shared_data);
*x = lock->m_current_x;
*y = lock->m_current_y;
*width = lock->m_current_width;
*height = lock->m_current_height;
}
void X11Input::GetCurrentSize(unsigned int *width, unsigned int *height) {
SharedLock lock(&m_shared_data);
*width = lock->m_current_width;
*height = lock->m_current_height;
}
double X11Input::GetFPS() {
int64_t timestamp = hrt_time_micro();
uint32_t frame_counter = m_frame_counter;
unsigned int time = timestamp - m_fps_last_timestamp;
if(time > 500000) {
unsigned int frames = frame_counter - m_fps_last_counter;
m_fps_last_timestamp = timestamp;
m_fps_last_counter = frame_counter;
m_fps_current = (double) frames / ((double) time * 1.0e-6);
}
return m_fps_current;
}
void X11Input::Init() {
// do the X11 stuff
// we need a separate display because the existing one would interfere with what Qt is doing in some cases
m_x11_display = XOpenDisplay(NULL); //QX11Info::display();
if(m_x11_display == NULL) {
Logger::LogError("[X11Input::Init] " + Logger::tr("Error: Can't open X display!", "Don't translate 'display'"));
throw X11Exception();
}
m_x11_screen = DefaultScreen(m_x11_display); //QX11Info::appScreen();
m_x11_root = RootWindow(m_x11_display, m_x11_screen); //QX11Info::appRootWindow(m_x11_screen);
m_x11_visual = DefaultVisual(m_x11_display, m_x11_screen); //(Visual*) QX11Info::appVisual(m_x11_screen);
m_x11_depth = DefaultDepth(m_x11_display, m_x11_screen); //QX11Info::appDepth(m_x11_screen);
m_x11_use_shm = XShmQueryExtension(m_x11_display);
if(m_x11_use_shm) {
Logger::LogInfo("[X11Input::Init] " + Logger::tr("Using X11 shared memory."));
} else {
Logger::LogInfo("[X11Input::Init] " + Logger::tr("Not using X11 shared memory."));
}
// showing the cursor requires XFixes (which should be supported on any modern X server, but let's check it anyway)
if(m_record_cursor) {
int event, error;
if(!XFixesQueryExtension(m_x11_display, &event, &error)) {
Logger::LogWarning("[X11Input::Init] " + Logger::tr("Warning: XFixes is not supported by X server, the cursor has been hidden.", "Don't translate 'XFixes'"));
m_record_cursor = false;
}
}
// get screen configuration information, so we can replace the unused areas with black rectangles (rather than showing random uninitialized memory)
// this is also used by the mouse following code to make sure that the rectangle stays on the screen
UpdateScreenConfiguration();
// initialize frame counter
m_frame_counter = 0;
m_fps_last_timestamp = hrt_time_micro();
m_fps_last_counter = 0;
m_fps_current = 0.0;
// start input thread
m_should_stop = false;
m_error_occurred = false;
m_thread = std::thread(&X11Input::InputThread, this);
}
void X11Input::Free() {
FreeImage();
if(m_x11_display != NULL) {
XCloseDisplay(m_x11_display);
m_x11_display = NULL;
}
}
void X11Input::AllocateImage(unsigned int width, unsigned int height) {
assert(m_x11_use_shm);
if(m_x11_shm_server_attached && m_x11_image->width == (int) width && m_x11_image->height == (int) height) {
return; // reuse existing image
}
FreeImage();
m_x11_image = XShmCreateImage(m_x11_display, m_x11_visual, m_x11_depth, ZPixmap, NULL, &m_x11_shm_info, width, height);
if(m_x11_image == NULL) {
Logger::LogError("[X11Input::Init] " + Logger::tr("Error: Can't create shared image!"));
throw X11Exception();
}
m_x11_shm_info.shmid = shmget(IPC_PRIVATE, m_x11_image->bytes_per_line * m_x11_image->height, IPC_CREAT | 0700);
if(m_x11_shm_info.shmid == -1) {
Logger::LogError("[X11Input::Init] " + Logger::tr("Error: Can't get shared memory!"));
throw X11Exception();
}
m_x11_shm_info.shmaddr = (char*) shmat(m_x11_shm_info.shmid, NULL, SHM_RND);
if(m_x11_shm_info.shmaddr == (char*) -1) {
Logger::LogError("[X11Input::Init] " + Logger::tr("Error: Can't attach to shared memory!"));
throw X11Exception();
}
m_x11_image->data = m_x11_shm_info.shmaddr;
if(!XShmAttach(m_x11_display, &m_x11_shm_info)) {
Logger::LogError("[X11Input::Init] " + Logger::tr("Error: Can't attach server to shared memory!"));
throw X11Exception();
}
m_x11_shm_server_attached = true;
}
void X11Input::FreeImage() {
if(m_x11_shm_server_attached) {
XShmDetach(m_x11_display, &m_x11_shm_info);
m_x11_shm_server_attached = false;
}
if(m_x11_shm_info.shmaddr != (char*) -1) {
shmdt(m_x11_shm_info.shmaddr);
m_x11_shm_info.shmaddr = (char*) -1;
}
if(m_x11_shm_info.shmid != -1) {
shmctl(m_x11_shm_info.shmid, IPC_RMID, NULL);
m_x11_shm_info.shmid = -1;
}
if(m_x11_image != NULL) {
XDestroyImage(m_x11_image);
m_x11_image = NULL;
}
}
void X11Input::UpdateScreenConfiguration() {
Logger::LogInfo("[X11Input::Init] " + Logger::tr("Detecting screen configuration ..."));
// get screen rectangles
m_screen_rects.clear();
int event_base, error_base;
if(XineramaQueryExtension(m_x11_display, &event_base, &error_base)) {
int num_screens;
XineramaScreenInfo *screens = XineramaQueryScreens(m_x11_display, &num_screens);
try {
for(int i = 0; i < num_screens; ++i) {
m_screen_rects.emplace_back(screens[i].x_org, screens[i].y_org, screens[i].x_org + screens[i].width, screens[i].y_org + screens[i].height);
}
} catch(...) {
XFree(screens);
throw;
}
XFree(screens);
} else {
Logger::LogWarning("[X11Input::Init] " + Logger::tr("Warning: Xinerama is not supported by X server, multi-monitor support may not work properly.", "Don't translate 'Xinerama'"));
return;
}
// make sure that we have at least one monitor
if(m_screen_rects.size() == 0) {
Logger::LogWarning("[X11Input::Init] " + Logger::tr("Warning: No monitors detected, multi-monitor support may not work properly."));
return;
}
// log the screen rectangles
for(size_t i = 0; i < m_screen_rects.size(); ++i) {
Rect &rect = m_screen_rects[i];
Logger::LogInfo("[X11Input::Init] " + Logger::tr("Screen %1:").arg(i)
+ " x1 = " + QString::number(rect.m_x1) + ", y1 = " + QString::number(rect.m_y1)
+ ", x2 = " + QString::number(rect.m_x2) + ", y2 = " + QString::number(rect.m_y2));
}
// calculate bounding box
m_screen_bbox = m_screen_rects[0];
for(size_t i = 1; i < m_screen_rects.size(); ++i) {
Rect &rect = m_screen_rects[i];
if(rect.m_x1 < m_screen_bbox.m_x1)
m_screen_bbox.m_x1 = rect.m_x1;
if(rect.m_y1 < m_screen_bbox.m_y1)
m_screen_bbox.m_y1 = rect.m_y1;
if(rect.m_x2 > m_screen_bbox.m_x2)
m_screen_bbox.m_x2 = rect.m_x2;
if(rect.m_y2 > m_screen_bbox.m_y2)
m_screen_bbox.m_y2 = rect.m_y2;
}
if(m_screen_bbox.m_x1 >= m_screen_bbox.m_x2 || m_screen_bbox.m_y1 >= m_screen_bbox.m_y2 ||
m_screen_bbox.m_x2 - m_screen_bbox.m_x1 > SSR_MAX_IMAGE_SIZE || m_screen_bbox.m_y2 - m_screen_bbox.m_y1 > SSR_MAX_IMAGE_SIZE) {
Logger::LogError("[X11Input::UpdateScreenConfiguration] " + Logger::tr("Error: Invalid screen bounding box!") + "\n"
" x1 = " + QString::number(m_screen_bbox.m_x1) + ", y1 = " + QString::number(m_screen_bbox.m_y1)
+ ", x2 = " + QString::number(m_screen_bbox.m_x2) + ", y2 = " + QString::number(m_screen_bbox.m_y2));
throw X11Exception();
}
/*qDebug() << "m_screen_rects:";
for(Rect &rect : m_screen_rects) {
qDebug() << " rect" << rect.m_x1 << rect.m_y1 << rect.m_x2 << rect.m_y2;
}
qDebug() << "m_screen_bbox:";
qDebug() << " rect" << m_screen_bbox.m_x1 << m_screen_bbox.m_y1 << m_screen_bbox.m_x2 << m_screen_bbox.m_y2;*/
// calculate dead space
m_screen_dead_space = {m_screen_bbox};
for(size_t i = 0; i < m_screen_rects.size(); ++i) {
/*qDebug() << "PARTIAL m_screen_dead_space:";
for(Rect &rect : m_screen_dead_space) {
qDebug() << " rect" << rect.m_x1 << rect.m_y1 << rect.m_x2 << rect.m_y2;
}*/
Rect &subtract = m_screen_rects[i];
std::vector<Rect> result;
for(Rect &rect : m_screen_dead_space) {
if(rect.m_x1 < subtract.m_x2 && rect.m_y1 < subtract.m_y2 && subtract.m_x1 < rect.m_x2 && subtract.m_y1 < rect.m_y2) {
unsigned int mid_y1 = std::max(rect.m_y1, subtract.m_y1);
unsigned int mid_y2 = std::min(rect.m_y2, subtract.m_y2);
if(rect.m_y1 < subtract.m_y1)
result.emplace_back(rect.m_x1, rect.m_y1, rect.m_x2, subtract.m_y1);
if(rect.m_x1 < subtract.m_x1)
result.emplace_back(rect.m_x1, mid_y1, subtract.m_x1, mid_y2);
if(subtract.m_x2 < rect.m_x2)
result.emplace_back(subtract.m_x2, mid_y1, rect.m_x2, mid_y2);
if(subtract.m_y2 < rect.m_y2)
result.emplace_back(rect.m_x1, subtract.m_y2, rect.m_x2, rect.m_y2);
} else {
result.emplace_back(rect);
}
}
m_screen_dead_space = std::move(result);
}
// log the dead space rectangles
for(size_t i = 0; i < m_screen_dead_space.size(); ++i) {
Rect &rect = m_screen_dead_space[i];
Logger::LogInfo("[X11Input::Init] " + Logger::tr("Dead space %1:").arg(i)
+ " x1 = " + QString::number(rect.m_x1) + ", y1 = " + QString::number(rect.m_y1)
+ ", x2 = " + QString::number(rect.m_x2) + ", y2 = " + QString::number(rect.m_y2));
}
/*qDebug() << "m_screen_dead_space:";
for(Rect &rect : m_screen_dead_space) {
qDebug() << " rect" << rect.m_x1 << rect.m_y1 << rect.m_x2 << rect.m_y2;
}*/
}
void X11Input::InputThread() {
try {
Logger::LogInfo("[X11Input::InputThread] " + Logger::tr("Input thread started."));
unsigned int grab_x = m_x, grab_y = m_y, grab_width = m_width, grab_height = m_height;
bool has_initial_cursor = false;
int64_t last_timestamp = hrt_time_micro();
while(!m_should_stop) {
// sleep
int64_t next_timestamp = CalculateNextVideoTimestamp();
int64_t timestamp = hrt_time_micro();
if(next_timestamp == SINK_TIMESTAMP_NONE) {
usleep(20000);
continue;
} else if(next_timestamp != SINK_TIMESTAMP_ASAP) {
int64_t wait = next_timestamp - timestamp;
if(wait > 21000) {
// the thread can't sleep for too long because it still has to check the m_should_stop flag periodically
usleep(20000);
continue;
} else if(wait > 0) {
usleep(wait);
timestamp = hrt_time_micro();
}
}
// follow the cursor
if(m_follow_cursor) {
int mouse_x, mouse_y, dummy;
Window dummy_win;
unsigned int dummy_mask;
if(XQueryPointer(m_x11_display, m_x11_root, &dummy_win, &dummy_win, &dummy, &dummy, &mouse_x, &mouse_y, &dummy_mask)) {
if(m_follow_fullscreen) {
for(Rect &rect : m_screen_rects) {
if(mouse_x >= (int) rect.m_x1 && mouse_y >= (int) rect.m_y1 && mouse_x < (int) rect.m_x2 && mouse_y < (int) rect.m_y2) {
grab_x = rect.m_x1;
grab_y = rect.m_y1;
grab_width = rect.m_x2 - rect.m_x1;
grab_height = rect.m_y2 - rect.m_y1;
break;
}
}
} else {
int grab_x_target = (mouse_x - (int) grab_width / 2) >> 1;
int grab_y_target = (mouse_y - (int) grab_height / 2) >> 1;
int frac = (has_initial_cursor)? lrint(1024.0 * exp(-1e-5 * (double) (timestamp - last_timestamp))) : 0;
grab_x_target = (grab_x_target + ((int) (grab_x >> 1) - grab_x_target) * frac / 1024) << 1;
grab_y_target = (grab_y_target + ((int) (grab_y >> 1) - grab_y_target) * frac / 1024) << 1;
grab_x = clamp(grab_x_target, (int) m_screen_bbox.m_x1, (int) m_screen_bbox.m_x2 - (int) grab_width);
grab_y = clamp(grab_y_target, (int) m_screen_bbox.m_y1, (int) m_screen_bbox.m_y2 - (int) grab_height);
}
}
has_initial_cursor = true;
}
// save current size
{
SharedLock lock(&m_shared_data);
if(lock->m_current_x != grab_x || lock->m_current_y != grab_y || lock->m_current_width != grab_width || lock->m_current_height != grab_height) {
lock->m_current_x = grab_x;
lock->m_current_y = grab_y;
lock->m_current_width = grab_width;
lock->m_current_height = grab_height;
emit CurrentRectangleChanged();
}
}
// get the image
if(m_x11_use_shm) {
AllocateImage(grab_width, grab_height);
if(!XShmGetImage(m_x11_display, m_x11_root, m_x11_image, grab_x, grab_y, AllPlanes)) {
Logger::LogError("[X11Input::InputThread] " + Logger::tr("Error: Can't get image (using shared memory)!\n"
" Usually this means the recording area is not completely inside the screen. Or did you change the screen resolution?"));
throw X11Exception();
}
} else {
if(m_x11_image != NULL) {
XDestroyImage(m_x11_image);
m_x11_image = NULL;
}
m_x11_image = XGetImage(m_x11_display, m_x11_root, grab_x, grab_y, grab_width, grab_height, AllPlanes, ZPixmap);
if(m_x11_image == NULL) {
Logger::LogError("[X11Input::InputThread] " + Logger::tr("Error: Can't get image (not using shared memory)!\n"
" Usually this means the recording area is not completely inside the screen. Or did you change the screen resolution?"));
throw X11Exception();
}
}
// clear the dead space
for(size_t i = 0; i < m_screen_dead_space.size(); ++i) {
Rect rect = m_screen_dead_space[i];
if(rect.m_x1 < grab_x)
rect.m_x1 = grab_x;
if(rect.m_y1 < grab_y)
rect.m_y1 = grab_y;
if(rect.m_x2 > grab_x + grab_width)
rect.m_x2 = grab_x + grab_width;
if(rect.m_y2 > grab_y + grab_height)
rect.m_y2 = grab_y + grab_height;
if(rect.m_x2 > rect.m_x1 && rect.m_y2 > rect.m_y1)
X11ImageClearRectangle(m_x11_image, rect.m_x1 - grab_x, rect.m_y1 - grab_y, rect.m_x2 - rect.m_x1, rect.m_y2 - rect.m_y1);
}
// draw the cursor
if(m_record_cursor) {
X11ImageDrawCursor(m_x11_display, m_x11_image, grab_x, grab_y);
}
// increase the frame counter
++m_frame_counter;
// push the frame
uint8_t *image_data = (uint8_t*) m_x11_image->data;
int image_stride = m_x11_image->bytes_per_line;
AVPixelFormat x11_image_format = X11ImageGetPixelFormat(m_x11_image);
PushVideoFrame(grab_width, grab_height, image_data, image_stride, x11_image_format, SWS_CS_DEFAULT, timestamp);
last_timestamp = timestamp;
}
Logger::LogInfo("[X11Input::InputThread] " + Logger::tr("Input thread stopped."));
} catch(const std::exception& e) {
m_error_occurred = true;
Logger::LogError("[X11Input::InputThread] " + Logger::tr("Exception '%1' in input thread.").arg(e.what()));
} catch(...) {
m_error_occurred = true;
Logger::LogError("[X11Input::InputThread] " + Logger::tr("Unknown exception in input thread."));
}
}