2

I want the screentshot have the mouse cursor in it, but the grab API in QT -- QPixmap::grabWindow() can only make a screenshot without the cursor.

I can draw the cursor in the picture by myself but I have to get the cursor picture and the cursor in the screenshot will stay the same as the unchangeable cursor picture. That's ugly. Is there a better solution?

NG_
  • 6,895
  • 7
  • 45
  • 67
  • The mouse cursor is usually managed by the underlying windowing system, and the windowing system sends mouse events to windows, and it overlays a little arrow image. Applications may receive such events, but they are totally unaware of the presence of a visible cursor and can't access its visual appearance (unless the unerlying windowing system has an API to access it). Therefore, your best bet is to get the *position* of the cursor with `QCursor::pos()`, and draw an arrow at that position yourself. – Fred Jan 10 '13 at 18:22

3 Answers3

4

As per QPixmap::grabWindow documentation, it will not capture mouse cursor,

I think you need to get mouse position and draw cursor youself. You can get QCursor from QWidget with QCursor cursor() const API.

And QCursor has QPixmap pixmap () API, you can use this Pixmap to draw mouse cursor.

sorry if I am not able to understand your question correctly.

Kunal
  • 3,475
  • 21
  • 14
2

Here is cross-platform solution:

#include <assert.h>
#include <QCursor>
#include <QPainter>

#ifdef Q_OS_LINUX
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/Xfixes.h>
#else // Q_OS_WINDOWs
#include <Windows.h>
#include <wingdi.h>
#pragma comment (lib, "User32.lib")
#pragma comment (lib, "Gdi32.lib")
#include <QtWinExtras>
#endif

namespace imageutil {

#ifdef Q_OS_LINUX

/* WebCore/plugins/qt/QtX11ImageConversion.cpp */
QImage qimageFromXImage(XImage* xi) {
    QImage::Format format = QImage::Format_ARGB32_Premultiplied;
    if (xi->depth == 24)
        format = QImage::Format_RGB32;
    else if (xi->depth == 16)
        format = QImage::Format_RGB16;

    QImage image = QImage(reinterpret_cast<uchar*>(xi->data), xi->width, xi->height, xi->bytes_per_line, format).copy();

    // we may have to swap the byte order
    if ((QSysInfo::ByteOrder == QSysInfo::LittleEndian && xi->byte_order == MSBFirst)
            || (QSysInfo::ByteOrder == QSysInfo::BigEndian && xi->byte_order == LSBFirst)) {

        for (int i = 0; i < image.height(); i++) {
            if (xi->depth == 16) {
                ushort* p = reinterpret_cast<ushort*>(image.scanLine(i));
                ushort* end = p + image.width();
                while (p < end) {
                    *p = ((*p << 8) & 0xff00) | ((*p >> 8) & 0x00ff);
                    p++;
                }
            } else {
                uint* p = reinterpret_cast<uint*>(image.scanLine(i));
                uint* end = p + image.width();
                while (p < end) {
                    *p = ((*p << 24) & 0xff000000) | ((*p << 8) & 0x00ff0000)
                         | ((*p >> 8) & 0x0000ff00) | ((*p >> 24) & 0x000000ff);
                    p++;
                }
            }
        }
    }

    // fix-up alpha channel
    if (format == QImage::Format_RGB32) {
        QRgb* p = reinterpret_cast<QRgb*>(image.bits());
        for (int y = 0; y < xi->height; ++y) {
            for (int x = 0; x < xi->width; ++x)
                p[x] |= 0xff000000;
            p += xi->bytes_per_line / 4;
        }
    }

    return image;
}

#endif // Q_OS_LINUX

QPixmap takeScreenShot(const QRect& area) {
    QRect screen; /* interested display area */
    QImage qimage; /* result image */

#ifdef Q_OS_LINUX
    QPoint cursorPos;

    Display* display = XOpenDisplay(nullptr);
    Window root = DefaultRootWindow(display);

    XWindowAttributes gwa;
    XGetWindowAttributes(display, root, &gwa);

    const auto goodArea = QRect(0, 0, gwa.width, gwa.height).contains(area);
    if (!goodArea) {
        screen = QRect(0, 0, gwa.width, gwa.height);
        cursorPos = QCursor::pos();
    } else {
        screen = area;
        cursorPos = QCursor::pos() - screen.topLeft();
    }

    XImage* image = XGetImage(display, root, screen.x(), screen.y(), screen.width(), screen.height(), AllPlanes, ZPixmap);
    assert(nullptr != image);

    qimage = qimageFromXImage(image);

    /* draw mouse cursor into QImage
     * https://msnkambule.wordpress.com/2010/04/09/capturing-a-screenshot-showing-mouse-cursor-in-kde/
     * https://github.com/rprichard/x11-canvas-screencast/blob/master/CursorX11.cpp#L31
     * */
    {
        XFixesCursorImage* cursor = XFixesGetCursorImage(display);
        cursorPos -= QPoint(cursor->xhot, cursor->yhot);
        std::vector<uint32_t> pixels(cursor->width * cursor->height);
        for (size_t i = 0; i < pixels.size(); ++i)
            pixels[i] = cursor->pixels[i];
        QImage cursorImage((uchar*)(pixels.data()), cursor->width, cursor->height, QImage::Format_ARGB32_Premultiplied);
        QPainter painter(&qimage);
        painter.drawImage(cursorPos, cursorImage);
        XFree(cursor);
    }

    XDestroyImage(image);
    XDestroyWindow(display, root);
    XCloseDisplay(display);

#elif defined(Q_OS_WINDOWS)
    HWND hwnd = GetDesktopWindow();
    HDC hdc = GetWindowDC(hwnd);
    HDC hdcMem = CreateCompatibleDC(hdc);

    RECT rect = { 0, 0, GetDeviceCaps(hdc, HORZRES), GetDeviceCaps(hdc, VERTRES) };
    const auto goodArea = QRect(rect.left, rect.top, rect.right, rect.bottom).contains(area);
    if (!goodArea) {
        screen = QRect(rect.left, rect.top, rect.right, rect.bottom);
    } else {
        screen = area;
    }

    HBITMAP hbitmap(nullptr);
    hbitmap = CreateCompatibleBitmap(hdc, screen.width(), screen.height());
    SelectObject(hdcMem, hbitmap);
    BitBlt(hdcMem, 0, 0, screen.width(), screen.height(), hdc, screen.x(), screen.y(), SRCCOPY);

    /* draw mouse cursor into DC
     * https://stackoverflow.com/a/48925443/5446734
     * */
    CURSORINFO cursor = { sizeof(cursor) };
    if (GetCursorInfo(&cursor) && cursor.flags == CURSOR_SHOWING) {
        RECT rect;
        GetWindowRect(hwnd, &rect);
        ICONINFO info = { sizeof(info) };
        GetIconInfo(cursor.hCursor, &info);
        const int x = (cursor.ptScreenPos.x - rect.left - rect.left - info.xHotspot) - screen.left();
        const int y = (cursor.ptScreenPos.y - rect.left - rect.left - info.yHotspot) - screen.top();
        BITMAP bmpCursor = { 0 };
        GetObject(info.hbmColor, sizeof(bmpCursor), &bmpCursor);
        DrawIconEx(hdcMem, x, y, cursor.hCursor, bmpCursor.bmWidth, bmpCursor.bmHeight,
                   0, nullptr, DI_NORMAL);
    }

    qimage = QtWin::imageFromHBITMAP(hdc, hbitmap, screen.width(), screen.height());
#endif // Q_OS_LINUX

    return QPixmap::fromImage(qimage);
}

} // namespace imageutil

Don't forget to add to qmake:

win32: QT += winextras
unix:!macx: LIBS += -lX11 -lXext -lXfixes
IMAN4K
  • 1,265
  • 3
  • 24
  • 42
0

QPixmap::pixmap only works when the cursor is a pixmap. When the current cursor is a standard cursor does not work. Some idea for that always can get a pixmap that contain the actual cursor?

Boris
  • 1