/*
 * Copyright (C) 2020, KylinSoft Co., Ltd.
 *
 * 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 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
 */
#include "Logger.h"
#include "X11Input.h"
#undef max
#undef min
/*
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_fullscreen)
{
   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_fullscreen;

   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 + height);
   {
       SharedLock lock(&m_shared_data);
       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::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);
    m_x11_depth = DefaultDepth(m_x11_display, 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()
{
    // 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;
    }

    // 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);
    }

    /*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);
                lock->m_current_width = grab_width;
                lock->m_current_height = grab_height;
            }

            //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, 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."));
    }
}
