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