Commit 4a3cb514 authored by Méven Car's avatar Méven Car Committed by Nate Graham
Browse files

Support Wayland rectangular selection HiDpi

When the drawn rectangle is on a single screen, the output image uses the screen native resolution.
When the drawn rectangle is on screens with multiple scale factor, the output image is upscaled to correspond to the screen with the hightest scale factor to keep the image undistorted.

Adds also a "scaled" full screen mode, to capture screens content downscaled to screens virtualsize allowing for undistorted content albeit loosing details.

Requires Plasma 5.19.80+

BUG: 409762
BUG: 420863
FIXED-IN: 20.12
parent b79ac774
......@@ -63,22 +63,30 @@ KSWidget::KSWidget(Platform::GrabModes theGrabModes, QWidget *parent)
// the capture mode options first
mCaptureModeLabel = new QLabel(i18n("<b>Capture Mode</b>"), this);
mCaptureArea = new QComboBox(this);
QString lFullScreenLabel = QApplication::screens().count() == 1
? i18n("Full Screen")
: i18n("Full Screen (All Monitors)");
if (theGrabModes.testFlag(Platform::GrabMode::AllScreens)) {
QString lFullScreenLabel = QApplication::screens().count() == 1
? i18n("Full Screen")
: i18n("Full Screen (All Monitors)");
mCaptureArea->insertItem(0, lFullScreenLabel, Spectacle::CaptureMode::AllScreens);
mCaptureArea->insertItem(1, i18n("Rectangular Region"), Spectacle::CaptureMode::RectangularRegion);
}
if (theGrabModes.testFlag(Platform::GrabMode::AllScreensScaled) && QApplication::screens().count() > 1) {
QString lFullScreenLabel = i18n("Full Screen (All Monitors, scaled)");
mCaptureArea->insertItem(1, lFullScreenLabel, Spectacle::CaptureMode::AllScreensScaled);
}
if (theGrabModes.testFlag(Platform::GrabMode::PerScreenImageNative)) {
mCaptureArea->insertItem(2, i18n("Rectangular Region"), Spectacle::CaptureMode::RectangularRegion);
}
if (theGrabModes.testFlag(Platform::GrabMode::CurrentScreen)) {
mCaptureArea->insertItem(2, i18n("Current Screen"), Spectacle::CaptureMode::CurrentScreen);
mCaptureArea->insertItem(3, i18n("Current Screen"), Spectacle::CaptureMode::CurrentScreen);
}
if (theGrabModes.testFlag(Platform::GrabMode::ActiveWindow)) {
mCaptureArea->insertItem(3, i18n("Active Window"), Spectacle::CaptureMode::ActiveWindow);
mCaptureArea->insertItem(4, i18n("Active Window"), Spectacle::CaptureMode::ActiveWindow);
}
if (theGrabModes.testFlag(Platform::GrabMode::WindowUnderCursor)) {
mCaptureArea->insertItem(4, i18n("Window Under Cursor"), Spectacle::CaptureMode::WindowUnderCursor);
mCaptureArea->insertItem(5, i18n("Window Under Cursor"), Spectacle::CaptureMode::WindowUnderCursor);
}
if (theGrabModes.testFlag(Platform::GrabMode::TransientWithParent)) {
mTransientWithParentAvailable = true;
......@@ -225,8 +233,7 @@ void KSWidget::lockOnClickEnabled()
void KSWidget::lockOnClickDisabled()
{
mCaptureOnClick->setCheckState(Qt::Unchecked);
mCaptureOnClick->setEnabled(false);
mCaptureOnClick->hide();
mDelayMsec->setEnabled(true);
}
......@@ -271,6 +278,7 @@ void KSWidget::captureModeChanged(int theIndex)
mCaptureTransientOnly->setEnabled(false);
break;
case Spectacle::CaptureMode::AllScreens:
case Spectacle::CaptureMode::AllScreensScaled:
case Spectacle::CaptureMode::CurrentScreen:
case Spectacle::CaptureMode::RectangularRegion:
mWindowDecorations->setEnabled(false);
......
......@@ -24,6 +24,8 @@
#include <QObject>
#include <QFlags>
#include "QuickEditor/ComparableQPoint.h"
class Platform: public QObject
{
Q_OBJECT
......@@ -36,7 +38,9 @@ class Platform: public QObject
CurrentScreen = 0x02,
ActiveWindow = 0x04,
WindowUnderCursor = 0x08,
TransientWithParent = 0x10
TransientWithParent = 0x10,
AllScreensScaled = 0x20,
PerScreenImageNative= 0x40,
};
using GrabModes = QFlags<GrabMode>;
Q_FLAG(GrabModes)
......@@ -62,6 +66,8 @@ class Platform: public QObject
Q_SIGNALS:
void newScreenshotTaken(const QPixmap &thePixmap);
void newScreensScreenshotTaken(const QVector<QImage> &images);
void newScreenshotFailed();
void windowTitleChanged(const QString &theWindowTitle);
};
......
......@@ -30,6 +30,8 @@
#include <QDBusUnixFileDescriptor>
#include <QDBusPendingCall>
#include <QFutureWatcher>
#include <QScreen>
#include <QGuiApplication>
#include <array>
......@@ -73,6 +75,30 @@ static QImage readImage(int thePipeFd)
return lImage;
}
static QVector<QImage> readImages(int thePipeFd)
{
QByteArray lContent;
if (readData(thePipeFd, lContent) != 0) {
close(thePipeFd);
return QVector<QImage>();
}
close(thePipeFd);
QDataStream lDataStream(lContent);
lDataStream.setVersion(QDataStream::Qt_DefaultCompiledVersion);
QImage lImage;
QVector<QImage> imgs;
while (!lDataStream.atEnd()){
lDataStream >> lImage;
if (!lImage.isNull()) {
imgs << lImage;
}
}
return imgs;
}
/* -- General Plumbing ------------------------------------------------------------------------- */
PlatformKWinWayland::PlatformKWinWayland(QObject *parent) :
......@@ -84,15 +110,6 @@ QString PlatformKWinWayland::platformName() const
return QStringLiteral("KWinWayland");
}
Platform::GrabModes PlatformKWinWayland::supportedGrabModes() const
{
Platform::GrabModes lSupportedModes({ GrabMode::AllScreens, GrabMode::WindowUnderCursor });
if (QApplication::screens().count() > 1) {
lSupportedModes |= Platform::GrabMode::CurrentScreen;
}
return lSupportedModes;
}
static std::array<int, 3> s_plasmaVersion = {-1, -1, -1};
std::array<int, 3> findPlasmaMinorVersion () {
......@@ -138,22 +155,77 @@ std::array<int, 3> findPlasmaMinorVersion () {
return s_plasmaVersion;
}
Platform::GrabModes PlatformKWinWayland::supportedGrabModes() const
{
Platform::GrabModes lSupportedModes({ Platform::GrabMode::AllScreens, GrabMode::WindowUnderCursor });
QList<QScreen *> screens = QApplication::screens();
// TODO remove sometime after Plasma 5.21 is released
// We can handle rectangular selection one one screen not scale factor
// on Plasma < 5.21
if (screenshotScreensAvailable() || (screens.count() == 1 && screens.first()->devicePixelRatio() == 1)) {
lSupportedModes |= Platform::GrabMode::PerScreenImageNative;
}
// TODO remove sometime after Plasma 5.20 is released
auto plasmaVersion = findPlasmaMinorVersion();
if (plasmaVersion.at(0) != -1 && (plasmaVersion.at(0) != 5 || (plasmaVersion.at(1) >= 20))) {
lSupportedModes |= Platform::GrabMode::AllScreensScaled;
}
if (screens.count() > 1) {
lSupportedModes |= Platform::GrabMode::CurrentScreen;
}
return lSupportedModes;
}
bool PlatformKWinWayland::screenshotScreensAvailable() const
{
// TODO remove sometime after Plasma 5.21 is released
auto plasmaVersion = findPlasmaMinorVersion();
// Screenshot screenshotScreens dbus interface requires Plasma 5.21
if (plasmaVersion.at(0) != -1 && (plasmaVersion.at(0) != 5 || (plasmaVersion.at(1) >= 21 || (plasmaVersion.at(1) == 20 && plasmaVersion.at(2) >= 80)))) {
return true;
} else {
return false;
}
}
Platform::ShutterModes PlatformKWinWayland::supportedShutterModes() const
{
// TODO remove sometime after Plasma 5.20 is released
auto plasmaVersion = findPlasmaMinorVersion();
if (plasmaVersion.at(0) != -1 && (plasmaVersion.at(0) != 5 || (plasmaVersion.at(1) >= 20 || (plasmaVersion.at(1) == 19 && plasmaVersion.at(2) >= 80)))) {
return { ShutterMode::Immediate | ShutterMode::OnClick };
if (plasmaVersion.at(0) != -1 && (plasmaVersion.at(0) != 5 || (plasmaVersion.at(1) >= 20))) {
return { ShutterMode::Immediate };
} else {
return { ShutterMode::OnClick };
}
}
void PlatformKWinWayland::doGrab(ShutterMode theShutterMode, GrabMode theGrabMode, bool theIncludePointer, bool theIncludeDecorations)
void PlatformKWinWayland::doGrab(ShutterMode /* theShutterMode */, GrabMode theGrabMode, bool theIncludePointer, bool theIncludeDecorations)
{
switch(theGrabMode) {
case GrabMode::AllScreens: {
doGrabHelper(QStringLiteral("screenshotFullscreen"), theIncludePointer);
case GrabMode::AllScreens:
doGrabHelper(QStringLiteral("screenshotFullscreen"), theIncludePointer, true);
return;
case GrabMode::AllScreensScaled:
doGrabHelper(QStringLiteral("screenshotFullscreen"), theIncludePointer, false);
return;
case GrabMode::PerScreenImageNative:
{
QList<QScreen *> screens = QGuiApplication::screens();
QStringList screenNames;
for (const auto screen : screens) {
screenNames << screen->name();
}
if (screenshotScreensAvailable()) {
doGrabImagesHelper(QStringLiteral("screenshotScreens"), screenNames, theIncludePointer, true);
} else {
// TODO remove sometime after Plasma 5.21 is released
// Use the dbus call screenshotFullscreen to get a single screen screenshot and treat it as a list of images
doGrabImagesHelper(QStringLiteral("screenshotFullscreen"), theIncludePointer, true);
}
return;
}
case GrabMode::CurrentScreen: {
......@@ -185,18 +257,43 @@ void PlatformKWinWayland::startReadImage(int theReadPipe)
[lWatcher, this] () {
lWatcher->deleteLater();
const QImage lImage = lWatcher->result();
emit newScreenshotTaken(QPixmap::fromImage(lImage));
if (lImage.isNull()) {
newScreenshotFailed();
} else {
newScreenshotTaken(QPixmap::fromImage(lImage));
}
}
);
lWatcher->setFuture(QtConcurrent::run(readImage, theReadPipe));
}
template <typename ArgType>
void PlatformKWinWayland::callDBus(const QString &theGrabMethod, ArgType theArgument, int theWriteFile)
void PlatformKWinWayland::startReadImages(int theReadPipe)
{
auto lWatcher = new QFutureWatcher<QVector<QImage>>(this);
QObject::connect(lWatcher, &QFutureWatcher<QVector<QImage>>::finished, this,
[lWatcher, this] () {
lWatcher->deleteLater();
auto result = lWatcher->result();
if (result.isEmpty()) {
newScreenshotFailed();
} else {
newScreensScreenshotTaken(result);
}
}
);
lWatcher->setFuture(QtConcurrent::run(readImages, theReadPipe));
}
template <typename ... ArgType>
void PlatformKWinWayland::callDBus(const QString &theGrabMethod, int theWriteFile, ArgType ... arguments)
{
QDBusInterface lInterface(QStringLiteral("org.kde.KWin"), QStringLiteral("/Screenshot"), QStringLiteral("org.kde.kwin.Screenshot"));
QDBusPendingCall pcall = lInterface.asyncCall(theGrabMethod, QVariant::fromValue(QDBusUnixFileDescriptor(theWriteFile)), theArgument);
QDBusPendingCall pcall = lInterface.asyncCall(theGrabMethod, QVariant::fromValue(QDBusUnixFileDescriptor(theWriteFile)), arguments...);
checkDbusPendingCall(pcall);
}
void PlatformKWinWayland::checkDbusPendingCall(QDBusPendingCall pcall)
{
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pcall, this);
QObject::connect(watcher, &QDBusPendingCallWatcher::finished,
this, [this](QDBusPendingCallWatcher* watcher) {
......@@ -209,8 +306,8 @@ void PlatformKWinWayland::callDBus(const QString &theGrabMethod, ArgType theArgu
});
}
template <typename ArgType>
void PlatformKWinWayland::doGrabHelper(const QString &theGrabMethod, ArgType theArgument)
template <typename ... ArgType>
void PlatformKWinWayland::doGrabHelper(const QString &theGrabMethod, ArgType ... arguments)
{
int lPipeFds[2];
if (pipe2(lPipeFds, O_CLOEXEC|O_NONBLOCK) != 0) {
......@@ -218,8 +315,23 @@ void PlatformKWinWayland::doGrabHelper(const QString &theGrabMethod, ArgType the
return;
}
callDBus(theGrabMethod, theArgument, lPipeFds[1]);
callDBus(theGrabMethod, lPipeFds[1], arguments...);
startReadImage(lPipeFds[0]);
close(lPipeFds[1]);
}
template <typename ... ArgType>
void PlatformKWinWayland::doGrabImagesHelper(const QString &theGrabMethod, ArgType ... arguments)
{
int lPipeFds[2];
if (pipe2(lPipeFds, O_CLOEXEC|O_NONBLOCK) != 0) {
emit newScreenshotFailed();
return;
}
callDBus(theGrabMethod, lPipeFds[1], arguments...);
startReadImages(lPipeFds[0]);
close(lPipeFds[1]);
}
......@@ -23,6 +23,8 @@
#include "Platform.h"
class QDBusPendingCall;
class PlatformKWinWayland final: public Platform
{
Q_OBJECT
......@@ -43,6 +45,13 @@ class PlatformKWinWayland final: public Platform
private:
void startReadImage(int theReadPipe);
template <typename ArgType> void doGrabHelper(const QString &theGrabMethod, ArgType theArgument);
template <typename ArgType> void callDBus(const QString &theGrabMethod, ArgType theArgument, int theWriteFile);
void startReadImages(int theReadPipe);
void checkDbusPendingCall(QDBusPendingCall pcall);
bool screenshotScreensAvailable() const;
template <typename ... ArgType> void callDBus(const QString &theGrabMethod, int theWriteFile, ArgType ... arguments);
template <typename ... ArgType> void doGrabHelper(const QString &theGrabMethod, ArgType ... arguments);
template <typename ... ArgType> void doGrabImagesHelper(const QString &theGrabMethod, ArgType ... arguments);
};
......@@ -36,7 +36,7 @@ QString PlatformNull::platformName() const
Platform::GrabModes PlatformNull::supportedGrabModes() const
{
return { GrabMode::AllScreens | GrabMode::CurrentScreen | GrabMode::ActiveWindow | GrabMode::WindowUnderCursor | GrabMode::TransientWithParent };
return { GrabMode::AllScreens | GrabMode::CurrentScreen | GrabMode::ActiveWindow | GrabMode::WindowUnderCursor | GrabMode::TransientWithParent | GrabMode::AllScreensScaled };
}
Platform::ShutterModes PlatformNull::supportedShutterModes() const
......
......@@ -151,7 +151,7 @@ QString PlatformXcb::platformName() const
Platform::GrabModes PlatformXcb::supportedGrabModes() const
{
Platform::GrabModes lSupportedModes({ GrabMode::AllScreens, GrabMode::ActiveWindow, GrabMode::WindowUnderCursor, GrabMode::TransientWithParent });
Platform::GrabModes lSupportedModes({ GrabMode::AllScreens, GrabMode::ActiveWindow, GrabMode::WindowUnderCursor, GrabMode::TransientWithParent, GrabMode::PerScreenImageNative });
if (QApplication::screens().count() > 1) {
lSupportedModes |= Platform::GrabMode::CurrentScreen;
}
......@@ -689,8 +689,22 @@ void PlatformXcb::doGrabNow(GrabMode theGrabMode, bool theIncludePointer, bool t
{
switch(theGrabMode) {
case GrabMode::AllScreens:
case GrabMode::AllScreensScaled:
grabAllScreens(theIncludePointer);
break;
case GrabMode::PerScreenImageNative:{
auto lPixmap = getToplevelPixmap(QRect(), theIncludePointer);
// break thePixmap into list of images
const auto screens = QGuiApplication::screens();
QVector<QImage> images;
for (const auto screen: screens) {
QRect geom = screen->geometry();
geom.setSize(screen->size() * screen->devicePixelRatio());
images << lPixmap.copy(geom).toImage();
}
emit newScreensScreenshotTaken(images);
break;
}
case GrabMode::CurrentScreen:
grabCurrentScreen(theIncludePointer);
break;
......
/*
* Copyright (C) 2020 Méven Car <meven.car@enioka.com>
*
* 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 <QPoint>
#ifndef COMPARABLEQPOINT_H
#define COMPARABLEQPOINT_H
class ComparableQPoint : public QPoint
{
public:
ComparableQPoint(const QPoint &point): QPoint(point.x(), point.y())
{}
ComparableQPoint(): QPoint()
{}
// utility class that allows using QMap to sort its keys when they are QPoint
bool operator<(const ComparableQPoint &other) const {
return x() < other.x() || y() < other.y();
}
};
#endif // COMPARABLEQPOINT_H
This diff is collapsed.
......@@ -27,6 +27,8 @@
#include <utility>
#include <vector>
#include "ComparableQPoint.h"
class QMouseEvent;
namespace KWayland {
......@@ -41,7 +43,7 @@ class QuickEditor: public QWidget
public:
explicit QuickEditor(const QPixmap &thePixmap, KWayland::Client::PlasmaShell *plasmashell, QWidget *parent = nullptr);
explicit QuickEditor(const QMap<ComparableQPoint, QImage> &images, KWayland::Client::PlasmaShell *plasmashell, QWidget *parent = nullptr);
virtual ~QuickEditor() = default;
private:
......@@ -117,7 +119,7 @@ class QuickEditor: public QWidget
QColor mCrossColor;
QColor mLabelBackgroundColor;
QColor mLabelForegroundColor;
QRectF mSelection;
QRect mSelection;
QPointF mStartPos;
QPointF mInitialTopLeft;
QString mMidHelpText;
......@@ -128,8 +130,11 @@ class QuickEditor: public QWidget
QPoint mBottomHelpContentPos;
int mBottomHelpGridLeftWidth;
MouseState mMouseDragState;
QMap<ComparableQPoint, QImage> mImages;
QVector<QPair<QRect, qreal>> mRectToDpr;
QPixmap mPixmap;
qreal dprI;
qreal devicePixelRatio;
qreal devicePixelRatioI;
QPointF mMousePos;
bool mMagnifierAllowed;
bool mShowMagnifier;
......@@ -139,6 +144,7 @@ class QuickEditor: public QWidget
bool mDisableArrowKeys;
QRect mPrimaryScreenGeo;
int mbottomHelpLength;
QRegion mScreenRegion;
// Midpoints of handles
QVector<QPointF> mHandlePositions = QVector<QPointF> {8};
......@@ -149,6 +155,12 @@ Q_SIGNALS:
void grabDone(const QPixmap &thePixmap);
void grabCancelled();
private:
QMap<ComparableQPoint, ComparableQPoint> computeCoordinatesAfterScaling(QMap<ComparableQPoint, QPair<qreal, QSize>> outputsRect);
void preparePaint();
};
#endif // QUICKEDITOR_H
......@@ -29,6 +29,7 @@ namespace Spectacle {
ActiveWindow = 2,
WindowUnderCursor = 3,
TransientWithParent = 4,
RectangularRegion = 5
RectangularRegion = 5,
AllScreensScaled = 6,
};
}
......@@ -44,6 +44,8 @@
#include <QProcess>
#include <QTimer>
#include <QScopedPointer>
#include <QPainter>
#include <QScreen>
SpectacleCore::SpectacleCore(QObject *parent):
QObject(parent),
......@@ -60,6 +62,7 @@ void SpectacleCore::init()
// essential connections
connect(this, &SpectacleCore::errorMessage, this, &SpectacleCore::showErrorMessage);
connect(mPlatform.get(), &Platform::newScreenshotTaken, this, &SpectacleCore::screenshotUpdated);
connect(mPlatform.get(), &Platform::newScreensScreenshotTaken, this, &SpectacleCore::screenshotsUpdated);
connect(mPlatform.get(), &Platform::newScreenshotFailed, this, &SpectacleCore::screenshotFailed);
// set up the export manager
......@@ -290,21 +293,38 @@ void SpectacleCore::showErrorMessage(const QString &theErrString)
}
}
void SpectacleCore::screenshotsUpdated(const QVector<QImage> &imgs)
{
QMap<ComparableQPoint, QImage> mapScreens;
QList<QScreen *> screens = QGuiApplication::screens();
if (imgs.length() != screens.size()) {
qWarning(SPECTACLE_CORE_LOG()) << "ERROR: images received from KWin do not match, expected:" << imgs.length() << "actual:" << screens.size();
return ;
}
// only used by Spectacle::CaptureMode::RectangularRegion
auto it = imgs.constBegin();
for (const QScreen *screen: screens) {
auto pos = screen->geometry().topLeft();
auto img = *it;
auto dpr = img.width() / static_cast<qreal>(screen->geometry().width());
mapScreens.insert(pos, img);
++it;
}
mQuickEditor = std::make_unique<QuickEditor>(mapScreens, mWaylandPlasmashell);
connect(mQuickEditor.get(), &QuickEditor::grabDone, this, &SpectacleCore::screenshotUpdated);
connect(mQuickEditor.get(), &QuickEditor::grabCancelled, this, &SpectacleCore::screenshotCanceled);
mQuickEditor->show();
}
void SpectacleCore::screenshotUpdated(const QPixmap &thePixmap)
{
auto lExportManager = ExportManager::instance();
// if we were running in rectangular crop mode, now would be
// the time to further process the image
if (lExportManager->captureMode() == Spectacle::CaptureMode::RectangularRegion) {
if(!mQuickEditor) {
mQuickEditor = std::make_unique<QuickEditor>(thePixmap, mWaylandPlasmashell);
connect(mQuickEditor.get(), &QuickEditor::grabDone, this, &SpectacleCore::screenshotUpdated);
connect(mQuickEditor.get(), &QuickEditor::grabCancelled, this, &SpectacleCore::screenshotCanceled);
mQuickEditor->show();
return;
} else {
if (mQuickEditor) {
mQuickEditor->hide();
mQuickEditor.reset(nullptr);
}
......@@ -395,6 +415,7 @@ void SpectacleCore::doNotify(const QUrl &theSavedAt)
switch(ExportManager::instance()->captureMode()) {
case Spectacle::CaptureMode::AllScreens:
case Spectacle::CaptureMode::AllScreensScaled:
lNotify->setTitle(i18nc("The entire screen area was captured, heading", "Full Screen Captured"));
break;
case Spectacle::CaptureMode::CurrentScreen:
......@@ -508,8 +529,11 @@ Platform::GrabMode SpectacleCore::toPlatformGrabMode(Spectacle::CaptureMode theC
case Spectacle::CaptureMode::InvalidChoice:
return Platform::GrabMode::InvalidChoice;
case Spectacle::CaptureMode::AllScreens:
case Spectacle::CaptureMode::RectangularRegion:
return Platform::GrabMode::AllScreens;
case Spectacle::CaptureMode::AllScreensScaled:
return Platform::GrabMode::AllScreensScaled;
case Spectacle::CaptureMode::RectangularRegion:
return Platform::GrabMode::PerScreenImageNative;
case Spectacle::CaptureMode::TransientWithParent:
return Platform::GrabMode::TransientWithParent;
case Spectacle::CaptureMode::CurrentScreen:
......
......@@ -73,6 +73,7 @@ class SpectacleCore: public QObject
void takeNewScreenshot(Spectacle::CaptureMode theCaptureMode, int theTimeout, bool theIncludePointer, bool theIncludeDecorations);
void showErrorMessage(const QString &theErrString);
void screenshotUpdated(const QPixmap &thePixmap);
void screenshotsUpdated(const QVector<QImage> &imgs);
void screenshotCanceled();
void screenshotFailed();
void doStartDragAndDrop();
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment