SpectacleCore.cpp 18.2 KB
Newer Older
1
/*
Boudhayan Gupta's avatar
Boudhayan Gupta committed
2
 *  Copyright (C) 2015 Boudhayan Gupta <bgupta@kde.org>
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 *
 *  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.
 */

Boudhayan Gupta's avatar
Boudhayan Gupta committed
20
#include "SpectacleCore.h"
21
#include "spectacle_core_debug.h"
22
23

#include "Config.h"
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
24

David Redondo's avatar
David Redondo committed
25
#include <KGlobalAccel>
Roman Inflianskas's avatar
Roman Inflianskas committed
26
27
28
29
#include <KLocalizedString>
#include <KMessageBox>
#include <KNotification>
#include <KRun>
30
31
32
#include <KWayland/Client/connection_thread.h>
#include <KWayland/Client/plasmashell.h>
#include <KWayland/Client/registry.h>
Roman Inflianskas's avatar
Roman Inflianskas committed
33
34
#include <KWindowSystem>

35
#include <QApplication>
Roman Inflianskas's avatar
Roman Inflianskas committed
36
37
38
#include <QClipboard>
#include <QDir>
#include <QDrag>
David Redondo's avatar
David Redondo committed
39
#include <QKeySequence>
Roman Inflianskas's avatar
Roman Inflianskas committed
40
#include <QMimeData>
41
#include <QProcess>
Roman Inflianskas's avatar
Roman Inflianskas committed
42
43
#include <QTimer>

44
45
46
47
48
49
50
SpectacleCore::SpectacleCore(StartMode theStartMode,
                             Spectacle::CaptureMode theCaptureMode,
                             QString &theSaveFileName,
                             qint64 theDelayMsec,
                             bool theNotifyOnGrab,
                             bool theCopyToClipboard,
                             QObject *parent) :
Boudhayan Gupta's avatar
Initial  
Boudhayan Gupta committed
51
    QObject(parent),
52
53
54
    mStartMode(theStartMode),
    mNotify(theNotifyOnGrab),
    mPlatform(loadPlatform()),
55
    mMainWindow(nullptr),
56
    mIsGuiInited(false),
57
    mCopyToClipboard(theCopyToClipboard),
58
    mWaylandPlasmashell(nullptr)
Boudhayan Gupta's avatar
Initial  
Boudhayan Gupta committed
59
{
60
61
    auto lConfig = KSharedConfig::openConfig(QStringLiteral("spectaclerc"));
    KConfigGroup lGuiConfig(lConfig, "GuiConfig");
Boudhayan Gupta's avatar
Initial  
Boudhayan Gupta committed
62

63
64
65
    if (!(theSaveFileName.isEmpty() || theSaveFileName.isNull())) {
        if (QDir::isRelativePath(theSaveFileName)) {
            theSaveFileName = QDir::current().absoluteFilePath(theSaveFileName);
66
        }
67
        setFilename(theSaveFileName);
68
69
    }

70
71
72
73
    // essential connections
    connect(this, &SpectacleCore::errorMessage, this, &SpectacleCore::showErrorMessage);
    connect(mPlatform.get(), &Platform::newScreenshotTaken, this, &SpectacleCore::screenshotUpdated);
    connect(mPlatform.get(), &Platform::newScreenshotFailed, this, &SpectacleCore::screenshotFailed);
Alexander Potashev's avatar
Alexander Potashev committed
74

75
76
77
78
    auto lImmediateAvailable = mPlatform->supportedShutterModes().testFlag(Platform::ShutterMode::Immediate);
    auto lOnClickAvailable = mPlatform->supportedShutterModes().testFlag(Platform::ShutterMode::OnClick);
    if ((!lOnClickAvailable) && (theDelayMsec < 0)) {
        theDelayMsec = 0;
Boudhayan Gupta's avatar
Boudhayan Gupta committed
79
80
    }

81
82
83
84
    // reset last region if it should not be remembered across restarts
    auto lSpectacleConfig = SpectacleConfig::instance();
    if(!lSpectacleConfig->alwaysRememberRegion()) {
        lSpectacleConfig->setCropRegion(QRect());
85
86
    }

87
88
89
90
91
92
93
    // set up the export manager
    auto lExportManager = ExportManager::instance();
    lExportManager->setCaptureMode(theCaptureMode);
    connect(lExportManager, &ExportManager::errorMessage, this, &SpectacleCore::showErrorMessage);
    connect(lExportManager, &ExportManager::imageSaved, this, &SpectacleCore::doCopyPath);
    connect(lExportManager, &ExportManager::forceNotify, this, &SpectacleCore::doNotify);
    connect(mPlatform.get(), &Platform::windowTitleChanged, lExportManager, &ExportManager::setWindowTitle);
94

95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
    // Needed so the QuickEditor can go fullscreen on wayland
    if (KWindowSystem::isPlatformWayland()) {
        using namespace KWayland::Client;
        ConnectionThread *connection = ConnectionThread::fromApplication(this);
        if (!connection) {
            return;
        }
        Registry *registry = new Registry(this);
        registry->create(connection);
        connect(registry, &Registry::plasmaShellAnnounced, this,
            [this, registry] (quint32 name, quint32 version) {
                mWaylandPlasmashell = registry->createPlasmaShell(name, version, this);
            }
        );
        registry->setup();
        connection->roundtrip();
    }

113
114
    switch (theStartMode) {
    case StartMode::DBus:
115
        break;
116
117
118
119
120
121
    case StartMode::Background: {
            auto lMsec = (KWindowSystem::compositingActive() ? 200 : 50) + theDelayMsec;
            auto lShutterMode = lImmediateAvailable ? Platform::ShutterMode::Immediate : Platform::ShutterMode::OnClick;
            auto lIncludePointer = lGuiConfig.readEntry("includePointer", true);
            auto lIncludeDecorations = lGuiConfig.readEntry("includeDecorations", true);
            const Platform::GrabMode lCaptureMode = toPlatformGrabMode(theCaptureMode);
122
            QTimer::singleShot(lMsec, this, [ this, lCaptureMode, lShutterMode, lIncludePointer, lIncludeDecorations ]() {
123
124
                mPlatform->doGrab(lShutterMode, lCaptureMode, lIncludePointer, lIncludeDecorations);
            });
125
126
        }
        break;
127
128
    case StartMode::Gui:
        initGui(lGuiConfig.readEntry("includePointer", true), lGuiConfig.readEntry("includeDecorations", true));
129
        break;
130
    }
David Redondo's avatar
David Redondo committed
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
    setUpShortcuts();
}

void SpectacleCore::setUpShortcuts()
{
    SpectacleConfig* config = SpectacleConfig::instance();

    QAction* openAction = config->shortCutActions->action(QStringLiteral("_launch"));
    KGlobalAccel::self()->setGlobalShortcut(openAction, Qt::Key_Print);

    QAction* fullScreenAction = config->shortCutActions->action(QStringLiteral("FullScreenScreenShot"));
    KGlobalAccel::self()->setGlobalShortcut(fullScreenAction, Qt::SHIFT + Qt::Key_Print);

    QAction* activeWindowAction = config->shortCutActions->action(QStringLiteral("ActiveWindowScreenShot"));
    KGlobalAccel::self()->setGlobalShortcut(activeWindowAction, Qt::META + Qt::Key_Print);

    QAction* regionAction = config->shortCutActions->action(QStringLiteral("RectangularRegionScreenShot"));
    KGlobalAccel::self()->setGlobalShortcut(regionAction, Qt::META + Qt::SHIFT + Qt::Key_Print);
149
150
151

    QAction* currentScreenAction = config->shortCutActions->action(QStringLiteral("CurrentMonitorScreenShot"));
    KGlobalAccel::self()->setGlobalShortcut(currentScreenAction, QList<QKeySequence>());
Boudhayan Gupta's avatar
Initial  
Boudhayan Gupta committed
152
153
}

Boudhayan Gupta's avatar
Boudhayan Gupta committed
154
QString SpectacleCore::filename() const
Boudhayan Gupta's avatar
Initial  
Boudhayan Gupta committed
155
156
157
158
{
    return mFileNameString;
}

Boudhayan Gupta's avatar
Boudhayan Gupta committed
159
void SpectacleCore::setFilename(const QString &filename)
Boudhayan Gupta's avatar
Initial  
Boudhayan Gupta committed
160
161
162
163
164
165
166
{
    mFileNameString = filename;
    mFileNameUrl = QUrl::fromUserInput(filename);
}

// Slots

Boudhayan Gupta's avatar
Boudhayan Gupta committed
167
void SpectacleCore::dbusStartAgent()
168
{
169
    qApp->setQuitOnLastWindowClosed(true);
170
171
172
173
174
175
176
177
178

    auto lConfig = KSharedConfig::openConfig(QStringLiteral("spectaclerc"));
    KConfigGroup lGuiConfig(lConfig, "GuiConfig");
    auto lIncludePointer = lGuiConfig.readEntry("includePointer", true);
    auto lIncludeDecorations = lGuiConfig.readEntry("includeDecorations", true);

    if (!(mStartMode == StartMode::Gui)) {
        mStartMode = StartMode::Gui;
        initGui(lIncludePointer, lIncludeDecorations);
179
180
181
    } else {
        using Actions = SpectacleConfig::PrintKeyActionRunning;
        switch (SpectacleConfig::instance()->printKeyActionRunning()) {
182
183
184
            case Actions::TakeNewScreenshot: {
                auto lShutterMode = mPlatform->supportedShutterModes().testFlag(Platform::ShutterMode::Immediate) ? Platform::ShutterMode::Immediate : Platform::ShutterMode::OnClick;
                auto lGrabMode = toPlatformGrabMode(ExportManager::instance()->captureMode());
185
                QTimer::singleShot(KWindowSystem::compositingActive() ? 200 : 50, this, [this, lShutterMode, lGrabMode, lIncludePointer, lIncludeDecorations]() {
186
187
                    mPlatform->doGrab(lShutterMode, lGrabMode, lIncludePointer, lIncludeDecorations);
                });
188
                break;
189
            }
190
            case Actions::FocusWindow:
David Redondo's avatar
David Redondo committed
191
192
193
194
                if (mMainWindow->isMinimized()) {
                    mMainWindow->setWindowState(mMainWindow->windowState() & ~Qt::WindowMinimized);
                }
                mMainWindow->activateWindow();
195
196
197
198
199
200
201
                break;
            case Actions::StartNewInstance:
                QProcess newInstance;
                newInstance.setProgram(QStringLiteral("spectacle"));
                newInstance.startDetached();
                break;
        }
202
203
204
    }
}

205
206
207
208
void SpectacleCore::takeNewScreenshot(Spectacle::CaptureMode theCaptureMode,
                                      int  theTimeout,
                                      bool theIncludePointer,
                                      bool theIncludeDecorations)
Boudhayan Gupta's avatar
Initial  
Boudhayan Gupta committed
209
{
210
211
    ExportManager::instance()->setCaptureMode(theCaptureMode);
    auto lGrabMode = toPlatformGrabMode(theCaptureMode);
Boudhayan Gupta's avatar
Initial  
Boudhayan Gupta committed
212

213
214
    if (theTimeout < 0) {
        mPlatform->doGrab(Platform::ShutterMode::OnClick, lGrabMode, theIncludePointer, theIncludeDecorations);
215
        return;
216
217
    }

218
219
220
221
222
223
    // when compositing is enabled, we need to give it enough time for the window
    // to disappear and all the effects are complete before we take the shot. there's
    // no way of knowing how long the disappearing effects take, but as per default
    // settings (and unless the user has set an extremely slow effect), 200
    // milliseconds is a good amount of wait time.

224
    auto lMsec = KWindowSystem::compositingActive() ? 200 : 50;
225
    QTimer::singleShot(theTimeout + lMsec, this, [this, lGrabMode, theIncludePointer, theIncludeDecorations]() {
226
227
        mPlatform->doGrab(Platform::ShutterMode::Immediate, lGrabMode, theIncludePointer, theIncludeDecorations);
    });
Boudhayan Gupta's avatar
Initial  
Boudhayan Gupta committed
228
229
}

230
void SpectacleCore::showErrorMessage(const QString &theErrString)
Boudhayan Gupta's avatar
Initial  
Boudhayan Gupta committed
231
{
232
    qCDebug(SPECTACLE_CORE_LOG) << "ERROR: " << theErrString;
Boudhayan Gupta's avatar
Initial  
Boudhayan Gupta committed
233

234
235
    if (mStartMode == StartMode::Gui) {
        KMessageBox::error(nullptr, theErrString);
Boudhayan Gupta's avatar
Initial  
Boudhayan Gupta committed
236
237
238
    }
}

239
void SpectacleCore::screenshotUpdated(const QPixmap &thePixmap)
Boudhayan Gupta's avatar
Initial  
Boudhayan Gupta committed
240
{
241
    auto lExportManager = ExportManager::instance();
242
243
244
245
246
247

    // 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) {
248
            mQuickEditor = std::make_unique<QuickEditor>(thePixmap, mWaylandPlasmashell);
249
250
            connect(mQuickEditor.get(), &QuickEditor::grabDone, this, &SpectacleCore::screenshotUpdated);
            connect(mQuickEditor.get(), &QuickEditor::grabCancelled, this, &SpectacleCore::screenshotFailed);
251
            mQuickEditor->show();
252
253
254
255
256
257
258
            return;
        } else {
            mQuickEditor->hide();
            mQuickEditor.reset(nullptr);
        }
    }

259
260
    lExportManager->setPixmap(thePixmap);
    lExportManager->updatePixmapTimestamp();
Boudhayan Gupta's avatar
Initial  
Boudhayan Gupta committed
261

262
    switch (mStartMode) {
263
264
    case StartMode::Background:
    case StartMode::DBus:
Boudhayan Gupta's avatar
Boudhayan Gupta committed
265
266
        {
            if (mNotify) {
267
                connect(lExportManager, &ExportManager::imageSaved, this, &SpectacleCore::doNotify);
Boudhayan Gupta's avatar
Boudhayan Gupta committed
268
269
            }

270
            if (mCopyToClipboard) {
271
                lExportManager->doCopyToClipboard(mNotify);
272
            } else {
273
                QUrl lSavePath = (mStartMode == StartMode::Background && mFileNameUrl.isValid() && mFileNameUrl.isLocalFile()) ?
Boudhayan Gupta's avatar
Boudhayan Gupta committed
274
                    mFileNameUrl : QUrl();
275
                lExportManager->doSave(lSavePath);
276
            }
Boudhayan Gupta's avatar
Boudhayan Gupta committed
277

Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
278
279
            // if we notify, we emit allDone only if the user either dismissed the notification or pressed
            // the "Open" button, otherwise the app closes before it can react to it.
280
281
282
283
284
            if (!mNotify && mCopyToClipboard) {
                // Allow some time for clipboard content to transfer if '--nonotify' is used, see Bug #411263
                // TODO: Find better solution
                QTimer::singleShot(250, this, &SpectacleCore::allDone);
            } else if (!mNotify) {
Boudhayan Gupta's avatar
Boudhayan Gupta committed
285
286
287
                emit allDone();
            }
        }
288
        break;
289
290
    case StartMode::Gui:
        mMainWindow->setScreenshotAndShow(thePixmap);
291

Antonio Prcela's avatar
Antonio Prcela committed
292
293
294
295
296
297
298
299
300
        bool autoSaveImage = SpectacleConfig::instance()->autoSaveImage();
        bool copyImageToClipboard = SpectacleConfig::instance()->copyImageToClipboard();

        if (autoSaveImage && copyImageToClipboard) {
            lExportManager->doSaveAndCopy();
        } else if (autoSaveImage) {
            lExportManager->doSave();
        } else if (copyImageToClipboard) {
            lExportManager->doCopyToClipboard(false);
301
        }
Boudhayan Gupta's avatar
Initial  
Boudhayan Gupta committed
302
303
304
    }
}

Boudhayan Gupta's avatar
Boudhayan Gupta committed
305
void SpectacleCore::screenshotFailed()
Boudhayan Gupta's avatar
Boudhayan Gupta committed
306
{
307
308
309
310
311
    if (ExportManager::instance()->captureMode() == Spectacle::CaptureMode::RectangularRegion && mQuickEditor) {
        mQuickEditor->hide();
        mQuickEditor.reset(nullptr);
    }

312
    switch (mStartMode) {
313
    case StartMode::Background:
314
        showErrorMessage(i18n("Screenshot capture canceled or failed"));
315
316
        emit allDone();
        return;
317
    case StartMode::DBus:
318
        emit grabFailed();
319
320
        emit allDone();
        return;
321
    case StartMode::Gui:
322
        mMainWindow->setScreenshotAndShow(QPixmap());
323
    }
Boudhayan Gupta's avatar
Boudhayan Gupta committed
324
325
}

326
void SpectacleCore::doNotify(const QUrl &theSavedAt)
Boudhayan Gupta's avatar
Initial  
Boudhayan Gupta committed
327
{
328
    KNotification *lNotify = new KNotification(QStringLiteral("newScreenshotSaved"));
329

330
331
332
    switch(ExportManager::instance()->captureMode()) {
    case Spectacle::CaptureMode::AllScreens:
        lNotify->setTitle(i18nc("The entire screen area was captured, heading", "Full Screen Captured"));
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
333
        break;
334
335
    case Spectacle::CaptureMode::CurrentScreen:
        lNotify->setTitle(i18nc("The current screen was captured, heading", "Current Screen Captured"));
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
336
        break;
337
338
    case Spectacle::CaptureMode::ActiveWindow:
        lNotify->setTitle(i18nc("The active window was captured, heading", "Active Window Captured"));
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
339
        break;
340
341
342
    case Spectacle::CaptureMode::WindowUnderCursor:
    case Spectacle::CaptureMode::TransientWithParent:
        lNotify->setTitle(i18nc("The window under the mouse was captured, heading", "Window Under Cursor Captured"));
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
343
        break;
344
345
    case Spectacle::CaptureMode::RectangularRegion:
        lNotify->setTitle(i18nc("A rectangular region was captured, heading", "Rectangular Region Captured"));
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
346
        break;
347
    case Spectacle::CaptureMode::InvalidChoice:
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
348
349
350
        break;
    }

351
    // a speaking message is prettier than a URL, special case for copy to clipboard and the default pictures location
352
    const QString &lSavePath = theSavedAt.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path();
353
    if (mCopyToClipboard) {
354
355
356
        lNotify->setText(i18n("A screenshot was saved to your clipboard."));
    } else if (lSavePath == QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)) {
        lNotify->setText(i18nc("Placeholder is filename", "A screenshot was saved as '%1' to your Pictures folder.", theSavedAt.fileName()));
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
357
    } else {
358
        lNotify->setText(i18n("A screenshot was saved as '%1' to '%2'.", theSavedAt.fileName(), lSavePath));
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
359
360
    }

361
    if (!mCopyToClipboard) {
362
363
364
        lNotify->setUrls({theSavedAt});
        lNotify->setDefaultAction(i18nc("Open the screenshot we just saved", "Open"));
        connect(lNotify, QOverload<uint>::of(&KNotification::activated), this, [this, theSavedAt](uint index) {
365
            if (index == 0) {
366
                new KRun(theSavedAt, nullptr);
367
                QTimer::singleShot(250, this, [this] {
368
                    if (mStartMode != StartMode::Gui || SpectacleConfig::instance()->quitAfterSaveOrCopyChecked()) {
369
370
371
372
373
                        emit allDone();
                    }
                });
            }
        });
374
    }
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
375

376
    connect(lNotify, &QObject::destroyed, this, [this] {
377
        if (mStartMode != StartMode::Gui || SpectacleConfig::instance()->quitAfterSaveOrCopyChecked()) {
378
379
380
381
            emit allDone();
        }
    });

382
    lNotify->sendEvent();
Boudhayan Gupta's avatar
Initial  
Boudhayan Gupta committed
383
384
}

385
386
387
388
389
390
391
void SpectacleCore::doCopyPath(const QUrl &savedAt)
{
    if (SpectacleConfig::instance()->copySaveLocationToClipboard()) {
        qApp->clipboard()->setText(savedAt.toLocalFile());
    }
}

Boudhayan Gupta's avatar
Boudhayan Gupta committed
392
void SpectacleCore::doStartDragAndDrop()
393
{
394
395
396
    auto lExportManager = ExportManager::instance();
    QUrl lTempFile = lExportManager->tempSave();
    if (!lTempFile.isValid()) {
Boudhayan Gupta's avatar
Boudhayan Gupta committed
397
398
399
        return;
    }

400
401
402
    auto lMimeData = new QMimeData;
    lMimeData->setUrls(QList<QUrl> { lTempFile });
    lMimeData->setData(QStringLiteral("application/x-kde-suggestedfilename"), QFile::encodeName(lTempFile.fileName()));
403

404
405
406
407
    auto lDragHandler = new QDrag(this);
    lDragHandler->setMimeData(lMimeData);
    lDragHandler->setPixmap(lExportManager->pixmap().scaled(256, 256, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation));
    lDragHandler->exec(Qt::CopyAction);
408
409
}

Boudhayan Gupta's avatar
Initial  
Boudhayan Gupta committed
410
411
// Private

412
Platform::GrabMode SpectacleCore::toPlatformGrabMode(Spectacle::CaptureMode theCaptureMode)
413
{
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
    switch(theCaptureMode) {
    case Spectacle::CaptureMode::InvalidChoice:
        return Platform::GrabMode::InvalidChoice;
    case Spectacle::CaptureMode::AllScreens:
    case Spectacle::CaptureMode::RectangularRegion:
        return Platform::GrabMode::AllScreens;
    case Spectacle::CaptureMode::TransientWithParent:
        return Platform::GrabMode::TransientWithParent;
    case Spectacle::CaptureMode::CurrentScreen:
        return Platform::GrabMode::CurrentScreen;
    case Spectacle::CaptureMode::ActiveWindow:
        return Platform::GrabMode::ActiveWindow;
    case Spectacle::CaptureMode::WindowUnderCursor:
        return Platform::GrabMode::WindowUnderCursor;
    }
    return Platform::GrabMode::InvalidChoice;
}

void SpectacleCore::initGui(bool theIncludePointer, bool theIncludeDecorations)
{
    if (!mIsGuiInited) {
        mMainWindow = std::make_unique<KSMainWindow>(mPlatform->supportedGrabModes(), mPlatform->supportedShutterModes());
436

437
438
        connect(mMainWindow.get(), &KSMainWindow::newScreenshotRequest, this, &SpectacleCore::takeNewScreenshot);
        connect(mMainWindow.get(), &KSMainWindow::dragAndDropRequest, this, &SpectacleCore::doStartDragAndDrop);
439

440
441
442
443
444
        mIsGuiInited = true;

        auto lShutterMode = mPlatform->supportedShutterModes().testFlag(Platform::ShutterMode::Immediate) ? Platform::ShutterMode::Immediate : Platform::ShutterMode::OnClick;
        auto lGrabMode = toPlatformGrabMode(ExportManager::instance()->captureMode());

445
        QTimer::singleShot(0, this, [this, lShutterMode, lGrabMode, theIncludePointer, theIncludeDecorations]() {
446
447
            mPlatform->doGrab(lShutterMode, lGrabMode, theIncludePointer, theIncludeDecorations);
        });
448
449
    }
}