diff --git a/CMakeLists.txt b/CMakeLists.txt index a420b6c..82f4671 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,115 +1,114 @@ # Spectacle project project(Spectacle) # KDE Application Version, managed by release script set(KDE_APPLICATIONS_VERSION_MAJOR "16") set(KDE_APPLICATIONS_VERSION_MINOR "11") set(KDE_APPLICATIONS_VERSION_MICRO "70") set(KDE_APPLICATIONS_VERSION "${KDE_APPLICATIONS_VERSION_MAJOR}.${KDE_APPLICATIONS_VERSION_MINOR}.${KDE_APPLICATIONS_VERSION_MICRO}") set(SPECTACLE_VERSION ${KDE_APPLICATIONS_VERSION}) # minimum requirements cmake_minimum_required (VERSION 2.8.12 FATAL_ERROR) set(QT_MIN_VERSION "5.4.0") set(KF5_MIN_VERSION "5.18.0") set(PLASMA_MIN_VERSION "5.4.0") find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE) set( CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR} ) # set up kf5 include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings NO_POLICY_SCOPE) include(ECMInstallIcons) include(ECMSetupVersion) include(FeatureSummary) find_package( Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED Core Widgets DBus PrintSupport Quick ) find_package( KF5 ${KF5_MIN_VERSION} REQUIRED CoreAddons WidgetsAddons DBusAddons Notifications Config I18n KIO XmlGui WindowSystem DocTools Declarative ) # optional components find_package(KF5Kipi) if (KF5Kipi_FOUND) set(KIPI_FOUND 1) endif () find_package(KDEExperimentalPurpose) if (KDEExperimentalPurpose_FOUND) set(PURPOSE_FOUND 1) endif() find_package(XCB COMPONENTS XFIXES IMAGE UTIL CURSOR) set(XCB_COMPONENTS_ERRORS FALSE) if (XCB_FOUND) find_package(Qt5X11Extras ${QT_MIN_VERSION} REQUIRED) - find_package(KF5Screen ${PLASMA_MIN_VERSION} REQUIRED) endif() set(XCB_COMPONENTS_FOUND TRUE) if(NOT XCB_XFIXES_FOUND) set(XCB_COMPONENTS_ERRORS "${XCB_COMPONENTS_ERRORS} XCB-XFIXES ") set(XCB_COMPONENTS_FOUND FALSE) endif() if(NOT XCB_IMAGE_FOUND) set(XCB_COMPONENTS_ERRORS "${XCB_COMPONENTS_ERRORS} XCB-IMAGE ") set(XCB_COMPONENTS_FOUND FALSE) endif() if(NOT XCB_UTIL_FOUND) set(XCB_COMPONENTS_ERRORS "${XCB_COMPONENTS_ERRORS} XCB-UTIL ") set(XCB_COMPONENTS_FOUND FALSE) endif() if(NOT XCB_CURSOR_FOUND) set(XCB_COMPONENTS_ERRORS "${XCB_COMPONENTS_ERRORS} XCB-CURSOR ") set(XCB_COMPONENTS_FOUND FALSE) endif() # fail build if none of the platform backends can be found if (NOT XCB_FOUND OR NOT XCB_COMPONENTS_FOUND) message(FATAL_ERROR "No suitable backend platform was found. Currenty supported platforms are: XCB Components Required: ${XCB_COMPONENTS_ERRORS}") endif() add_definitions(-DQT_NO_URL_CAST_FROM_STRING) # hand off to subdirectories add_subdirectory(src) add_subdirectory(dbus) add_subdirectory(desktop) add_subdirectory(icons) add_subdirectory(doc) # summaries feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 71143c6..7a03b95 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,104 +1,103 @@ # common - configure file and version definitions configure_file(Config.h.in ${CMAKE_CURRENT_BINARY_DIR}/Config.h) set(CMAKE_AUTORCC 1) # target set( SPECTACLE_SRCS_DEFAULT Main.cpp ExportManager.cpp SpectacleCore.cpp SpectacleConfig.cpp SpectacleDBusAdapter.cpp PlatformBackends/ImageGrabber.cpp PlatformBackends/DummyImageGrabber.cpp Gui/KSMainWindow.cpp Gui/KSWidget.cpp Gui/KSImageWidget.cpp Gui/ExportMenu.cpp Gui/SmartSpinBox.cpp Gui/SettingsDialog/SettingsDialog.cpp Gui/SettingsDialog/SettingsPage.cpp Gui/SettingsDialog/SaveOptionsPage.cpp Gui/SettingsDialog/GeneralOptionsPage.cpp QuickEditor/QuickEditor.cpp QuickEditor/QmlResources.qrc ) if(XCB_FOUND) set( SPECTACLE_SRCS_X11 PlatformBackends/X11ImageGrabber.cpp ) endif() if(KF5Kipi_FOUND) set( SPECTACLE_SRCS_KIPI KipiInterface/KSGKipiInterface.cpp KipiInterface/KSGKipiInfoShared.cpp KipiInterface/KSGKipiImageCollectionShared.cpp KipiInterface/KSGKipiImageCollectionSelector.cpp ) endif() set( SPECTACLE_SRCS_ALL ${SPECTACLE_SRCS_DEFAULT} ${SPECTACLE_SRCS_KIPI} ${SPECTACLE_SRCS_X11} ) add_executable( spectacle ${SPECTACLE_SRCS_ALL} ) # link libraries target_link_libraries( spectacle Qt5::DBus Qt5::PrintSupport Qt5::Quick KF5::CoreAddons KF5::DBusAddons KF5::WidgetsAddons KF5::Notifications KF5::ConfigCore KF5::I18n KF5::KIOWidgets KF5::WindowSystem KF5::XmlGui KF5::Declarative ) if(XCB_FOUND) target_link_libraries( spectacle XCB::XFIXES XCB::IMAGE XCB::CURSOR XCB::UTIL Qt5::X11Extras - KF5::Screen ) endif() if(KF5Kipi_FOUND) target_link_libraries ( spectacle KF5::Kipi ) endif() if(KDEExperimentalPurpose_FOUND) target_link_libraries ( spectacle KDEExperimental::PurposeWidgets ) endif() install(TARGETS spectacle ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/src/PlatformBackends/X11ImageGrabber.cpp b/src/PlatformBackends/X11ImageGrabber.cpp index 371748c..0525ec4 100644 --- a/src/PlatformBackends/X11ImageGrabber.cpp +++ b/src/PlatformBackends/X11ImageGrabber.cpp @@ -1,713 +1,672 @@ /* * Copyright (C) 2015 Boudhayan Gupta * * Contains code from kxutils.cpp, part of KWindowSystem. Copyright * notices reproduced below: * * Copyright (C) 2008 Lubos Lunak (l.lunak@kde.org) * Copyright (C) 2013 Martin Gräßlin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 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 Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "X11ImageGrabber.h" #include #include #include #include #include #include #include #include #include #include #include #include -#include -#include -#include #include #include #include X11ImageGrabber::X11ImageGrabber(QObject *parent) : - ImageGrabber(parent), - mScreenConfigOperation(nullptr) + ImageGrabber(parent) { mNativeEventFilter = new OnClickEventFilter(this); } X11ImageGrabber::~X11ImageGrabber() { delete mNativeEventFilter; } // for onClick grab OnClickEventFilter::OnClickEventFilter(X11ImageGrabber *grabber) : QAbstractNativeEventFilter(), mImageGrabber(grabber) {} bool OnClickEventFilter::nativeEventFilter(const QByteArray &eventType, void *message, long *result) { Q_UNUSED(result); if (eventType == "xcb_generic_event_t") { xcb_generic_event_t *ev = static_cast(message); switch (ev->response_type & ~0x80) { case XCB_BUTTON_RELEASE: // uninstall the eventfilter and release the mouse qApp->removeNativeEventFilter(this); xcb_ungrab_pointer(QX11Info::connection(), XCB_TIME_CURRENT_TIME); // decide whether to grab or abort. regrab the mouse // on mouse-wheel events { xcb_button_release_event_t *ev2 = static_cast(message); - qDebug() << ev2->detail; if (ev2->detail == 1) { QMetaObject::invokeMethod(mImageGrabber, "doImageGrab", Qt::QueuedConnection); } else if (ev2->detail < 4) { emit mImageGrabber->imageGrabFailed(); } else { QMetaObject::invokeMethod(mImageGrabber, "doOnClickGrab", Qt::QueuedConnection); } } return true; default: return false; } } return false; } bool X11ImageGrabber::onClickGrabSupported() const { return true; } void X11ImageGrabber::doOnClickGrab() { // get the cursor image xcb_cursor_t xcbCursor = XCB_CURSOR_NONE; xcb_cursor_context_t *xcbCursorCtx; xcb_screen_t *xcbAppScreen = xcb_aux_get_screen(QX11Info::connection(), QX11Info::appScreen()); if (xcb_cursor_context_new(QX11Info::connection(), xcbAppScreen, &xcbCursorCtx) >= 0) { QVector cursorNames = { QByteArrayLiteral("cross"), QByteArrayLiteral("crosshair"), QByteArrayLiteral("diamond-cross"), QByteArrayLiteral("cross-reverse") }; Q_FOREACH (const QByteArray &cursorName, cursorNames) { xcb_cursor_t cursor = xcb_cursor_load_cursor(xcbCursorCtx, cursorName.constData()); if (cursor != XCB_CURSOR_NONE) { xcbCursor = cursor; break; } } } // grab the cursor xcb_grab_pointer_cookie_t grabPointerCookie = xcb_grab_pointer_unchecked( QX11Info::connection(), // xcb connection 0, // deliver events to owner? nope QX11Info::appRootWindow(), // window to grab pointer for (root) XCB_EVENT_MASK_BUTTON_RELEASE, // which events do I want XCB_GRAB_MODE_SYNC, // pointer grab mode XCB_GRAB_MODE_ASYNC, // keyboard grab mode (why is this even here) XCB_NONE, // confine pointer to which window (none) xcbCursor, // cursor to change to for the duration of grab XCB_TIME_CURRENT_TIME // do this right now ); CScopedPointer grabPointerReply(xcb_grab_pointer_reply(QX11Info::connection(), grabPointerCookie, NULL)); // if the grab failed, take the screenshot right away if (grabPointerReply->status != XCB_GRAB_STATUS_SUCCESS) { return doImageGrab(); } // fix things if our pointer grab causes a lockup xcb_allow_events(QX11Info::connection(), XCB_ALLOW_SYNC_POINTER, XCB_TIME_CURRENT_TIME); // and install our event filter qApp->installNativeEventFilter(mNativeEventFilter); // done. clean stuff up xcb_cursor_context_free(xcbCursorCtx); xcb_free_cursor(QX11Info::connection(), xcbCursor); } // image conversion routine QPixmap X11ImageGrabber::convertFromNative(xcb_image_t *xcbImage) { QImage::Format format = QImage::Format_Invalid; switch (xcbImage->depth) { case 1: format = QImage::Format_MonoLSB; break; case 16: format = QImage::Format_RGB16; break; case 24: format = QImage::Format_RGB32; break; case 30: format = QImage::Format_BGR30; break; case 32: format = QImage::Format_ARGB32_Premultiplied; break; default: return QPixmap(); // we don't know } // The RGB32 format requires data format 0xffRRGGBB, ensure that this fourth byte really is 0xff if (format == QImage::Format_RGB32) { quint32 *data = reinterpret_cast(xcbImage->data); for (int i = 0; i < xcbImage->width * xcbImage->height; i++) { data[i] |= 0xff000000; } } QImage image(xcbImage->data, xcbImage->width, xcbImage->height, format); if (image.isNull()) { return QPixmap(); } // work around an abort in QImage::color if (image.format() == QImage::Format_MonoLSB) { image.setColorCount(2); image.setColor(0, QColor(Qt::white).rgb()); image.setColor(1, QColor(Qt::black).rgb()); } // Image is ready. Since the backing data from xcbImage could be freed // before the QPixmap goes away, a deep copy is necessary. return QPixmap::fromImage(image).copy(); } // utility functions QPixmap X11ImageGrabber::blendCursorImage(const QPixmap &pixmap, int x, int y, int width, int height) { // first we get the cursor position, compute the co-ordinates of the region // of the screen we're grabbing, and see if the cursor is actually visible in // the region const qreal dpr = pixmap.devicePixelRatio(); // cursor position operates on application's device pixel ratio, not the pixmap! QPoint cursorPos = QCursor::pos() / dpr * qApp->devicePixelRatio(); QRect screenRect(x / dpr, y / dpr, width / dpr, height / dpr); if (!screenRect.contains(cursorPos)) { return pixmap; } // now we can get the image and start processing xcb_connection_t *xcbConn = QX11Info::connection(); xcb_xfixes_get_cursor_image_cookie_t cursorCookie = xcb_xfixes_get_cursor_image_unchecked(xcbConn); CScopedPointer cursorReply(xcb_xfixes_get_cursor_image_reply(xcbConn, cursorCookie, NULL)); if (cursorReply.isNull()) { return pixmap; } quint32 *pixelData = xcb_xfixes_get_cursor_image_cursor_image(cursorReply.data()); if (!pixelData) { return pixmap; } // process the image into a QImage QImage cursorImage = QImage((quint8 *)pixelData, cursorReply->width, cursorReply->height, QImage::Format_ARGB32_Premultiplied); cursorImage.setDevicePixelRatio(dpr); // a small fix for the cursor position for fancier cursors cursorPos -= QPoint(cursorReply->xhot, cursorReply->yhot) / dpr; // now we translate the cursor point to our screen rectangle cursorPos -= QPoint(x, y) / dpr; // and do the painting QPixmap blendedPixmap = pixmap; QPainter painter(&blendedPixmap); painter.drawImage(cursorPos, cursorImage); // and done return blendedPixmap; } QPixmap X11ImageGrabber::getWindowPixmap(xcb_window_t window, bool blendPointer) { xcb_connection_t *xcbConn = QX11Info::connection(); // first get geometry information for our drawable xcb_get_geometry_cookie_t geomCookie = xcb_get_geometry_unchecked(xcbConn, window); CScopedPointer geomReply(xcb_get_geometry_reply(xcbConn, geomCookie, NULL)); // then proceed to get an image QScopedPointer xcbImage( xcb_image_get( xcbConn, window, geomReply->x, geomReply->y, geomReply->width, geomReply->height, ~0, XCB_IMAGE_FORMAT_Z_PIXMAP ) ); // if the image is null, this means we need to get the root image window // and run a crop if (xcbImage.isNull()) { return getWindowPixmap(QX11Info::appRootWindow(), blendPointer) .copy(geomReply->x, geomReply->y, geomReply->width, geomReply->height); } // now process the image QPixmap nativePixmap = convertFromNative(xcbImage.data()); if (!(blendPointer)) { return nativePixmap; } // now we blend in a pointer image xcb_get_geometry_cookie_t geomRootCookie = xcb_get_geometry_unchecked(xcbConn, geomReply->root); CScopedPointer geomRootReply(xcb_get_geometry_reply(xcbConn, geomRootCookie, NULL)); xcb_translate_coordinates_cookie_t translateCookie = xcb_translate_coordinates_unchecked( xcbConn, window, geomReply->root, geomRootReply->x, geomRootReply->y); CScopedPointer translateReply( xcb_translate_coordinates_reply(xcbConn, translateCookie, NULL)); return blendCursorImage(nativePixmap, translateReply->dst_x,translateReply->dst_y, geomReply->width, geomReply->height); } bool X11ImageGrabber::isKWinAvailable() { if (QDBusConnection::sessionBus().interface()->isServiceRegistered(QStringLiteral("org.kde.KWin"))) { QDBusInterface interface(QStringLiteral("org.kde.KWin"), QStringLiteral("/Effects"), QStringLiteral("org.kde.kwin.Effects")); QDBusReply reply = interface.call(QStringLiteral("isEffectLoaded"), "screenshot"); return reply.value(); } return false; } void X11ImageGrabber::KWinDBusScreenshotHelper(quint64 window) { mPixmap = getWindowPixmap((xcb_window_t)window, false); emit pixmapChanged(mPixmap); } -void X11ImageGrabber::KScreenCurrentMonitorScreenshotHelper(KScreen::ConfigOperation *op) -{ - KScreen::ConfigPtr config = qobject_cast(op)->config(); - - if (!config) { return grabFullScreen(); } - if (!config->screen()) { return grabFullScreen(); } - - // we'll store the cursor position first - - QPoint cursorPosition = QCursor::pos(); - - // next, we'll get all our outputs and figure out which one has the cursor - - const KScreen::OutputList outputs = config->outputs(); - for (auto output: outputs) { - if (!(output->isConnected())) { continue; } - if (!(output->currentMode())) { continue; } - - QPoint screenPosition = output->pos(); - QSize screenSize = output->currentMode()->size(); - QRect screenRect = QRect(screenPosition, screenSize); - - if (!(screenRect.contains(cursorPosition))) { - continue; - } - - // bingo, we've found an output that contains the cursor. Now - // to take a shot - - mPixmap = getWindowPixmap(QX11Info::appRootWindow(), mCapturePointer); - mPixmap = mPixmap.copy(screenPosition.x(), screenPosition.y(), screenSize.width(), screenSize.height()); - emit pixmapChanged(mPixmap); - - mScreenConfigOperation->disconnect(); - mScreenConfigOperation->deleteLater(); - mScreenConfigOperation = nullptr; - - return; - } - - mScreenConfigOperation->disconnect(); - mScreenConfigOperation->deleteLater(); - mScreenConfigOperation = nullptr; - - return grabFullScreen(); -} - void X11ImageGrabber::rectangleSelectionCancelled() { QObject *sender = QObject::sender(); sender->disconnect(); sender->deleteLater(); emit imageGrabFailed(); } void X11ImageGrabber::rectangleSelectionConfirmed(const QPixmap &pixmap, const QRect ®ion) { QObject *sender = QObject::sender(); sender->disconnect(); sender->deleteLater(); if (mCapturePointer) { mPixmap = blendCursorImage(pixmap, region.x(), region.y(), region.width(), region.height()); } else { mPixmap = pixmap; } emit pixmapChanged(mPixmap); } // grabber methods void X11ImageGrabber::grabFullScreen() { mPixmap = getWindowPixmap(QX11Info::appRootWindow(), mCapturePointer); emit pixmapChanged(mPixmap); } void X11ImageGrabber::grabTransientWithParent() { xcb_window_t curWin = getRealWindowUnderCursor(); // do we have a top-level or a transient window? KWindowInfo info(curWin, NET::WMName, NET::WM2TransientFor | NET::WM2WindowClass); if (!(info.valid(true) && (info.transientFor() != XCB_WINDOW_NONE)) || info.windowClassClass().isEmpty() || info.windowClassName().isEmpty()) { return grabWindowUnderCursor(); } // grab the image early mPixmap = getWindowPixmap(QX11Info::appRootWindow(), false); // now that we know we have a transient window, let's // see if the parent has any other transient windows who's // transient for the same app QRegion clipRegion; QStack childrenStack = findAllChildren(findParent(curWin)); while (!(childrenStack.isEmpty())) { xcb_window_t winId = childrenStack.pop(); KWindowInfo tempInfo(winId, 0, NET::WM2TransientFor); if (info.transientFor() == tempInfo.transientFor()) { clipRegion += getApplicationWindowGeometry(winId); } } // now we have a list of all the transient windows for the // parent, time to find the parent QList winList = KWindowSystem::stackingOrder(); for (int i = winList.size() - 1; i >= 0; i--) { KWindowInfo tempInfo(winList[i], NET::WMGeometry | NET::WMFrameExtents, NET::WM2WindowClass); QString winClass(tempInfo.windowClassClass()); QString winName(tempInfo.windowClassName()); if (winClass.contains(info.name(), Qt::CaseInsensitive) || winName.contains(info.name(), Qt::CaseInsensitive)) { if (mCaptureDecorations) { clipRegion += tempInfo.frameGeometry(); } else { clipRegion += tempInfo.geometry(); } break; } } // we can probably go ahead and generate the image now QImage tempImage(mPixmap.size(), QImage::Format_ARGB32); tempImage.fill(Qt::transparent); QPainter tempPainter(&tempImage); tempPainter.setClipRegion(clipRegion); tempPainter.drawPixmap(0, 0, mPixmap); tempPainter.end(); mPixmap = QPixmap::fromImage(tempImage).copy(clipRegion.boundingRect()); // why stop here, when we can render a 20px drop shadow all around it QGraphicsDropShadowEffect *effect = new QGraphicsDropShadowEffect; effect->setOffset(0); effect->setBlurRadius(20); QGraphicsPixmapItem *item = new QGraphicsPixmapItem; item->setPixmap(mPixmap); item->setGraphicsEffect(effect); QImage shadowImage(mPixmap.size() + QSize(40, 40), QImage::Format_ARGB32); shadowImage.fill(Qt::transparent); QPainter shadowPainter(&shadowImage); QGraphicsScene scene; scene.addItem(item); scene.render(&shadowPainter, QRectF(), QRectF(-20, -20, mPixmap.width() + 40, mPixmap.height() + 40)); shadowPainter.end(); // we can finish up now mPixmap = QPixmap::fromImage(shadowImage); if (mCapturePointer) { QPoint topLeft = clipRegion.boundingRect().topLeft() - QPoint(20, 20); mPixmap = blendCursorImage(mPixmap, topLeft.x(), topLeft.y(), mPixmap.width(), mPixmap.height()); } emit pixmapChanged(mPixmap); } void X11ImageGrabber::grabActiveWindow() { xcb_window_t activeWindow = KWindowSystem::activeWindow(); // if KWin is available, use the KWin DBus interfaces if (mCaptureDecorations && isKWinAvailable()) { QDBusConnection bus = QDBusConnection::sessionBus(); bus.connect(QStringLiteral("org.kde.KWin"), QStringLiteral("/Screenshot"), QStringLiteral("org.kde.kwin.Screenshot"), QStringLiteral("screenshotCreated"), this, SLOT(KWinDBusScreenshotHelper(quint64))); QDBusInterface interface(QStringLiteral("org.kde.KWin"), QStringLiteral("/Screenshot"), QStringLiteral("org.kde.kwin.Screenshot")); int mask = 1; if (mCapturePointer) { mask |= 1 << 1; } interface.call(QStringLiteral("screenshotForWindow"), (quint64)activeWindow, mask); return; } // otherwise, use the native functionality return grabApplicationWindowHelper(activeWindow); } void X11ImageGrabber::grabWindowUnderCursor() { // if KWin is available, use the KWin DBus interfaces if (mCaptureDecorations && isKWinAvailable()) { QDBusConnection bus = QDBusConnection::sessionBus(); bus.connect(QStringLiteral("org.kde.KWin"), QStringLiteral("/Screenshot"), QStringLiteral("org.kde.kwin.Screenshot"), QStringLiteral("screenshotCreated"), this, SLOT(KWinDBusScreenshotHelper(quint64))); QDBusInterface interface(QStringLiteral("org.kde.KWin"), QStringLiteral("/Screenshot"), QStringLiteral("org.kde.kwin.Screenshot")); int mask = 1; if (mCapturePointer) { mask |= 1 << 1; } interface.call(QStringLiteral("screenshotWindowUnderCursor"), mask); return; } // else, go native return grabApplicationWindowHelper(getRealWindowUnderCursor()); } void X11ImageGrabber::grabApplicationWindowHelper(xcb_window_t window) { // if the user doesn't want decorations captured, we're in luck. This is // the easiest bit mPixmap = getWindowPixmap(window, mCapturePointer); if (!mCaptureDecorations || window == QX11Info::appRootWindow()) { emit pixmapChanged(mPixmap); return; } // if the user wants the window decorations, things get a little tricky. // we can't simply get a handle to the window manager frame window and // just grab it, because some compositing window managers (yes, kwin // included) do not render the window onto the frame but keep it in a // separate opengl buffer, so grabbing this window is going to simply // give us a transparent image with the frame and titlebar. // all is not lost. what we need to do is grab the image of the entire // desktop, find the geometry of the window including its frame, and // crop the root image accordingly. KWindowInfo info(window, NET::WMFrameExtents); if (info.valid()) { QRect frameGeom = info.frameGeometry(); mPixmap = getWindowPixmap(QX11Info::appRootWindow(), mCapturePointer).copy(frameGeom); } // fallback is window without the frame emit pixmapChanged(mPixmap); } QRect X11ImageGrabber::getApplicationWindowGeometry(xcb_window_t window) { xcb_connection_t *xcbConn = QX11Info::connection(); xcb_get_geometry_cookie_t geomCookie = xcb_get_geometry_unchecked(xcbConn, window); CScopedPointer geomReply(xcb_get_geometry_reply(xcbConn, geomCookie, NULL)); return QRect(geomReply->x, geomReply->y, geomReply->width, geomReply->height); } void X11ImageGrabber::grabCurrentScreen() { - mScreenConfigOperation = new KScreen::GetConfigOperation; - connect(mScreenConfigOperation, &KScreen::GetConfigOperation::finished, - this, &X11ImageGrabber::KScreenCurrentMonitorScreenshotHelper); + QPoint cursorPosition = QCursor::pos(); + for (auto screen : QGuiApplication::screens()) { + const QRect screenRect = screen->geometry(); + if (!screenRect.contains(cursorPosition)) { + continue; + } + + mPixmap = getWindowPixmap(QX11Info::appRootWindow(), mCapturePointer).copy(screenRect); + emit pixmapChanged(mPixmap); + return; + } + + // No screen found with our cursor, fallback to capturing full screen + grabFullScreen(); } void X11ImageGrabber::grabRectangularRegion() { QuickEditor *editor = new QuickEditor(getWindowPixmap(QX11Info::appRootWindow(), false)); connect(editor, &QuickEditor::grabDone, this, &X11ImageGrabber::rectangleSelectionConfirmed); connect(editor, &QuickEditor::grabCancelled, this, &X11ImageGrabber::rectangleSelectionCancelled); } xcb_window_t X11ImageGrabber::getRealWindowUnderCursor() { xcb_connection_t *xcbConn = QX11Info::connection(); xcb_window_t curWin = QX11Info::appRootWindow(); const QByteArray atomName("WM_STATE"); xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(xcbConn, 0, atomName.length(), atomName.constData()); xcb_query_pointer_cookie_t pointerCookie = xcb_query_pointer_unchecked(xcbConn, curWin); CScopedPointer atomReply(xcb_intern_atom_reply(xcbConn, atomCookie, NULL)); CScopedPointer pointerReply(xcb_query_pointer_reply(xcbConn, pointerCookie, NULL)); if (atomReply->atom == XCB_ATOM_NONE) { return QX11Info::appRootWindow(); } // now start testing QStack windowStack; windowStack.push(pointerReply->child); while (!windowStack.isEmpty()) { curWin = windowStack.pop(); // next, check if our window has the WM_STATE peoperty set on // the window. if yes, return the window - we have found it xcb_get_property_cookie_t propertyCookie = xcb_get_property_unchecked(xcbConn, 0, curWin, atomReply->atom, XCB_ATOM_ANY, 0, 0); CScopedPointer propertyReply(xcb_get_property_reply(xcbConn, propertyCookie, NULL)); if (propertyReply->type != XCB_ATOM_NONE) { return curWin; } // if we're here, this means the window is not the real window // we should start looking at its children xcb_query_tree_cookie_t treeCookie = xcb_query_tree_unchecked(xcbConn, curWin); CScopedPointer treeReply(xcb_query_tree_reply(xcbConn, treeCookie, NULL)); xcb_window_t *winChildren = xcb_query_tree_children(treeReply.data()); int winChildrenLength = xcb_query_tree_children_length(treeReply.data()); for (int i = winChildrenLength - 1; i >= 0; i--) { windowStack.push(winChildren[i]); } } // return the window. it has geometry information for a crop return pointerReply->child; } QStack X11ImageGrabber::findAllChildren(xcb_window_t window) { QStack winStack; xcb_connection_t *xcbConn = QX11Info::connection(); xcb_query_tree_cookie_t treeCookie = xcb_query_tree_unchecked(xcbConn, window); CScopedPointer treeReply(xcb_query_tree_reply(xcbConn, treeCookie, NULL)); xcb_window_t *winChildren = xcb_query_tree_children(treeReply.data()); int winChildrenLength = xcb_query_tree_children_length(treeReply.data()); for (int i = winChildrenLength - 1; i >= 0; i--) { winStack.push(winChildren[i]); } return winStack; } xcb_window_t X11ImageGrabber::findParent(xcb_window_t window) { xcb_connection_t *xcbConn = QX11Info::connection(); xcb_query_tree_cookie_t treeCookie = xcb_query_tree_unchecked(xcbConn, window); CScopedPointer treeReply(xcb_query_tree_reply(xcbConn, treeCookie, NULL)); return treeReply->parent; } diff --git a/src/PlatformBackends/X11ImageGrabber.h b/src/PlatformBackends/X11ImageGrabber.h index 338ffee..513bf4b 100644 --- a/src/PlatformBackends/X11ImageGrabber.h +++ b/src/PlatformBackends/X11ImageGrabber.h @@ -1,105 +1,98 @@ /* * Copyright (C) 2015 Boudhayan Gupta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 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 Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef X11IMAGEGRABBER_H #define X11IMAGEGRABBER_H #include #include #include #include "ImageGrabber.h" class X11ImageGrabber; -namespace KScreen -{ - class GetConfigOperation; - class ConfigOperation; -} class OnClickEventFilter : public QAbstractNativeEventFilter { public: explicit OnClickEventFilter(X11ImageGrabber *grabber); bool nativeEventFilter(const QByteArray &eventType, void *message, long *result); private: X11ImageGrabber *mImageGrabber; }; class X11ImageGrabber : public ImageGrabber { Q_OBJECT public: explicit X11ImageGrabber(QObject * parent = 0); ~X11ImageGrabber(); bool onClickGrabSupported() const Q_DECL_OVERRIDE; protected: void grabFullScreen() Q_DECL_OVERRIDE; void grabCurrentScreen() Q_DECL_OVERRIDE; void grabActiveWindow() Q_DECL_OVERRIDE; void grabRectangularRegion() Q_DECL_OVERRIDE; void grabWindowUnderCursor() Q_DECL_OVERRIDE; void grabTransientWithParent() Q_DECL_OVERRIDE; QPixmap blendCursorImage(const QPixmap &pixmap, int x, int y, int width, int height) Q_DECL_OVERRIDE; private slots: void KWinDBusScreenshotHelper(quint64 window); - void KScreenCurrentMonitorScreenshotHelper(KScreen::ConfigOperation *op); void rectangleSelectionConfirmed(const QPixmap &pixmap, const QRect ®ion); void rectangleSelectionCancelled(); public slots: void doOnClickGrab() Q_DECL_OVERRIDE; private: bool isKWinAvailable(); xcb_window_t getRealWindowUnderCursor(); void grabApplicationWindowHelper(xcb_window_t window); QRect getApplicationWindowGeometry(xcb_window_t window); QStack findAllChildren(xcb_window_t window); xcb_window_t findParent(xcb_window_t window); QPixmap getWindowPixmap(xcb_window_t window, bool blendPointer); QPixmap convertFromNative(xcb_image_t *xcbImage); OnClickEventFilter *mNativeEventFilter; - KScreen::GetConfigOperation *mScreenConfigOperation; }; template using CScopedPointer = QScopedPointer; struct ScopedPointerXcbImageDeleter { static inline void cleanup(xcb_image_t *xcbImage) { xcb_image_destroy(xcbImage); } }; #endif // X11IMAGEGRABBER_H