1 /*
2  *  SPDX-FileCopyrightText: 2019 David Redondo <kde@david-redondo.de>
3  *  SPDX-FileCopyrightText: 2015 Boudhayan Gupta <bgupta@kde.org>
4  *
5  *  SPDX-License-Identifier: LGPL-2.0-or-later
6  */
7 
8 #include "SpectacleCore.h"
9 #include "spectacle_core_debug.h"
10 
11 #include "Config.h"
12 #include "ShortcutActions.h"
13 #include "settings.h"
14 
15 #include <KGlobalAccel>
16 #include <KIO/OpenUrlJob>
17 #include <KLocalizedString>
18 #include <KMessageBox>
19 #include <KNotification>
20 #include <KWayland/Client/connection_thread.h>
21 #include <KWayland/Client/plasmashell.h>
22 #include <KWayland/Client/registry.h>
23 #include <KWindowSystem>
24 
25 #include <QApplication>
26 #include <QClipboard>
27 #include <QDir>
28 #include <QDrag>
29 #include <QKeySequence>
30 #include <QMimeData>
31 #include <QPainter>
32 #include <QProcess>
33 #include <QScopedPointer>
34 #include <QScreen>
35 #include <QTimer>
36 
SpectacleCore(QObject * parent)37 SpectacleCore::SpectacleCore(QObject *parent)
38     : QObject(parent)
39 {
40 }
41 
init()42 void SpectacleCore::init()
43 {
44     mPlatform = loadPlatform();
45 
46     // essential connections
47     connect(this, &SpectacleCore::errorMessage, this, &SpectacleCore::showErrorMessage);
48     connect(mPlatform.get(), &Platform::newScreenshotTaken, this, &SpectacleCore::screenshotUpdated);
49     connect(mPlatform.get(), &Platform::newScreensScreenshotTaken, this, &SpectacleCore::screenshotsUpdated);
50     connect(mPlatform.get(), &Platform::newScreenshotFailed, this, &SpectacleCore::screenshotFailed);
51 
52     // set up the export manager
53     auto lExportManager = ExportManager::instance();
54     connect(lExportManager, &ExportManager::errorMessage, this, &SpectacleCore::showErrorMessage);
55     connect(lExportManager, &ExportManager::imageSaved, this, &SpectacleCore::doCopyPath);
56     connect(lExportManager, &ExportManager::forceNotify, this, &SpectacleCore::doNotify);
57     connect(mPlatform.get(), &Platform::windowTitleChanged, lExportManager, &ExportManager::setWindowTitle);
58 
59     // Needed so the QuickEditor can go fullscreen on wayland
60     if (KWindowSystem::isPlatformWayland()) {
61         using namespace KWayland::Client;
62         ConnectionThread *connection = ConnectionThread::fromApplication(this);
63         if (!connection) {
64             return;
65         }
66         Registry *registry = new Registry(this);
67         registry->create(connection);
68         connect(registry, &Registry::plasmaShellAnnounced, this, [this, registry](quint32 name, quint32 version) {
69             mWaylandPlasmashell = registry->createPlasmaShell(name, version, this);
70         });
71         registry->setup();
72         connection->roundtrip();
73     }
74     setUpShortcuts();
75 }
76 
onActivateRequested(QStringList arguments,const QString &)77 void SpectacleCore::onActivateRequested(QStringList arguments, const QString & /*workingDirectory */)
78 {
79     // QCommandLineParser expects the first argument to be the executable name
80     // In the current version it just strips it away
81     arguments.prepend(qApp->applicationFilePath());
82 
83     // We can't re-use QCommandLineParser instances, it preserves earlier parsed values
84     QScopedPointer<QCommandLineParser> parser(new QCommandLineParser);
85     populateCommandLineParser(parser.data());
86     parser->parse(arguments);
87 
88     mStartMode = SpectacleCore::StartMode::Gui;
89     mNotify = true;
90     qint64 lDelayMsec = 0;
91 
92     // are we ask to run in background or dbus mode?
93     if (parser->isSet(QStringLiteral("background"))) {
94         mStartMode = SpectacleCore::StartMode::Background;
95     } else if (parser->isSet(QStringLiteral("dbus"))) {
96         mStartMode = SpectacleCore::StartMode::DBus;
97     }
98 
99     auto lOnClickAvailable = mPlatform->supportedShutterModes().testFlag(Platform::ShutterMode::OnClick);
100     if ((!lOnClickAvailable) && (lDelayMsec < 0)) {
101         lDelayMsec = 0;
102     }
103 
104     // reset last region if it should not be remembered across restarts
105     if (!(Settings::rememberLastRectangularRegion() == Settings::EnumRememberLastRectangularRegion::Always)) {
106         Settings::setCropRegion({0, 0, 0, 0});
107     }
108 
109     Spectacle::CaptureMode lCaptureMode = Spectacle::CaptureMode::AllScreens;
110     // extract the capture mode
111     if (parser->isSet(QStringLiteral("fullscreen"))) {
112         lCaptureMode = Spectacle::CaptureMode::AllScreens;
113     } else if (parser->isSet(QStringLiteral("current"))) {
114         lCaptureMode = Spectacle::CaptureMode::CurrentScreen;
115     } else if (parser->isSet(QStringLiteral("activewindow"))) {
116         lCaptureMode = Spectacle::CaptureMode::ActiveWindow;
117     } else if (parser->isSet(QStringLiteral("region"))) {
118         lCaptureMode = Spectacle::CaptureMode::RectangularRegion;
119     } else if (parser->isSet(QStringLiteral("windowundercursor"))) {
120         lCaptureMode = Spectacle::CaptureMode::TransientWithParent;
121     } else if (parser->isSet(QStringLiteral("transientonly"))) {
122         lCaptureMode = Spectacle::CaptureMode::WindowUnderCursor;
123     } else if (mStartMode == SpectacleCore::StartMode::Gui
124                && (parser->isSet(QStringLiteral("launchonly")) || Settings::onLaunchAction() == Settings::EnumOnLaunchAction::DoNotTakeScreenshot)) {
125         initGuiNoScreenshot();
126         return;
127     } else if (Settings::onLaunchAction() == Settings::EnumOnLaunchAction::UseLastUsedCapturemode) {
128         lCaptureMode = Settings::captureMode();
129         if (Settings::onClickChecked()) {
130             lDelayMsec = -1;
131             takeNewScreenshot(lCaptureMode, lDelayMsec, Settings::includePointer(), Settings::includeDecorations());
132         }
133     }
134 
135     auto lExportManager = ExportManager::instance();
136     lExportManager->setCaptureMode(lCaptureMode);
137 
138     switch (mStartMode) {
139     case StartMode::DBus:
140         mCopyImageToClipboard = Settings::clipboardGroup() == Settings::EnumClipboardGroup::PostScreenshotCopyImage;
141         mCopyLocationToClipboard = Settings::clipboardGroup() == Settings::EnumClipboardGroup::PostScreenshotCopyLocation;
142 
143         qApp->setQuitOnLastWindowClosed(false);
144         break;
145 
146     case StartMode::Background: {
147         mCopyImageToClipboard = false;
148         mCopyLocationToClipboard = false;
149 
150         if (parser->isSet(QStringLiteral("nonotify"))) {
151             mNotify = false;
152         }
153 
154         if (parser->isSet(QStringLiteral("output"))) {
155             mSaveToOutput = true;
156             QString lFileName = parser->value(QStringLiteral("output"));
157             if (!(lFileName.isEmpty() || lFileName.isNull())) {
158                 if (QDir::isRelativePath(lFileName)) {
159                     lFileName = QDir::current().absoluteFilePath(lFileName);
160                 }
161                 setFilename(lFileName);
162             }
163         }
164 
165         if (parser->isSet(QStringLiteral("delay"))) {
166             bool lParseOk = false;
167             qint64 lDelayValue = parser->value(QStringLiteral("delay")).toLongLong(&lParseOk);
168             if (lParseOk) {
169                 lDelayMsec = lDelayValue;
170             }
171         }
172 
173         if (parser->isSet(QStringLiteral("onclick"))) {
174             lDelayMsec = -1;
175         }
176 
177         if (parser->isSet(QStringLiteral("copy-image"))) {
178             mCopyImageToClipboard = true;
179         } else if (parser->isSet(QStringLiteral("copy-path"))) {
180             mCopyLocationToClipboard = true;
181         }
182 
183         if (!mIsGuiInited) {
184             static_cast<QGuiApplication *>(qApp->instance())->setQuitOnLastWindowClosed(false);
185         }
186 
187         auto lIncludePointer = false;
188         auto lIncludeDecorations = true;
189 
190         if (parser->isSet(QStringLiteral("pointer"))) {
191             lIncludePointer = true;
192         }
193 
194         if (parser->isSet(QStringLiteral("no-decoration"))) {
195             lIncludeDecorations = false;
196         }
197 
198         takeNewScreenshot(lCaptureMode, lDelayMsec, lIncludePointer, lIncludeDecorations);
199     } break;
200 
201     case StartMode::Gui:
202         if (!mIsGuiInited) {
203             initGui(lDelayMsec, Settings::includePointer(), Settings::includeDecorations());
204         } else {
205             using Actions = Settings::EnumPrintKeyActionRunning;
206             switch (Settings::printKeyActionRunning()) {
207             case Actions::TakeNewScreenshot: {
208                 // 0 means Immediate, -1 onClick
209                 int timeout = mPlatform->supportedShutterModes().testFlag(Platform::ShutterMode::Immediate) ? 0 : -1;
210                 takeNewScreenshot(Settings::captureMode(), timeout, Settings::includePointer(), Settings::includeDecorations());
211                 break;
212             }
213             case Actions::FocusWindow:
214                 if (mMainWindow->isHidden()) {
215                     mMainWindow->show();
216                 }
217                 if (mMainWindow->isMinimized()) {
218                     mMainWindow->setWindowState(mMainWindow->windowState() & ~Qt::WindowMinimized);
219                 }
220                 mMainWindow->activateWindow();
221                 break;
222             case Actions::StartNewInstance:
223                 QProcess newInstance;
224                 newInstance.setProgram(QStringLiteral("spectacle"));
225                 newInstance.setArguments({QStringLiteral("--new-instance")});
226                 newInstance.startDetached();
227                 break;
228             }
229         }
230 
231         break;
232     }
233 }
234 
setUpShortcuts()235 void SpectacleCore::setUpShortcuts()
236 {
237     KGlobalAccel::self()->setGlobalShortcut(ShortcutActions::self()->openAction(), Qt::Key_Print);
238 
239     KGlobalAccel::self()->setGlobalShortcut(ShortcutActions::self()->fullScreenAction(), Qt::SHIFT | Qt::Key_Print);
240 
241     KGlobalAccel::self()->setGlobalShortcut(ShortcutActions::self()->activeWindowAction(), Qt::META | Qt::Key_Print);
242 
243     KGlobalAccel::self()->setGlobalShortcut(ShortcutActions::self()->windowUnderCursorAction(), Qt::META | Qt::CTRL | Qt::Key_Print);
244 
245     KGlobalAccel::self()->setGlobalShortcut(ShortcutActions::self()->regionAction(), Qt::META | Qt::SHIFT | Qt::Key_Print);
246 
247     KGlobalAccel::self()->setGlobalShortcut(ShortcutActions::self()->currentScreenAction(), QList<QKeySequence>());
248 
249     KGlobalAccel::self()->setGlobalShortcut(ShortcutActions::self()->openWithoutScreenshotAction(), QList<QKeySequence>());
250 }
251 
filename() const252 QString SpectacleCore::filename() const
253 {
254     return mFileNameString;
255 }
256 
setFilename(const QString & filename)257 void SpectacleCore::setFilename(const QString &filename)
258 {
259     mFileNameString = filename;
260     mFileNameUrl = QUrl::fromUserInput(filename);
261 }
262 
takeNewScreenshot(Spectacle::CaptureMode theCaptureMode,int theTimeout,bool theIncludePointer,bool theIncludeDecorations)263 void SpectacleCore::takeNewScreenshot(Spectacle::CaptureMode theCaptureMode, int theTimeout, bool theIncludePointer, bool theIncludeDecorations)
264 {
265     ExportManager::instance()->setCaptureMode(theCaptureMode);
266     auto lGrabMode = toPlatformGrabMode(theCaptureMode);
267 
268     if (theTimeout < 0 || !mPlatform->supportedShutterModes().testFlag(Platform::ShutterMode::Immediate)) {
269         mPlatform->doGrab(Platform::ShutterMode::OnClick, lGrabMode, theIncludePointer, theIncludeDecorations);
270         return;
271     }
272 
273     // when compositing is enabled, we need to give it enough time for the window
274     // to disappear and all the effects are complete before we take the shot. there's
275     // no way of knowing how long the disappearing effects take, but as per default
276     // settings (and unless the user has set an extremely slow effect), 200
277     // milliseconds is a good amount of wait time.
278 
279     auto lMsec = KWindowSystem::compositingActive() ? 200 : 50;
280     QTimer::singleShot(theTimeout + lMsec, this, [this, lGrabMode, theIncludePointer, theIncludeDecorations]() {
281         mPlatform->doGrab(Platform::ShutterMode::Immediate, lGrabMode, theIncludePointer, theIncludeDecorations);
282     });
283 }
284 
showErrorMessage(const QString & theErrString)285 void SpectacleCore::showErrorMessage(const QString &theErrString)
286 {
287     qCDebug(SPECTACLE_CORE_LOG) << "ERROR: " << theErrString;
288 
289     if (mStartMode == StartMode::Gui) {
290         KMessageBox::error(nullptr, theErrString);
291     }
292 }
293 
screenshotsUpdated(const QVector<QImage> & imgs)294 void SpectacleCore::screenshotsUpdated(const QVector<QImage> &imgs)
295 {
296     QMap<const QScreen *, QImage> mapScreens;
297     QList<QScreen *> screens = QGuiApplication::screens();
298 
299     if (imgs.length() != screens.size()) {
300         qWarning(SPECTACLE_CORE_LOG()) << "ERROR: images received from KWin do not match, expected:" << imgs.length() << "actual:" << screens.size();
301         return;
302     }
303 
304     // only used by Spectacle::CaptureMode::RectangularRegion
305     auto it = imgs.constBegin();
306     for (const QScreen *screen : screens) {
307         mapScreens.insert(screen, *it);
308         ++it;
309     }
310 
311     mQuickEditor = std::make_unique<QuickEditor>(mapScreens, mWaylandPlasmashell);
312     connect(mQuickEditor.get(), &QuickEditor::grabDone, this, &SpectacleCore::screenshotUpdated);
313     connect(mQuickEditor.get(), &QuickEditor::grabCancelled, this, &SpectacleCore::screenshotCanceled);
314     mQuickEditor->show();
315 }
316 
screenshotUpdated(const QPixmap & thePixmap)317 void SpectacleCore::screenshotUpdated(const QPixmap &thePixmap)
318 {
319     auto lExportManager = ExportManager::instance();
320 
321     if (lExportManager->captureMode() == Spectacle::CaptureMode::RectangularRegion) {
322         if (mQuickEditor) {
323             mQuickEditor->hide();
324             mQuickEditor.reset(nullptr);
325         }
326     }
327 
328     lExportManager->setPixmap(thePixmap);
329     lExportManager->updatePixmapTimestamp();
330 
331     switch (mStartMode) {
332     case StartMode::Background:
333     case StartMode::DBus: {
334         if (mSaveToOutput || !mCopyImageToClipboard || (Settings::autoSaveImage() && !mSaveToOutput)) {
335             mSaveToOutput = Settings::autoSaveImage();
336             QUrl lSavePath = (mStartMode == StartMode::Background && mFileNameUrl.isValid() && mFileNameUrl.isLocalFile()) ? mFileNameUrl : QUrl();
337             lExportManager->doSave(lSavePath, mNotify);
338         }
339 
340         if (mCopyImageToClipboard) {
341             lExportManager->doCopyToClipboard(mNotify);
342         } else if (mCopyLocationToClipboard) {
343             lExportManager->doCopyLocationToClipboard(mNotify);
344         }
345 
346         // if we don't have a Gui already opened, Q_EMIT allDone
347         if (!mIsGuiInited) {
348             // if we notify, we Q_EMIT allDone only if the user either dismissed the notification or pressed
349             // the "Open" button, otherwise the app closes before it can react to it.
350             if (!mNotify && mCopyImageToClipboard) {
351                 // Allow some time for clipboard content to transfer if '--nonotify' is used, see Bug #411263
352                 // TODO: Find better solution
353                 QTimer::singleShot(250, this, &SpectacleCore::allDone);
354             } else if (!mNotify) {
355                 Q_EMIT allDone();
356             }
357         }
358     } break;
359     case StartMode::Gui:
360         if (thePixmap.isNull()) {
361             mMainWindow->setScreenshotAndShow(thePixmap);
362             mMainWindow->setPlaceholderTextOnLaunch();
363             return;
364         }
365         mMainWindow->setScreenshotAndShow(thePixmap);
366 
367         bool autoSaveImage = Settings::autoSaveImage();
368         mCopyImageToClipboard = Settings::clipboardGroup() == Settings::EnumClipboardGroup::PostScreenshotCopyImage;
369         mCopyLocationToClipboard = Settings::clipboardGroup() == Settings::EnumClipboardGroup::PostScreenshotCopyLocation;
370 
371         if (autoSaveImage && mCopyImageToClipboard) {
372             lExportManager->doSaveAndCopy();
373         } else if (autoSaveImage) {
374             lExportManager->doSave();
375         } else if (mCopyImageToClipboard) {
376             lExportManager->doCopyToClipboard(false);
377         } else if (mCopyLocationToClipboard) {
378             lExportManager->doCopyLocationToClipboard(false);
379         }
380     }
381 }
382 
screenshotCanceled()383 void SpectacleCore::screenshotCanceled()
384 {
385     mQuickEditor->hide();
386     mQuickEditor.reset(nullptr);
387     if (mStartMode == StartMode::Gui) {
388         mMainWindow->setScreenshotAndShow(QPixmap());
389     } else {
390         Q_EMIT allDone();
391     }
392 }
393 
screenshotFailed()394 void SpectacleCore::screenshotFailed()
395 {
396     if (ExportManager::instance()->captureMode() == Spectacle::CaptureMode::RectangularRegion && mQuickEditor) {
397         mQuickEditor->hide();
398         mQuickEditor.reset(nullptr);
399     }
400 
401     switch (mStartMode) {
402     case StartMode::Background:
403         showErrorMessage(i18n("Screenshot capture canceled or failed"));
404         Q_EMIT allDone();
405         return;
406     case StartMode::DBus:
407         Q_EMIT grabFailed();
408         Q_EMIT allDone();
409         return;
410     case StartMode::Gui:
411         mMainWindow->screenshotFailed();
412         mMainWindow->setScreenshotAndShow(QPixmap());
413     }
414 }
415 
doNotify(const QUrl & theSavedAt)416 void SpectacleCore::doNotify(const QUrl &theSavedAt)
417 {
418     KNotification *lNotify = new KNotification(QStringLiteral("newScreenshotSaved"));
419 
420     switch (ExportManager::instance()->captureMode()) {
421     case Spectacle::CaptureMode::AllScreens:
422     case Spectacle::CaptureMode::AllScreensScaled:
423         lNotify->setTitle(i18nc("The entire screen area was captured, heading", "Full Screen Captured"));
424         break;
425     case Spectacle::CaptureMode::CurrentScreen:
426         lNotify->setTitle(i18nc("The current screen was captured, heading", "Current Screen Captured"));
427         break;
428     case Spectacle::CaptureMode::ActiveWindow:
429         lNotify->setTitle(i18nc("The active window was captured, heading", "Active Window Captured"));
430         break;
431     case Spectacle::CaptureMode::WindowUnderCursor:
432     case Spectacle::CaptureMode::TransientWithParent:
433         lNotify->setTitle(i18nc("The window under the mouse was captured, heading", "Window Under Cursor Captured"));
434         break;
435     case Spectacle::CaptureMode::RectangularRegion:
436         lNotify->setTitle(i18nc("A rectangular region was captured, heading", "Rectangular Region Captured"));
437         break;
438     case Spectacle::CaptureMode::InvalidChoice:
439         break;
440     }
441 
442     // a speaking message is prettier than a URL, special case for copy to clipboard and the default pictures location
443     const QString &lSavePath = theSavedAt.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path();
444 
445     static bool isBackgroundMode = (mStartMode == SpectacleCore::StartMode::Background);
446 
447     if (mSaveToOutput || isBackgroundMode) {
448         lNotify->setText(i18n("A screenshot was saved as '%1' to '%2'.", theSavedAt.fileName(), lSavePath));
449         // set to false so it won't show the same message twice
450         isBackgroundMode = false;
451     } else if (mCopyImageToClipboard) {
452         lNotify->setText(i18n("A screenshot was saved to your clipboard."));
453     } else if (lSavePath == QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)) {
454         lNotify->setText(i18nc("Placeholder is filename", "A screenshot was saved as '%1' to your Pictures folder.", theSavedAt.fileName()));
455     } else {
456         lNotify->setText(i18n("A screenshot was saved as '%1' to '%2'.", theSavedAt.fileName(), lSavePath));
457     }
458 
459     if (!theSavedAt.isEmpty()) {
460         lNotify->setUrls({theSavedAt});
461         lNotify->setDefaultAction(i18nc("Open the screenshot we just saved", "Open"));
462         connect(lNotify, QOverload<uint>::of(&KNotification::activated), this, [this, theSavedAt](uint index) {
463             if (index == 0) {
464                 auto job = new KIO::OpenUrlJob(theSavedAt);
465                 job->start();
466                 QTimer::singleShot(250, this, [this] {
467                     if (!mIsGuiInited || Settings::quitAfterSaveCopyExport()) {
468                         Q_EMIT allDone();
469                     }
470                 });
471             }
472         });
473     }
474 
475     connect(lNotify, &QObject::destroyed, this, [this] {
476         if (!mIsGuiInited || Settings::quitAfterSaveCopyExport()) {
477             Q_EMIT allDone();
478         }
479     });
480 
481     lNotify->sendEvent();
482 }
483 
doCopyPath(const QUrl & savedAt)484 void SpectacleCore::doCopyPath(const QUrl &savedAt)
485 {
486     if (Settings::clipboardGroup() == Settings::EnumClipboardGroup::PostScreenshotCopyLocation) {
487         qApp->clipboard()->setText(savedAt.toLocalFile());
488     }
489 }
490 
populateCommandLineParser(QCommandLineParser * lCmdLineParser)491 void SpectacleCore::populateCommandLineParser(QCommandLineParser *lCmdLineParser)
492 {
493     lCmdLineParser->addOptions({
494         {{QStringLiteral("f"), QStringLiteral("fullscreen")}, i18n("Capture the entire desktop (default)")},
495         {{QStringLiteral("m"), QStringLiteral("current")}, i18n("Capture the current monitor")},
496         {{QStringLiteral("a"), QStringLiteral("activewindow")}, i18n("Capture the active window")},
497         {{QStringLiteral("u"), QStringLiteral("windowundercursor")}, i18n("Capture the window currently under the cursor, including parents of pop-up menus")},
498         {{QStringLiteral("t"), QStringLiteral("transientonly")}, i18n("Capture the window currently under the cursor, excluding parents of pop-up menus")},
499         {{QStringLiteral("r"), QStringLiteral("region")}, i18n("Capture a rectangular region of the screen")},
500         {{QStringLiteral("l"), QStringLiteral("launchonly")}, i18n("Launch Spectacle without taking a screenshot")},
501         {{QStringLiteral("g"), QStringLiteral("gui")}, i18n("Start in GUI mode (default)")},
502         {{QStringLiteral("b"), QStringLiteral("background")}, i18n("Take a screenshot and exit without showing the GUI")},
503         {{QStringLiteral("s"), QStringLiteral("dbus")}, i18n("Start in DBus-Activation mode")},
504         {{QStringLiteral("n"), QStringLiteral("nonotify")}, i18n("In background mode, do not pop up a notification when the screenshot is taken")},
505         {{QStringLiteral("o"), QStringLiteral("output")}, i18n("In background mode, save image to specified file"), QStringLiteral("fileName")},
506         {{QStringLiteral("d"), QStringLiteral("delay")},
507          i18n("In background mode, delay before taking the shot (in milliseconds)"),
508          QStringLiteral("delayMsec")},
509         {{QStringLiteral("c"), QStringLiteral("copy-image")}, i18n("In background mode, copy screenshot image to clipboard")},
510         {{QStringLiteral("C"), QStringLiteral("copy-path")}, i18n("In background mode, copy screenshot file path to clipboard")},
511         {{QStringLiteral("w"), QStringLiteral("onclick")}, i18n("Wait for a click before taking screenshot. Invalidates delay")},
512         {{QStringLiteral("i"), QStringLiteral("new-instance")}, i18n("Starts a new GUI instance of spectacle without registering to DBus")},
513         {{QStringLiteral("p"), QStringLiteral("pointer")}, i18n("In background mode, include pointer in the screenshot")},
514         {{QStringLiteral("e"), QStringLiteral("no-decoration")}, i18n("In background mode, exclude decorations in the screenshot")},
515     });
516 }
517 
doStartDragAndDrop()518 void SpectacleCore::doStartDragAndDrop()
519 {
520     auto lExportManager = ExportManager::instance();
521     if (lExportManager->pixmap().isNull()) {
522         return;
523     }
524     QUrl lTempFile = lExportManager->tempSave();
525     if (!lTempFile.isValid()) {
526         return;
527     }
528 
529     auto lMimeData = new QMimeData;
530     lMimeData->setUrls(QList<QUrl>{lTempFile});
531     lMimeData->setData(QStringLiteral("application/x-kde-suggestedfilename"), QFile::encodeName(lTempFile.fileName()));
532 
533     auto lDragHandler = new QDrag(this);
534     lDragHandler->setMimeData(lMimeData);
535     lDragHandler->setPixmap(lExportManager->pixmap().scaled(256, 256, Qt::KeepAspectRatio, Qt::SmoothTransformation));
536     lDragHandler->exec(Qt::CopyAction);
537 }
538 
539 // Private
540 
toPlatformGrabMode(Spectacle::CaptureMode theCaptureMode)541 Platform::GrabMode SpectacleCore::toPlatformGrabMode(Spectacle::CaptureMode theCaptureMode)
542 {
543     switch (theCaptureMode) {
544     case Spectacle::CaptureMode::InvalidChoice:
545         return Platform::GrabMode::InvalidChoice;
546     case Spectacle::CaptureMode::AllScreens:
547         return Platform::GrabMode::AllScreens;
548     case Spectacle::CaptureMode::AllScreensScaled:
549         return Platform::GrabMode::AllScreensScaled;
550     case Spectacle::CaptureMode::RectangularRegion:
551         return Platform::GrabMode::PerScreenImageNative;
552     case Spectacle::CaptureMode::TransientWithParent:
553         return Platform::GrabMode::TransientWithParent;
554     case Spectacle::CaptureMode::CurrentScreen:
555         return Platform::GrabMode::CurrentScreen;
556     case Spectacle::CaptureMode::ActiveWindow:
557         return Platform::GrabMode::ActiveWindow;
558     case Spectacle::CaptureMode::WindowUnderCursor:
559         return Platform::GrabMode::WindowUnderCursor;
560     }
561     return Platform::GrabMode::InvalidChoice;
562 }
563 
ensureGuiInitiad()564 void SpectacleCore::ensureGuiInitiad()
565 {
566     if (!mIsGuiInited) {
567         mMainWindow = std::make_unique<KSMainWindow>(mPlatform->supportedGrabModes(), mPlatform->supportedShutterModes());
568 
569         connect(mMainWindow.get(), &KSMainWindow::newScreenshotRequest, this, &SpectacleCore::takeNewScreenshot);
570         connect(mMainWindow.get(), &KSMainWindow::dragAndDropRequest, this, &SpectacleCore::doStartDragAndDrop);
571 
572         mIsGuiInited = true;
573     }
574 }
575 
initGui(int theDelay,bool theIncludePointer,bool theIncludeDecorations)576 void SpectacleCore::initGui(int theDelay, bool theIncludePointer, bool theIncludeDecorations)
577 {
578     ensureGuiInitiad();
579 
580     takeNewScreenshot(ExportManager::instance()->captureMode(), theDelay, theIncludePointer, theIncludeDecorations);
581 }
582 
initGuiNoScreenshot()583 void SpectacleCore::initGuiNoScreenshot()
584 {
585     ensureGuiInitiad();
586     screenshotUpdated(QPixmap());
587 }
588