diff --git a/doc/index.docbook b/doc/index.docbook --- a/doc/index.docbook +++ b/doc/index.docbook @@ -311,6 +311,7 @@ %H: Hour %m: Minute %S: Second + %T: Window title If a file with this name already exists, a serial number will be appended to the filename. For example, if the filename is Screenshot, and Screenshot.png already exists, the image will be saved as Screenshot-1.png. Typing an extension into the filename will automatically set the image format correctly and remove the extension from the filename field. diff --git a/src/ExportManager.h b/src/ExportManager.h --- a/src/ExportManager.h +++ b/src/ExportManager.h @@ -26,6 +26,8 @@ #include #include +#include "PlatformBackends/ImageGrabber.h" + class QTemporaryDir; class ExportManager : public QObject @@ -52,14 +54,20 @@ 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; QString pixmapDataUri() const; + void setWindowTitle(const QString &windowTitle); + QString windowTitle() const; + ImageGrabber::GrabMode grabMode() const; + void setGrabMode(const ImageGrabber::GrabMode &grabMode); signals: @@ -81,6 +89,7 @@ private: + QString truncatedFilename(const QString &filename); QString makeAutosaveFilename(); using FileNameAlreadyUsedCheck = bool (ExportManager::*)(const QUrl&) const; QString autoIncrementFilename(const QString &baseName, const QString &extension, @@ -97,6 +106,8 @@ QUrl mTempFile; QTemporaryDir *mTempDir; QList mUsedTempFileNames; + QString mWindowTitle; + ImageGrabber::GrabMode mGrabMode; }; #endif // EXPORTMANAGER_H diff --git a/src/ExportManager.cpp b/src/ExportManager.cpp --- a/src/ExportManager.cpp +++ b/src/ExportManager.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -87,6 +88,26 @@ return uri; } +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; @@ -153,41 +174,75 @@ } } +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() { KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("spectaclerc")); KConfigGroup generalConfig = KConfigGroup(config, "General"); const QDateTime timestamp = QDateTime::currentDateTime(); QString baseName = generalConfig.readEntry("save-filename-format", "Screenshot_%Y%M%D_%H%m%S"); - return 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"))); + QString title; + + if (mGrabMode == ImageGrabber::GrabMode::ActiveWindow || + mGrabMode == ImageGrabber::GrabMode::TransientWithParent || + mGrabMode == ImageGrabber::GrabMode::WindowUnderCursor) { + title = mWindowTitle; + } 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) + .replace(QLatin1String("/"), QLatin1String("_")); // POSIX doesn't allow "/" in filenames + if (result.isEmpty()) { + result = QStringLiteral("Screenshot"); + } + return truncatedFilename(result); } QString ExportManager::autoIncrementFilename(const QString &baseName, const QString &extension, FileNameAlreadyUsedCheck isFileNameUsed) { - if (!((this->*isFileNameUsed)(QUrl::fromUserInput(baseName + QLatin1Char('.') + extension)))) { - return baseName + QLatin1Char('.') + extension; + QString result = truncatedFilename(baseName) + QLatin1Literal(".") + extension; + if (!((this->*isFileNameUsed)(QUrl::fromUserInput(result)))) { + return result; } - QString fileNameFmt(baseName + QStringLiteral("-%1.") + extension); + QString fileNameFmt = truncatedFilename(baseName) + QStringLiteral("-%1."); for (quint64 i = 1; i < std::numeric_limits::max(); i++) { - if (!((this->*isFileNameUsed)(QUrl::fromUserInput(fileNameFmt.arg(i))))) { - return fileNameFmt.arg(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 - return fileNameFmt.arg(QStringLiteral("OVERFLOW-") + QString::number(qrand() % 10000)); + result = fileNameFmt.arg(QStringLiteral("OVERFLOW-") + QString::number(qrand() % 10000)); + return truncatedFilename(result) + extension; } QString ExportManager::makeSaveMimetype(const QUrl &url) diff --git a/src/Gui/SettingsDialog/SaveOptionsPage.cpp b/src/Gui/SettingsDialog/SaveOptionsPage.cpp --- a/src/Gui/SettingsDialog/SaveOptionsPage.cpp +++ b/src/Gui/SettingsDialog/SaveOptionsPage.cpp @@ -103,13 +103,15 @@ "%D: Day
" "%H: Hour
" "%m: Minute
" - "%S: Second" + "%S: Second
" + "%T: Window title" "" ); QLabel *fmtHelpText = new QLabel(helpText, this); fmtHelpText->setWordWrap(true); fmtHelpText->setTextFormat(Qt::RichText); + fmtHelpText->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); fmtLayout->addWidget(fmtHelpText); // read in the data diff --git a/src/PlatformBackends/ImageGrabber.h b/src/PlatformBackends/ImageGrabber.h --- a/src/PlatformBackends/ImageGrabber.h +++ b/src/PlatformBackends/ImageGrabber.h @@ -69,6 +69,7 @@ signals: void pixmapChanged(const QPixmap &pixmap); + void windowTitleChanged(const QString &windowTitle); void imageGrabFailed(); void capturePointerChanged(bool capturePointer); void captureDecorationsChanged(bool captureDecorations); diff --git a/src/PlatformBackends/X11ImageGrabber.h b/src/PlatformBackends/X11ImageGrabber.h --- a/src/PlatformBackends/X11ImageGrabber.h +++ b/src/PlatformBackends/X11ImageGrabber.h @@ -87,6 +87,7 @@ QPoint getNativeCursorPosition(); OnClickEventFilter *mNativeEventFilter; + void updateWindowTitle(xcb_window_t window); }; template using CScopedPointer = QScopedPointer; diff --git a/src/PlatformBackends/X11ImageGrabber.cpp b/src/PlatformBackends/X11ImageGrabber.cpp --- a/src/PlatformBackends/X11ImageGrabber.cpp +++ b/src/PlatformBackends/X11ImageGrabber.cpp @@ -44,6 +44,9 @@ #include #include +#include +#include + X11ImageGrabber::X11ImageGrabber(QObject *parent) : ImageGrabber(parent) { @@ -413,15 +416,22 @@ // grabber methods +void X11ImageGrabber::updateWindowTitle(xcb_window_t window) +{ + QString windowTitle = KWindowSystem::readNameProperty(window, XA_WM_NAME); + emit windowTitleChanged(windowTitle); +} + void X11ImageGrabber::grabFullScreen() { mPixmap = getToplevelPixmap(QRect(), mCapturePointer); emit pixmapChanged(mPixmap); } void X11ImageGrabber::grabTransientWithParent() { - xcb_window_t curWin = getRealWindowUnderCursor(); + xcb_window_t curWin = getRealWindowUnderCursor(); + updateWindowTitle(curWin); // grab the image early @@ -514,6 +524,7 @@ void X11ImageGrabber::grabActiveWindow() { xcb_window_t activeWindow = KWindowSystem::activeWindow(); + updateWindowTitle(activeWindow); // if KWin is available, use the KWin DBus interfaces @@ -542,6 +553,9 @@ void X11ImageGrabber::grabWindowUnderCursor() { + const xcb_window_t windowUnderCursor = getRealWindowUnderCursor(); + updateWindowTitle(windowUnderCursor); + // if KWin is available, use the KWin DBus interfaces if (mCaptureDecorations && isKWinAvailable()) { @@ -564,7 +578,7 @@ // else, go native - return grabApplicationWindowHelper(getRealWindowUnderCursor()); + return grabApplicationWindowHelper(windowUnderCursor); } void X11ImageGrabber::grabApplicationWindowHelper(xcb_window_t window) diff --git a/src/SpectacleCore.cpp b/src/SpectacleCore.cpp --- a/src/SpectacleCore.cpp +++ b/src/SpectacleCore.cpp @@ -75,7 +75,7 @@ mImageGrabber = new DummyImageGrabber; } - mImageGrabber->setGrabMode(grabMode); + setGrabMode(grabMode); mImageGrabber->setCapturePointer(guiConfig.readEntry("includePointer", true)); mImageGrabber->setCaptureDecorations(guiConfig.readEntry("includeDecorations", true)); @@ -86,6 +86,7 @@ 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); @@ -132,6 +133,7 @@ void SpectacleCore::setGrabMode(const ImageGrabber::GrabMode &grabMode) { mImageGrabber->setGrabMode(grabMode); + mExportManager->setGrabMode(grabMode); } // Slots @@ -148,7 +150,7 @@ void SpectacleCore::takeNewScreenshot(const ImageGrabber::GrabMode &mode, const int &timeout, const bool &includePointer, const bool &includeDecorations) { - mImageGrabber->setGrabMode(mode); + setGrabMode(mode); mImageGrabber->setCapturePointer(includePointer); mImageGrabber->setCaptureDecorations(includeDecorations);