diff --git a/src/ExportManager.cpp b/src/ExportManager.cpp index b5e618a..cb54536 100644 --- a/src/ExportManager.cpp +++ b/src/ExportManager.cpp @@ -1,479 +1,484 @@ /* * Copyright (C) 2015 Boudhayan Gupta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "ExportManager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "SpectacleConfig.h" ExportManager::ExportManager(QObject *parent) : QObject(parent), mSavePixmap(QPixmap()), mLastSavePath(QUrl()), mTempFile(QUrl()), mTempDir(nullptr) { connect(this, &ExportManager::imageSaved, [this](const QUrl savedAt) { mLastSavePath = savedAt; }); } ExportManager::~ExportManager() { delete mTempDir; } ExportManager* ExportManager::instance() { static ExportManager instance; return &instance; } // screenshot pixmap setter and getter QPixmap ExportManager::pixmap() const { return mSavePixmap; } void ExportManager::setWindowTitle(const QString &windowTitle) { mWindowTitle = windowTitle; } QString ExportManager::windowTitle() const { return mWindowTitle; } ImageGrabber::GrabMode ExportManager::grabMode() const { return mGrabMode; } void ExportManager::setGrabMode(const ImageGrabber::GrabMode &grabMode) { mGrabMode = grabMode; } void ExportManager::setPixmap(const QPixmap &pixmap) { mSavePixmap = pixmap; // reset our saved tempfile if (mTempFile.isValid()) { mUsedTempFileNames.append(mTempFile); QFile file(mTempFile.toLocalFile()); file.remove(); mTempFile = QUrl(); } } +void ExportManager::updatePixmapTimestamp() +{ + mPixmapTimestamp = QDateTime::currentDateTime(); +} + // native file save helpers QString ExportManager::saveLocation() const { KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("spectaclerc")); KConfigGroup generalConfig = KConfigGroup(config, "General"); QString savePath = generalConfig.readPathEntry( "default-save-location", QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); if (savePath.isEmpty() || savePath.isNull()) { savePath = QDir::homePath(); } savePath = QDir::cleanPath(savePath); QDir savePathDir(savePath); if (!(savePathDir.exists())) { savePathDir.mkpath(QStringLiteral(".")); generalConfig.writePathEntry("last-saved-to", savePath); } return savePath; } QUrl ExportManager::lastSavePath() const { return isFileExists(mLastSavePath) ? mLastSavePath : QUrl(); } void ExportManager::setSaveLocation(const QString &savePath) { KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("spectaclerc")); KConfigGroup generalConfig = KConfigGroup(config, "General"); generalConfig.writePathEntry("last-saved-to", savePath); } QUrl ExportManager::getAutosaveFilename() { const QString baseDir = saveLocation(); const QDir baseDirPath(baseDir); const QString filename = makeAutosaveFilename(); const QString fullpath = autoIncrementFilename(baseDirPath.filePath(filename), SpectacleConfig::instance()->saveImageFormat(), &ExportManager::isFileExists); const QUrl fileNameUrl = QUrl::fromUserInput(fullpath); if (fileNameUrl.isValid()) { return fileNameUrl; } else { return QUrl(); } } QString ExportManager::truncatedFilename(QString const &filename) { QString result = filename; constexpr auto maxFilenameLength = 255; constexpr auto maxExtensionLength = 5; // For example, ".jpeg" constexpr auto maxCounterLength = 20; // std::numeric_limits::max() == 18446744073709551615 constexpr auto maxLength = maxFilenameLength - maxCounterLength - maxExtensionLength; result.truncate(maxLength); return result; } QString ExportManager::makeAutosaveFilename() { - const QDateTime timestamp = QDateTime::currentDateTime(); + const QDateTime timestamp = mPixmapTimestamp; QString baseName = SpectacleConfig::instance()->autoSaveFilenameFormat(); QString title; if (mGrabMode == ImageGrabber::GrabMode::ActiveWindow || mGrabMode == ImageGrabber::GrabMode::TransientWithParent || mGrabMode == ImageGrabber::GrabMode::WindowUnderCursor) { title = mWindowTitle.replace(QLatin1String("/"), QLatin1String("_")); // POSIX doesn't allow "/" in filenames } else { // Remove '%T' with separators around it const auto wordSymbol = QStringLiteral(R"(\p{L}\p{M}\p{N})"); const auto separator = QStringLiteral("([^%1]+)").arg(wordSymbol); const auto re = QRegularExpression(QStringLiteral("(.*?)(%1%T|%T%1)(.*?)").arg(separator)); baseName.replace(re, QStringLiteral(R"(\1\5)")); } QString result = baseName.replace(QLatin1String("%Y"), timestamp.toString(QStringLiteral("yyyy"))) .replace(QLatin1String("%y"), timestamp.toString(QStringLiteral("yy"))) .replace(QLatin1String("%M"), timestamp.toString(QStringLiteral("MM"))) .replace(QLatin1String("%D"), timestamp.toString(QStringLiteral("dd"))) .replace(QLatin1String("%H"), timestamp.toString(QStringLiteral("hh"))) .replace(QLatin1String("%m"), timestamp.toString(QStringLiteral("mm"))) .replace(QLatin1String("%S"), timestamp.toString(QStringLiteral("ss"))) .replace(QLatin1String("%T"), title); // Remove leading and trailing '/' while (result.startsWith(QLatin1Char('/'))) { result.remove(0, 1); } while (result.endsWith(QLatin1Char('/'))) { result.chop(1); } if (result.isEmpty()) { result = SpectacleConfig::instance()->defaultFilename(); } return truncatedFilename(result); } QString ExportManager::autoIncrementFilename(const QString &baseName, const QString &extension, FileNameAlreadyUsedCheck isFileNameUsed) { QString result = truncatedFilename(baseName) + QLatin1Literal(".") + extension; if (!((this->*isFileNameUsed)(QUrl::fromUserInput(result)))) { return result; } QString fileNameFmt = truncatedFilename(baseName) + QStringLiteral("-%1."); for (quint64 i = 1; i < std::numeric_limits::max(); i++) { result = fileNameFmt.arg(i) + extension; if (!((this->*isFileNameUsed)(QUrl::fromUserInput(result)))) { return result; } } // unlikely this will ever happen, but just in case we've run // out of numbers result = fileNameFmt.arg(QStringLiteral("OVERFLOW-") + QString::number(qrand() % 10000)); return truncatedFilename(result) + extension; } QString ExportManager::makeSaveMimetype(const QUrl &url) { QMimeDatabase mimedb; QString type = mimedb.mimeTypeForUrl(url).preferredSuffix(); if (type.isEmpty()) { return SpectacleConfig::instance()->saveImageFormat(); } return type; } bool ExportManager::writeImage(QIODevice *device, const QByteArray &format) { QImageWriter imageWriter(device, format); if (!(imageWriter.canWrite())) { emit errorMessage(i18n("QImageWriter cannot write image: %1", imageWriter.errorString())); return false; } return imageWriter.write(mSavePixmap.toImage()); } bool ExportManager::localSave(const QUrl &url, const QString &mimetype) { // Create save directory if it doesn't exist const QUrl dirPath(url.adjusted(QUrl::RemoveFilename)); const QDir dir(dirPath.path()); if (!dir.mkpath(QLatin1String("."))) { emit errorMessage(xi18nc("@info", "Cannot save screenshot because creating " "the directory failed:%1", dirPath.path())); return false; } QFile outputFile(url.toLocalFile()); outputFile.open(QFile::WriteOnly); if(!writeImage(&outputFile, mimetype.toLatin1())) { emit errorMessage(i18n("Cannot save screenshot. Error while writing file.")); return false; } return true; } bool ExportManager::remoteSave(const QUrl &url, const QString &mimetype) { // Check if remote save directory exists const QUrl dirPath(url.adjusted(QUrl::RemoveFilename)); KIO::ListJob *listJob = KIO::listDir(dirPath); listJob->exec(); if (listJob->error() != KJob::NoError) { // Create remote save directory KIO::MkpathJob *mkpathJob = KIO::mkpath(dirPath, QUrl(saveLocation())); mkpathJob->exec(); if (mkpathJob->error() != KJob::NoError) { emit errorMessage(xi18nc("@info", "Cannot save screenshot because creating the " "remote directory failed:%1", dirPath.path())); return false; } } QTemporaryFile tmpFile; if (tmpFile.open()) { if(!writeImage(&tmpFile, mimetype.toLatin1())) { emit errorMessage(i18n("Cannot save screenshot. Error while writing temporary local file.")); return false; } KIO::FileCopyJob *uploadJob = KIO::file_copy(QUrl::fromLocalFile(tmpFile.fileName()), url); uploadJob->exec(); if (uploadJob->error() != KJob::NoError) { emit errorMessage(i18n("Unable to save image. Could not upload file to remote location.")); return false; } return true; } return false; } QUrl ExportManager::tempSave(const QString &mimetype) { // if we already have a temp file saved, use that if (mTempFile.isValid()) { if (QFile(mTempFile.toLocalFile()).exists()) { return mTempFile; } } if (!mTempDir) { mTempDir = new QTemporaryDir(QDir::tempPath() + QDir::separator() + QStringLiteral("Spectacle.XXXXXX")); } if (mTempDir && mTempDir->isValid()) { // create the temporary file itself with normal file name and also unique one for this session // supports the use-case of creating multiple screenshots in a row // and exporting them to the same destination e.g. via clipboard, // where the temp file name is used as filename suggestion const QString baseFileName = mTempDir->path() + QDir::separator() + makeAutosaveFilename(); const QString fileName = autoIncrementFilename(baseFileName, mimetype, &ExportManager::isTempFileAlreadyUsed); QFile tmpFile(fileName); if (tmpFile.open(QFile::WriteOnly)) { if(writeImage(&tmpFile, mimetype.toLatin1())) { mTempFile = QUrl::fromLocalFile(tmpFile.fileName()); // try to make sure 3rd-party which gets the url of the temporary file e.g. on export // properly treats this as readonly, also hide from other users tmpFile.setPermissions(QFile::ReadUser); return mTempFile; } } } emit errorMessage(i18n("Cannot save screenshot. Error while writing temporary local file.")); return QUrl(); } bool ExportManager::save(const QUrl &url) { if (!(url.isValid())) { emit errorMessage(i18n("Cannot save screenshot. The save filename is invalid.")); return false; } QString mimetype = makeSaveMimetype(url); if (url.isLocalFile()) { return localSave(url, mimetype); } return remoteSave(url, mimetype); } bool ExportManager::isFileExists(const QUrl &url) const { if (!(url.isValid())) { return false; } KIO::StatJob * existsJob = KIO::stat(url, KIO::StatJob::DestinationSide, 0); existsJob->exec(); return (existsJob->error() == KJob::NoError); } bool ExportManager::isTempFileAlreadyUsed(const QUrl &url) const { return mUsedTempFileNames.contains(url); } // save slots void ExportManager::doSave(const QUrl &url, bool notify) { if (mSavePixmap.isNull()) { emit errorMessage(i18n("Cannot save an empty screenshot image.")); return; } QUrl savePath = url.isValid() ? url : getAutosaveFilename(); if (save(savePath)) { QDir dir(savePath.path()); dir.cdUp(); setSaveLocation(dir.absolutePath()); emit imageSaved(savePath); if (notify) { emit forceNotify(savePath); } } } bool ExportManager::doSaveAs(QWidget *parentWindow, bool notify) { QStringList supportedFilters; SpectacleConfig *config = SpectacleConfig::instance(); // construct the supported mimetype list Q_FOREACH (auto mimeType, QImageWriter::supportedMimeTypes()) { supportedFilters.append(QString::fromUtf8(mimeType).trimmed()); } // construct the file name QFileDialog dialog(parentWindow); dialog.setAcceptMode(QFileDialog::AcceptSave); dialog.setFileMode(QFileDialog::AnyFile); dialog.setDirectoryUrl(config->lastSaveAsLocation()); dialog.selectFile(makeAutosaveFilename() + QStringLiteral(".png")); dialog.setDefaultSuffix(QStringLiteral(".png")); dialog.setMimeTypeFilters(supportedFilters); dialog.selectMimeTypeFilter(QStringLiteral("image/png")); // launch the dialog if (dialog.exec() == QFileDialog::Accepted) { const QUrl saveUrl = dialog.selectedUrls().first(); if (saveUrl.isValid()) { if (save(saveUrl)) { emit imageSaved(saveUrl); config->setLastSaveAsLocation(saveUrl.adjusted(QUrl::RemoveFilename)); if (notify) { emit forceNotify(saveUrl); } return true; } } } return false; } // misc helpers void ExportManager::doCopyToClipboard() { QApplication::clipboard()->setPixmap(mSavePixmap, QClipboard::Clipboard); } void ExportManager::doPrint(QPrinter *printer) { QPainter painter; if (!(painter.begin(printer))) { emit errorMessage(i18n("Printing failed. The printer failed to initialize.")); delete printer; return; } QRect devRect(0, 0, printer->width(), printer->height()); QPixmap pixmap = mSavePixmap.scaled(devRect.size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); QRect srcRect = pixmap.rect(); srcRect.moveCenter(devRect.center()); painter.drawPixmap(srcRect.topLeft(), pixmap); painter.end(); delete printer; return; } diff --git a/src/ExportManager.h b/src/ExportManager.h index c91e643..74b7468 100644 --- a/src/ExportManager.h +++ b/src/ExportManager.h @@ -1,112 +1,115 @@ /* * Copyright (C) 2015 Boudhayan Gupta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef EXPORTMANAGER_H #define EXPORTMANAGER_H #include #include #include #include +#include #include #include "PlatformBackends/ImageGrabber.h" class QTemporaryDir; class ExportManager : public QObject { Q_OBJECT // singleton-ize the class public: static ExportManager* instance(); private: explicit ExportManager(QObject *parent = nullptr); virtual ~ExportManager(); ExportManager(ExportManager const&) = delete; void operator= (ExportManager const&) = delete; // now the usual stuff public: Q_PROPERTY(QString saveLocation READ saveLocation WRITE setSaveLocation NOTIFY saveLocationChanged) Q_PROPERTY(QPixmap pixmap READ pixmap WRITE setPixmap NOTIFY pixmapChanged) Q_PROPERTY(QString windowTitle READ windowTitle WRITE setWindowTitle) Q_PROPERTY(ImageGrabber::GrabMode grabMode READ grabMode WRITE setGrabMode) void setSaveLocation(const QString &location); QString saveLocation() const; QUrl lastSavePath() const; bool isFileExists(const QUrl &url) const; void setPixmap(const QPixmap &pixmap); QPixmap pixmap() const; + void updatePixmapTimestamp(); void setWindowTitle(const QString &windowTitle); QString windowTitle() const; ImageGrabber::GrabMode grabMode() const; void setGrabMode(const ImageGrabber::GrabMode &grabMode); signals: void errorMessage(const QString &str); void saveLocationChanged(const QString &location); void pixmapChanged(const QPixmap &pixmap); void imageSaved(const QUrl &savedAt); void forceNotify(const QUrl &savedAt); public slots: QUrl getAutosaveFilename(); QUrl tempSave(const QString &mimetype = QStringLiteral("png")); void doSave(const QUrl &url = QUrl(), bool notify = false); bool doSaveAs(QWidget *parentWindow = nullptr, bool notify = false); void doCopyToClipboard(); void doPrint(QPrinter *printer); private: QString truncatedFilename(const QString &filename); QString makeAutosaveFilename(); using FileNameAlreadyUsedCheck = bool (ExportManager::*)(const QUrl&) const; QString autoIncrementFilename(const QString &baseName, const QString &extension, FileNameAlreadyUsedCheck isFileNameUsed); QString makeSaveMimetype(const QUrl &url); bool writeImage(QIODevice *device, const QByteArray &format); bool save(const QUrl &url); bool localSave(const QUrl &url, const QString &mimetype); bool remoteSave(const QUrl &url, const QString &mimetype); bool isTempFileAlreadyUsed(const QUrl &url) const; QPixmap mSavePixmap; + QDateTime mPixmapTimestamp; QUrl mLastSavePath; QUrl mTempFile; QTemporaryDir *mTempDir; QList mUsedTempFileNames; QString mWindowTitle; ImageGrabber::GrabMode mGrabMode; }; #endif // EXPORTMANAGER_H diff --git a/src/SpectacleCore.cpp b/src/SpectacleCore.cpp index e371162..e5fcc4c 100644 --- a/src/SpectacleCore.cpp +++ b/src/SpectacleCore.cpp @@ -1,313 +1,314 @@ /* * Copyright (C) 2015 Boudhayan Gupta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "SpectacleCore.h" #include "Config.h" #include "PlatformBackends/DummyImageGrabber.h" #ifdef XCB_FOUND #include "PlatformBackends/X11ImageGrabber.h" #endif #include "PlatformBackends/KWinWaylandImageGrabber.h" #include #include #include #include #include #include #include #include #include #include #include SpectacleCore::SpectacleCore(StartMode startMode, ImageGrabber::GrabMode grabMode, QString &saveFileName, qint64 delayMsec, bool notifyOnGrab, QObject *parent) : QObject(parent), mExportManager(ExportManager::instance()), mStartMode(startMode), mNotify(notifyOnGrab), mImageGrabber(nullptr), mMainWindow(nullptr), isGuiInited(false) { KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("spectaclerc")); KConfigGroup guiConfig(config, "GuiConfig"); if (!(saveFileName.isEmpty() || saveFileName.isNull())) { if (QDir::isRelativePath(saveFileName)) { saveFileName = QDir::current().absoluteFilePath(saveFileName); } setFilename(saveFileName); } // We might be using the XCB platform (with Xwayland) in a wayland session, // but the X11 grabber won't work in that case. So force the Wayland grabber // in Wayland sessions. if (KWindowSystem::isPlatformWayland() || qstrcmp(qgetenv("XDG_SESSION_TYPE"), "wayland") == 0) { mImageGrabber = new KWinWaylandImageGrabber; } #ifdef XCB_FOUND else if (KWindowSystem::isPlatformX11()) { mImageGrabber = new X11ImageGrabber; } #endif else { mImageGrabber = new DummyImageGrabber; } setGrabMode(grabMode); mImageGrabber->setCapturePointer(guiConfig.readEntry("includePointer", true)); mImageGrabber->setCaptureDecorations(guiConfig.readEntry("includeDecorations", true)); if ((!(mImageGrabber->onClickGrabSupported())) && (delayMsec < 0)) { delayMsec = 0; } connect(mExportManager, &ExportManager::errorMessage, this, &SpectacleCore::showErrorMessage); connect(this, &SpectacleCore::errorMessage, this, &SpectacleCore::showErrorMessage); connect(mImageGrabber, &ImageGrabber::pixmapChanged, this, &SpectacleCore::screenshotUpdated); connect(mImageGrabber, &ImageGrabber::windowTitleChanged, mExportManager, &ExportManager::setWindowTitle); connect(mImageGrabber, &ImageGrabber::imageGrabFailed, this, &SpectacleCore::screenshotFailed); connect(mExportManager, &ExportManager::imageSaved, this, &SpectacleCore::doCopyPath); connect(mExportManager, &ExportManager::forceNotify, this, &SpectacleCore::doNotify); switch (startMode) { case DBusMode: default: break; case BackgroundMode: { int msec = (KWindowSystem::compositingActive() ? 200 : 50) + delayMsec; QTimer::singleShot(msec, mImageGrabber, &ImageGrabber::doImageGrab); } break; case GuiMode: initGui(); break; } } SpectacleCore::~SpectacleCore() { if (mMainWindow) { delete mMainWindow; } delete mImageGrabber; } // Q_PROPERTY stuff QString SpectacleCore::filename() const { return mFileNameString; } void SpectacleCore::setFilename(const QString &filename) { mFileNameString = filename; mFileNameUrl = QUrl::fromUserInput(filename); } ImageGrabber::GrabMode SpectacleCore::grabMode() const { return mImageGrabber->grabMode(); } void SpectacleCore::setGrabMode(const ImageGrabber::GrabMode &grabMode) { mImageGrabber->setGrabMode(grabMode); mExportManager->setGrabMode(grabMode); } // Slots void SpectacleCore::dbusStartAgent() { qApp->setQuitOnLastWindowClosed(true); if (!(mStartMode == GuiMode)) { mStartMode = GuiMode; return initGui(); } } void SpectacleCore::takeNewScreenshot(const ImageGrabber::GrabMode &mode, const int &timeout, const bool &includePointer, const bool &includeDecorations) { setGrabMode(mode); mImageGrabber->setCapturePointer(includePointer); mImageGrabber->setCaptureDecorations(includeDecorations); if (timeout < 0) { mImageGrabber->doOnClickGrab(); return; } // 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. const int msec = KWindowSystem::compositingActive() ? 200 : 50; QTimer::singleShot(timeout + msec, mImageGrabber, &ImageGrabber::doImageGrab); } void SpectacleCore::showErrorMessage(const QString &errString) { qDebug() << "ERROR: " << errString; if (mStartMode == GuiMode) { KMessageBox::error(nullptr, errString); } } void SpectacleCore::screenshotUpdated(const QPixmap &pixmap) { mExportManager->setPixmap(pixmap); + mExportManager->updatePixmapTimestamp(); switch (mStartMode) { case BackgroundMode: case DBusMode: default: { if (mNotify) { connect(mExportManager, &ExportManager::imageSaved, this, &SpectacleCore::doNotify); } QUrl savePath = (mStartMode == BackgroundMode && mFileNameUrl.isValid() && mFileNameUrl.isLocalFile()) ? mFileNameUrl : QUrl(); mExportManager->doSave(savePath); // 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. if (!mNotify) { emit allDone(); } } break; case GuiMode: mMainWindow->setScreenshotAndShow(pixmap); } } void SpectacleCore::screenshotFailed() { switch (mStartMode) { case BackgroundMode: showErrorMessage(i18n("Screenshot capture canceled or failed")); emit allDone(); return; case DBusMode: default: emit grabFailed(); emit allDone(); return; case GuiMode: mMainWindow->show(); } } void SpectacleCore::doNotify(const QUrl &savedAt) { KNotification *notify = new KNotification(QStringLiteral("newScreenshotSaved")); switch(mImageGrabber->grabMode()) { case ImageGrabber::GrabMode::FullScreen: notify->setTitle(i18nc("The entire screen area was captured, heading", "Full Screen Captured")); break; case ImageGrabber::GrabMode::CurrentScreen: notify->setTitle(i18nc("The current screen was captured, heading", "Current Screen Captured")); break; case ImageGrabber::GrabMode::ActiveWindow: notify->setTitle(i18nc("The active window was captured, heading", "Active Window Captured")); break; case ImageGrabber::GrabMode::WindowUnderCursor: case ImageGrabber::GrabMode::TransientWithParent: notify->setTitle(i18nc("The window under the mouse was captured, heading", "Window Under Cursor Captured")); break; case ImageGrabber::GrabMode::RectangularRegion: notify->setTitle(i18nc("A rectangular region was captured, heading", "Rectangular Region Captured")); break; case ImageGrabber::GrabMode::InvalidChoice: default: break; } const QString &path = savedAt.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path(); // a speaking message is prettier than a URL, special case for the default pictures location if (path == QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)) { notify->setText(i18nc("Placeholder is filename", "A screenshot was saved as '%1' to your Pictures folder.", savedAt.fileName())); } else { notify->setText(i18n("A screenshot was saved as '%1' to '%2'.", savedAt.fileName(), path)); } notify->setActions({i18nc("Open the screenshot we just saved", "Open")}); notify->setUrls({savedAt}); connect(notify, &KNotification::action1Activated, this, [this, savedAt] { new KRun(savedAt, nullptr); QTimer::singleShot(250, this, &SpectacleCore::allDone); }); connect(notify, &QObject::destroyed, this, &SpectacleCore::allDone); notify->sendEvent(); } void SpectacleCore::doCopyPath(const QUrl &savedAt) { if (SpectacleConfig::instance()->copySaveLocationToClipboard()) { qApp->clipboard()->setText(savedAt.toLocalFile()); } } void SpectacleCore::doStartDragAndDrop() { QUrl tempFile = mExportManager->tempSave(); if (!tempFile.isValid()) { return; } QMimeData *mimeData = new QMimeData; mimeData->setUrls(QList { tempFile }); mimeData->setData(QStringLiteral("application/x-kde-suggestedfilename"), QFile::encodeName(tempFile.fileName())); QDrag *dragHandler = new QDrag(this); dragHandler->setMimeData(mimeData); dragHandler->setPixmap(mExportManager->pixmap().scaled(256, 256, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation)); dragHandler->exec(Qt::CopyAction); } // Private void SpectacleCore::initGui() { if (!isGuiInited) { mMainWindow = new KSMainWindow(mImageGrabber->onClickGrabSupported()); connect(mMainWindow, &KSMainWindow::newScreenshotRequest, this, &SpectacleCore::takeNewScreenshot); connect(mMainWindow, &KSMainWindow::dragAndDropRequest, this, &SpectacleCore::doStartDragAndDrop); isGuiInited = true; QMetaObject::invokeMethod(mImageGrabber, "doImageGrab", Qt::QueuedConnection); } }