diff --git a/CMakeLists.txt b/CMakeLists.txt index a1786dc..bb87bff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,112 +1,118 @@ # KDE Application Version, managed by release script set(KDE_APPLICATIONS_VERSION_MAJOR "19") set(KDE_APPLICATIONS_VERSION_MINOR "07") set(KDE_APPLICATIONS_VERSION_MICRO "70") set(KDE_APPLICATIONS_VERSION "${KDE_APPLICATIONS_VERSION_MAJOR}.${KDE_APPLICATIONS_VERSION_MINOR}.${KDE_APPLICATIONS_VERSION_MICRO}") set(SPECTACLE_VERSION ${KDE_APPLICATIONS_VERSION}) # minimum requirements -cmake_minimum_required (VERSION 3.0 FATAL_ERROR) +cmake_minimum_required (VERSION 3.10 FATAL_ERROR) # Spectacle project project(Spectacle VERSION ${SPECTACLE_VERSION}) set(QT_MIN_VERSION "5.10.0") set(KF5_MIN_VERSION "5.53.0") set(PLASMA_MIN_VERSION "5.12.0") find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE) set( CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ) +# require c++17 + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + # set up kf5 include(KDEInstallDirs) include(KDECMakeSettings) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(ECMInstallIcons) include(ECMSetupVersion) include(FeatureSummary) include(ECMQtDeclareLoggingCategory) include(ECMAddTests) find_package( Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED Core Widgets DBus PrintSupport Test ) find_package( KF5 ${KF5_MIN_VERSION} REQUIRED CoreAddons WidgetsAddons DBusAddons Notifications Config I18n KIO WindowSystem DocTools NewStuff ) # optional components find_package(KF5Kipi) if (KF5Kipi_FOUND) set(KIPI_FOUND 1) endif () find_package(KF5Purpose) if (KF5Purpose_FOUND) set(PURPOSE_FOUND 1) endif() find_package(XCB COMPONENTS XFIXES IMAGE UTIL CURSOR) set(XCB_COMPONENTS_ERRORS FALSE) if (XCB_FOUND) find_package(Qt5X11Extras ${QT_MIN_VERSION} REQUIRED) endif() set(XCB_COMPONENTS_FOUND TRUE) if(NOT XCB_XFIXES_FOUND) set(XCB_COMPONENTS_ERRORS "${XCB_COMPONENTS_ERRORS} XCB-XFIXES ") set(XCB_COMPONENTS_FOUND FALSE) endif() if(NOT XCB_IMAGE_FOUND) set(XCB_COMPONENTS_ERRORS "${XCB_COMPONENTS_ERRORS} XCB-IMAGE ") set(XCB_COMPONENTS_FOUND FALSE) endif() if(NOT XCB_UTIL_FOUND) set(XCB_COMPONENTS_ERRORS "${XCB_COMPONENTS_ERRORS} XCB-UTIL ") set(XCB_COMPONENTS_FOUND FALSE) endif() if(NOT XCB_CURSOR_FOUND) set(XCB_COMPONENTS_ERRORS "${XCB_COMPONENTS_ERRORS} XCB-CURSOR ") set(XCB_COMPONENTS_FOUND FALSE) endif() # fail build if none of the platform backends can be found if (NOT XCB_FOUND OR NOT XCB_COMPONENTS_FOUND) message(FATAL_ERROR "No suitable backend platform was found. Currently supported platforms are: XCB Components Required: ${XCB_COMPONENTS_ERRORS}") endif() # hand off to subdirectories add_subdirectory(src) add_subdirectory(dbus) add_subdirectory(desktop) add_subdirectory(icons) add_subdirectory(doc) -add_subdirectory(tests) +#add_subdirectory(tests) install( FILES spectacle.categories DESTINATION ${KDE_INSTALL_CONFDIR} ) # summaries feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8207814..6442d81 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,104 +1,110 @@ # common - configure file and version definitions configure_file(Config.h.in ${CMAKE_CURRENT_BINARY_DIR}/Config.h) set(CMAKE_AUTORCC 1) # target +if(XCB_FOUND) + set( + SPECTACLE_SRCS_PLATFORM_XCB + Platforms/PlatformXcb.cpp + ) +endif() + +set( + SPECTACLE_SRCS_PLATFORM + Platforms/PlatformLoader.cpp + Platforms/Platform.cpp + Platforms/PlatformNull.cpp + Platforms/PlatformKWinWayland.cpp + ${SPECTACLE_SRCS_PLATFORM_XCB} +) + set( SPECTACLE_SRCS_DEFAULT Main.cpp ExportManager.cpp SpectacleCore.cpp SpectacleConfig.cpp SpectacleDBusAdapter.cpp - PlatformBackends/ImageGrabber.cpp - PlatformBackends/DummyImageGrabber.cpp - PlatformBackends/KWinWaylandImageGrabber.cpp + ${SPECTACLE_SRCS_PLATFORM} Gui/KSMainWindow.cpp Gui/KSWidget.cpp Gui/KSImageWidget.cpp Gui/ExportMenu.cpp Gui/SmartSpinBox.cpp Gui/SettingsDialog/SettingsDialog.cpp Gui/SettingsDialog/SettingsPage.cpp Gui/SettingsDialog/SaveOptionsPage.cpp Gui/SettingsDialog/GeneralOptionsPage.cpp QuickEditor/QuickEditor.cpp ) ecm_qt_declare_logging_category(SPECTACLE_SRCS_DEFAULT HEADER spectacle_core_debug.h IDENTIFIER SPECTACLE_CORE_LOG CATEGORY_NAME org.kde.spectacle.core) ecm_qt_declare_logging_category(SPECTACLE_SRCS_DEFAULT HEADER spectacle_gui_debug.h IDENTIFIER SPECTACLE_GUI_LOG CATEGORY_NAME org.kde.spectacle.gui) -if(XCB_FOUND) - set( - SPECTACLE_SRCS_X11 - PlatformBackends/X11ImageGrabber.cpp - ) -endif() - if(KIPI_FOUND) set( SPECTACLE_SRCS_KIPI KipiInterface/KSGKipiInterface.cpp KipiInterface/KSGKipiInfoShared.cpp KipiInterface/KSGKipiImageCollectionShared.cpp KipiInterface/KSGKipiImageCollectionSelector.cpp ) endif() set( SPECTACLE_SRCS_ALL ${SPECTACLE_SRCS_DEFAULT} ${SPECTACLE_SRCS_KIPI} - ${SPECTACLE_SRCS_X11} ) add_executable( spectacle ${SPECTACLE_SRCS_ALL} ) # link libraries target_link_libraries( spectacle Qt5::DBus Qt5::PrintSupport KF5::CoreAddons KF5::DBusAddons KF5::WidgetsAddons KF5::Notifications KF5::ConfigCore KF5::I18n KF5::KIOWidgets KF5::WindowSystem KF5::NewStuff ) if(XCB_FOUND) target_link_libraries( spectacle XCB::XFIXES XCB::IMAGE XCB::CURSOR XCB::UTIL Qt5::X11Extras ) endif() if(KIPI_FOUND) target_link_libraries ( spectacle KF5::Kipi ) endif() if(PURPOSE_FOUND) target_link_libraries ( spectacle KF5::PurposeWidgets ) endif() install(TARGETS spectacle ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/src/ExportManager.cpp b/src/ExportManager.cpp index bd67015..1ed0a0b 100644 --- a/src/ExportManager.cpp +++ b/src/ExportManager.cpp @@ -1,567 +1,569 @@ -/* - * Copyright (C) 2015 Boudhayan Gupta +/* This file is part of Spectacle, the KDE screenshot utility + * 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 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. + * 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. + * 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. + * + * SPDX-License-Identifier: LGPL-2.0-or-later */ #include "ExportManager.h" #include #include #include #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()), mTempFile(QUrl()), mTempDir(nullptr) { connect(this, &ExportManager::imageSaved, [](const QUrl &savedAt) { SpectacleConfig::instance()->setLastSaveFile(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 +Spectacle::CaptureMode ExportManager::captureMode() const { - return mGrabMode; + return mCaptureMode; } -void ExportManager::setGrabMode(const ImageGrabber::GrabMode &grabMode) +void ExportManager::setCaptureMode(const Spectacle::CaptureMode &theCaptureMode) { - mGrabMode = grabMode; + mCaptureMode = theCaptureMode; } 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(); } void ExportManager::setTimestamp(const QDateTime ×tamp) { mPixmapTimestamp = timestamp; } // native file save helpers QString ExportManager::defaultSaveLocation() const { QString savePath = SpectacleConfig::instance()->defaultSaveLocation(); if (savePath.isEmpty() || savePath.isNull()) { savePath = QDir::homePath(); } savePath = QDir::cleanPath(savePath); QDir savePathDir(savePath); if (!(savePathDir.exists())) { savePathDir.mkpath(QStringLiteral(".")); SpectacleConfig::instance()->setDefaultSaveLocation(savePath); } return savePath; } QUrl ExportManager::getAutosaveFilename() { const QString baseDir = defaultSaveLocation(); 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() { return formatFilename(SpectacleConfig::instance()->autoSaveFilenameFormat()); } QString ExportManager::formatFilename(const QString &nameTemplate) { const QDateTime timestamp = mPixmapTimestamp; QString baseName = nameTemplate; const QString baseDir = defaultSaveLocation(); QString title; - if (mGrabMode == ImageGrabber::GrabMode::ActiveWindow || - mGrabMode == ImageGrabber::GrabMode::TransientWithParent || - mGrabMode == ImageGrabber::GrabMode::WindowUnderCursor) { + if (mCaptureMode == Spectacle::CaptureMode::ActiveWindow || + mCaptureMode == Spectacle::CaptureMode::TransientWithParent || + mCaptureMode == Spectacle::CaptureMode::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); // check if basename includes %[N]d token for sequential file numbering QRegularExpression paddingRE; paddingRE.setPattern(QStringLiteral("%(\\d*)d")); QRegularExpressionMatch paddingMatch; while (result.indexOf(paddingRE, 0, &paddingMatch) > -1) { int highestFileNumber = 0; // determine padding value int paddedLength = 1; if (!paddingMatch.captured(1).isEmpty()) { paddedLength = paddingMatch.captured(1).toInt(); } // search save directory for files QDir dir(baseDir); const QStringList fileNames = dir.entryList(QDir::Files, QDir::Name); // if there are files in the directory... if (fileNames.length() > 0) { QString resultCopy = result; QRegularExpression fileNumberRE; const QString replacement = QStringLiteral("(\\d{").append(QString::number(paddedLength)).append(QLatin1String(",})")); const QString fullNameMatch = QStringLiteral("^").append(resultCopy.replace(paddingMatch.captured(),replacement)).append(QStringLiteral("\\..*$")); fileNumberRE.setPattern(fullNameMatch); // ... check the file names for string matching token with padding specified in result const QStringList filteredFiles = fileNames.filter(fileNumberRE); // if there are files in the direcory that look like the file name with sequential numbering if (filteredFiles.length() > 0) { // loop through filtered file names looking for highest number for (const QString &filteredFile: filteredFiles) { int currentFileNumber = fileNumberRE.match(filteredFile).captured(1).toInt(); if (currentFileNumber > highestFileNumber) { highestFileNumber = currentFileNumber; } } } } // replace placeholder with next number padded const QString nextFileNumberPadded = QString::number(highestFileNumber + 1).rightJustified(paddedLength, QLatin1Char('0')); result = result.replace(paddingMatch.captured(), nextFileNumberPadded); } // 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); imageWriter.setQuality(SpectacleConfig::instance()->compressionQuality()); 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(QStringLiteral("."))) { 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(defaultSaveLocation())); 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(); SpectacleConfig::instance()->setLastSaveFile(savePath); 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 const QString filenameExtension = SpectacleConfig::instance()->saveImageFormat(); const QString mimetype = QMimeDatabase().mimeTypeForFile(QStringLiteral("~/fakefile.") + filenameExtension, QMimeDatabase::MatchExtension).name(); QFileDialog dialog(parentWindow); dialog.setAcceptMode(QFileDialog::AcceptSave); dialog.setFileMode(QFileDialog::AnyFile); dialog.setDirectoryUrl(config->lastSaveAsLocation()); dialog.selectFile(makeAutosaveFilename() + QStringLiteral(".") + filenameExtension); dialog.setDefaultSuffix(QStringLiteral(".") + filenameExtension); dialog.setMimeTypeFilters(supportedFilters); dialog.selectMimeTypeFilter(mimetype); // launch the dialog if (dialog.exec() == QFileDialog::Accepted) { const QUrl saveUrl = dialog.selectedUrls().first(); if (saveUrl.isValid()) { if (save(saveUrl)) { emit imageSaved(saveUrl); config->setLastSaveAsFile(saveUrl); if (notify) { emit forceNotify(saveUrl); } return true; } } } return false; } // misc helpers void ExportManager::doCopyToClipboard(bool notify) { auto data = new QMimeData(); data->setImageData(mSavePixmap.toImage()); data->setData(QStringLiteral("x-kde-force-image-copy"), QByteArray()); QApplication::clipboard()->setMimeData(data, QClipboard::Clipboard); if (notify) { emit forceNotify(QUrl()); } } 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; } const QMap ExportManager::filenamePlaceholders { {QStringLiteral("%Y"), ki18nc( "A placeholder in the user configurable filename will replaced by the specified value", "Year (4 digit)")}, {QStringLiteral("%y"), ki18nc( "A placeholder in the user configurable filename will replaced by the specified value", "Year (2 digit)")}, {QStringLiteral("%M"), ki18nc( "A placeholder in the user configurable filename will replaced by the specified value", "Month")}, {QStringLiteral("%D"), ki18nc( "A placeholder in the user configurable filename will replaced by the specified value", "Day")}, {QStringLiteral("%H"), ki18nc( "A placeholder in the user configurable filename will replaced by the specified value", "Hour")}, {QStringLiteral("%m"), ki18nc( "A placeholder in the user configurable filename will replaced by the specified value", "Minute")}, {QStringLiteral("%S"), ki18nc( "A placeholder in the user configurable filename will replaced by the specified value", "Second")}, {QStringLiteral("%T"), ki18nc( "A placeholder in the user configurable filename will replaced by the specified value", "Window Title")}, {QStringLiteral("%d"), ki18nc( "A placeholder in the user configurable filename will replaced by the specified value", "Sequential numbering")}, {QStringLiteral("%Nd"), ki18nc( "A placeholder in the user configurable filename will replaced by the specified value", "Sequential numbering, padded out to N digits")}, }; diff --git a/src/ExportManager.h b/src/ExportManager.h index 03d5808..0a871e7 100644 --- a/src/ExportManager.h +++ b/src/ExportManager.h @@ -1,117 +1,111 @@ -/* - * Copyright (C) 2015 Boudhayan Gupta +/* This file is part of Spectacle, the KDE screenshot utility + * 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 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. + * 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. + * 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. + * + * SPDX-License-Identifier: LGPL-2.0-or-later */ -#ifndef EXPORTMANAGER_H -#define EXPORTMANAGER_H +#pragma once + +#include #include #include #include #include #include #include #include - #include -#include "PlatformBackends/ImageGrabber.h" - class QTemporaryDir; -class ExportManager : public QObject +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(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) - QString defaultSaveLocation() const; bool isFileExists(const QUrl &url) const; void setPixmap(const QPixmap &pixmap); QPixmap pixmap() const; void updatePixmapTimestamp(); void setTimestamp(const QDateTime ×tamp); - void setWindowTitle(const QString &windowTitle); QString windowTitle() const; - ImageGrabber::GrabMode grabMode() const; - void setGrabMode(const ImageGrabber::GrabMode &grabMode); + Spectacle::CaptureMode captureMode() const; + void setCaptureMode(const Spectacle::CaptureMode &theCaptureMode); QString formatFilename(const QString &nameTemplate); static const QMap filenamePlaceholders; Q_SIGNALS: void errorMessage(const QString &str); void pixmapChanged(const QPixmap &pixmap); void imageSaved(const QUrl &savedAt); void forceNotify(const QUrl &savedAt); public Q_SLOTS: QUrl getAutosaveFilename(); QUrl tempSave(const QString &mimetype = QStringLiteral("png")); + void setWindowTitle(const QString &windowTitle); void doSave(const QUrl &url = QUrl(), bool notify = false); bool doSaveAs(QWidget *parentWindow = nullptr, bool notify = false); void doCopyToClipboard(bool notify); 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 mTempFile; QTemporaryDir *mTempDir; QList mUsedTempFileNames; QString mWindowTitle; - ImageGrabber::GrabMode mGrabMode; + Spectacle::CaptureMode mCaptureMode { Spectacle::CaptureMode::AllScreens }; }; - -#endif // EXPORTMANAGER_H diff --git a/src/Gui/KSImageWidget.cpp b/src/Gui/KSImageWidget.cpp index a627f5a..b127257 100644 --- a/src/Gui/KSImageWidget.cpp +++ b/src/Gui/KSImageWidget.cpp @@ -1,91 +1,93 @@ -/* - * Copyright (C) 2015 Boudhayan Gupta +/* This file is part of Spectacle, the KDE screenshot utility + * 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 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. + * 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. + * 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. + * + * SPDX-License-Identifier: LGPL-2.0-or-later */ #include "KSImageWidget.h" KSImageWidget::KSImageWidget(QWidget *parent): QLabel(parent), mPixmap(QPixmap()) { mDSEffect = new QGraphicsDropShadowEffect(this); mDSEffect->setBlurRadius(SpectacleImage::SHADOW_RADIUS); mDSEffect->setOffset(0); mDSEffect->setColor(QColor(Qt::black)); setGraphicsEffect(mDSEffect); setCursor(Qt::OpenHandCursor); setAlignment(Qt::AlignCenter); setMinimumSize(size()); } void KSImageWidget::setScreenshot(const QPixmap &pixmap) { mPixmap = pixmap; setToolTip(i18n("Image Size: %1x%2 pixels", mPixmap.width(), mPixmap.height())); setScaledPixmap(); } void KSImageWidget::setScaledPixmap() { const qreal scale = qApp->devicePixelRatio(); QPixmap scaledPixmap = mPixmap.scaled(size() * scale, Qt::KeepAspectRatio, Qt::SmoothTransformation); scaledPixmap.setDevicePixelRatio(scale); setPixmap(scaledPixmap); } // drag handlers void KSImageWidget::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { mDragStartPosition = event->pos(); setCursor(Qt::ClosedHandCursor); } } void KSImageWidget::mouseReleaseEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { setCursor(Qt::OpenHandCursor); } } void KSImageWidget::mouseMoveEvent(QMouseEvent *event) { if (!(event->buttons() & Qt::LeftButton)) { return; } if ((event->pos() - mDragStartPosition).manhattanLength() < QGuiApplication::styleHints()->startDragDistance()) { return; } setCursor(Qt::OpenHandCursor); emit dragInitiated(); } // resize handler void KSImageWidget::resizeEvent(QResizeEvent *event) { - Q_UNUSED(event); + Q_UNUSED(event) setScaledPixmap(); } diff --git a/src/Gui/KSImageWidget.h b/src/Gui/KSImageWidget.h index b843944..bfbcba4 100644 --- a/src/Gui/KSImageWidget.h +++ b/src/Gui/KSImageWidget.h @@ -1,68 +1,67 @@ -/* - * Copyright (C) 2015 Boudhayan Gupta +/* This file is part of Spectacle, the KDE screenshot utility + * 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 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. + * 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. + * 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. + * + * SPDX-License-Identifier: LGPL-2.0-or-later */ -#ifndef KSIMAGEWIDGET_H -#define KSIMAGEWIDGET_H +#pragma once #include #include #include #include #include #include #include #include #include namespace SpectacleImage { static const int SHADOW_RADIUS = 5; } class KSImageWidget : public QLabel { Q_OBJECT public: explicit KSImageWidget(QWidget *parent = nullptr); void setScreenshot(const QPixmap &pixmap); Q_SIGNALS: void dragInitiated(); protected: void mousePressEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; void resizeEvent(QResizeEvent *event) override; private: void setScaledPixmap(); QGraphicsDropShadowEffect *mDSEffect; QPixmap mPixmap; QPoint mDragStartPosition; }; - -#endif // KSIMAGEWIDGET_H diff --git a/src/Gui/KSMainWindow.cpp b/src/Gui/KSMainWindow.cpp index e9ef8c5..6d01b58 100644 --- a/src/Gui/KSMainWindow.cpp +++ b/src/Gui/KSMainWindow.cpp @@ -1,434 +1,428 @@ -/* - * Copyright (C) 2015 Boudhayan Gupta +/* This file is part of Spectacle, the KDE screenshot utility + * 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 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. + * 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. + * 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. + * + * SPDX-License-Identifier: LGPL-2.0-or-later */ #include "KSMainWindow.h" - #include "Config.h" #include "SettingsDialog/SettingsDialog.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - +#include #include #include #include #include #include #include #include + #ifdef XCB_FOUND #include #include #endif +#include +#include +#include +#include +#include +#include +#include +#include static const int DEFAULT_WINDOW_HEIGHT = 420; static const int DEFAULT_WINDOW_WIDTH = 840; static const int MAXIMUM_WINDOW_WIDTH = 1000; -KSMainWindow::KSMainWindow(const QVector& supportedModes, bool onClickAvailable, QWidget *parent) : +KSMainWindow::KSMainWindow(const Platform::GrabModes &theGrabModes, const Platform::ShutterModes &theShutterModes, QWidget *parent) : QDialog(parent), - mKSWidget(new KSWidget(supportedModes, this)), + mKSWidget(new KSWidget(theGrabModes, this)), mDivider(new QFrame(this)), mDialogButtonBox(new QDialogButtonBox(this)), mConfigureButton(new QToolButton(this)), mToolsButton(new QPushButton(this)), mSendToButton(new QPushButton(this)), mClipboardButton(new QToolButton(this)), mSaveButton(new QToolButton(this)), mSaveMenu(new QMenu(this)), mSaveAsAction(new QAction(this)), mSaveAction(new QAction(this)), mMessageWidget(new KMessageWidget(this)), mToolsMenu(new QMenu(this)), mScreenRecorderToolsMenu(new QMenu(this)), mExportMenu(new ExportMenu(this)), - mOnClickAvailable(onClickAvailable) + mShutterModes(theShutterModes) { // before we do anything, we need to set a window property // that skips the close/hide window animation on kwin. this // fixes a ghost image of the spectacle window that appears // on subsequent screenshots taken with the take new screenshot // button // // credits for this goes to Thomas Lübking #ifdef XCB_FOUND if (KWindowSystem::isPlatformX11()) { // create a window if we haven't already. note that the QWidget constructor // should already have done this if (winId() == 0) { create(0, true, true); } // do the xcb shenanigans - xcb_connection_t *xcbConn = QX11Info::connection(); const QByteArray effectName = QByteArrayLiteral("_KDE_NET_WM_SKIP_CLOSE_ANIMATION"); xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(xcbConn, false, effectName.length(), effectName.constData()); QScopedPointer atom(xcb_intern_atom_reply(xcbConn, atomCookie, nullptr)); if (atom.isNull()) { goto done; } uint32_t value = 1; xcb_change_property(xcbConn, XCB_PROP_MODE_REPLACE, winId(), atom->atom, XCB_ATOM_CARDINAL, 32, 1, &value); } done: #endif QMetaObject::invokeMethod(this, "init", Qt::QueuedConnection); } -KSMainWindow::~KSMainWindow() -{} - // GUI init void KSMainWindow::init() { KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("spectaclerc")); KConfigGroup guiConfig(config, "GuiConfig"); // window properties setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); QPoint location = guiConfig.readEntry("window-position", QPoint(50, 50)); move(location); // change window title on save connect(ExportManager::instance(), &ExportManager::imageSaved, this, &KSMainWindow::setScreenshotWindowTitle); // the KSGWidget connect(mKSWidget, &KSWidget::newScreenshotRequest, this, &KSMainWindow::captureScreenshot); connect(mKSWidget, &KSWidget::dragInitiated, this, &KSMainWindow::dragAndDropRequest); // the Button Bar mDialogButtonBox->setStandardButtons(QDialogButtonBox::Help); mConfigureButton->setDefaultAction(KStandardAction::preferences(this, SLOT(showPreferencesDialog()), this)); mConfigureButton->setText(i18n("Configure...")); mConfigureButton->setToolTip(i18n("Change Spectacle's settings.")); mConfigureButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); mDialogButtonBox->addButton(mConfigureButton, QDialogButtonBox::ResetRole); KGuiItem::assign(mToolsButton, KGuiItem(i18n("Tools"))); mToolsButton->setIcon(QIcon::fromTheme(QStringLiteral("tools"), QIcon::fromTheme(QStringLiteral("application-menu")))); mDialogButtonBox->addButton(mToolsButton, QDialogButtonBox::ActionRole); mToolsButton->setMenu(mToolsMenu); KGuiItem::assign(mSendToButton, KGuiItem(i18n("Export"))); mSendToButton->setIcon(QIcon::fromTheme(QStringLiteral("document-share"))); mDialogButtonBox->addButton(mSendToButton, QDialogButtonBox::ActionRole); mClipboardButton->setDefaultAction(KStandardAction::copy(this, SLOT(sendToClipboard()), this)); mClipboardButton->setText(i18n("Copy to Clipboard")); mClipboardButton->setToolTip(i18n("Copy the current screenshot image to the clipboard.")); mClipboardButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); mDialogButtonBox->addButton(mClipboardButton, QDialogButtonBox::ActionRole); mSaveButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); mSaveButton->setMenu(mSaveMenu); mSaveButton->setPopupMode(QToolButton::MenuButtonPopup); mDialogButtonBox->addButton(mSaveButton, QDialogButtonBox::ActionRole); // the help menu KHelpMenu *helpMenu = new KHelpMenu(this, KAboutData::applicationData(), true); mDialogButtonBox->button(QDialogButtonBox::Help)->setMenu(helpMenu->menu()); // the tools menu mToolsMenu->addAction(QIcon::fromTheme(QStringLiteral("document-open-folder")), i18n("Open Screenshots Folder"), this, &KSMainWindow::openScreenshotsFolder); mToolsMenu->addAction(KStandardAction::print(this, &KSMainWindow::showPrintDialog, this)); mScreenRecorderToolsMenu = mToolsMenu->addMenu(i18n("Record Screen")); connect(mScreenRecorderToolsMenu, &QMenu::aboutToShow, [this]() { KMoreToolsMenuFactory *moreToolsMenuFactory = new KMoreToolsMenuFactory(QStringLiteral("spectacle/screenrecorder-tools")); moreToolsMenuFactory->setParentWidget(this); mScreenrecorderToolsMenuFactory.reset(moreToolsMenuFactory); mScreenRecorderToolsMenu->clear(); mScreenrecorderToolsMenuFactory->fillMenuFromGroupingNames(mScreenRecorderToolsMenu, { QStringLiteral("screenrecorder") }); } ); // the save menu mSaveAsAction = KStandardAction::saveAs(this, &KSMainWindow::saveAs, this); mSaveAction = KStandardAction::save(this, &KSMainWindow::save, this); mSaveMenu->addAction(mSaveAsAction); mSaveMenu->addAction(mSaveAction); setDefaultSaveAction(); // message widget connect(mMessageWidget, &KMessageWidget::linkActivated, this, [](const QString &str) { QDesktopServices::openUrl(QUrl(str)); } ); // layouts - mDivider->setFrameShape(QFrame::HLine); mDivider->setLineWidth(2); QVBoxLayout *layout = new QVBoxLayout(this); layout->addWidget(mKSWidget); layout->addWidget(mMessageWidget); layout->addWidget(mDivider); layout->addWidget(mDialogButtonBox); mMessageWidget->hide(); // populate our send-to actions - mSendToButton->setMenu(mExportMenu); connect(mExportMenu, &ExportMenu::imageShared, this, &KSMainWindow::showImageSharedFeedback); - // disable onClick mode if not available on the platform - - if (!mOnClickAvailable) { - mKSWidget->disableOnClick(); + // lock down the onClick mode depending on available shutter modes + if (!mShutterModes.testFlag(Platform::ShutterMode::OnClick)) { + mKSWidget->lockOnClickDisabled(); + } else if (!mShutterModes.testFlag(Platform::ShutterMode::Immediate)) { + mKSWidget->lockOnClickEnabled(); } resize(QSize(DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT).expandedTo(minimumSize())); // Allow Ctrl+Q to quit the app QAction *actionQuit = KStandardAction::quit(qApp, &QApplication::quit, this); actionQuit->setShortcut(QKeySequence::Quit); addAction(actionQuit); // done with the init } int KSMainWindow::windowWidth(const QPixmap &pixmap) const { // Calculates what the width of the window should be for the captured image to perfectly fit // the area reserved for the image, with the height already set. const float pixmapAspectRatio = (float)pixmap.width() / pixmap.height(); const int imageHeight = mKSWidget->height() - 2 * layout()->spacing(); const int imageWidth = pixmapAspectRatio * imageHeight; int alignedWindowWidth = qMin(mKSWidget->imagePaddingWidth() + imageWidth, MAXIMUM_WINDOW_WIDTH); alignedWindowWidth += layout()->contentsMargins().left() + layout()->contentsMargins().right(); alignedWindowWidth += 2; // margins is removing 1 - 1 pixel for some reason return alignedWindowWidth; } void KSMainWindow::setDefaultSaveAction() { switch (SpectacleConfig::instance()->lastUsedSaveMode()) { case SaveMode::SaveAs: default: mSaveButton->setDefaultAction(mSaveAsAction); mSaveButton->setText(i18n("Save As...")); break; case SaveMode::Save: mSaveButton->setDefaultAction(mSaveAction); break; } } // overrides void KSMainWindow::moveEvent(QMoveEvent *event) { - Q_UNUSED(event); + Q_UNUSED(event) KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("spectaclerc")); KConfigGroup guiConfig(config, "GuiConfig"); guiConfig.writeEntry("window-position", pos()); guiConfig.sync(); } // slots -void KSMainWindow::captureScreenshot(ImageGrabber::GrabMode mode, int timeout, bool includePointer, bool includeDecorations) +void KSMainWindow::captureScreenshot(Spectacle::CaptureMode theCaptureMode, int theTimeout, bool theIncludePointer, bool theIncludeDecorations) { hide(); mMessageWidget->hide(); - emit newScreenshotRequest(mode, timeout, includePointer, includeDecorations); + emit newScreenshotRequest(theCaptureMode, theTimeout, theIncludePointer, theIncludeDecorations); } void KSMainWindow::setScreenshotAndShow(const QPixmap &pixmap) { mKSWidget->setScreenshotPixmap(pixmap); mExportMenu->imageUpdated(); setWindowTitle(i18nc("@title:window Unsaved Screenshot", "Unsaved[*]")); setWindowModified(true); show(); resize(QSize(windowWidth(pixmap), DEFAULT_WINDOW_HEIGHT)); } void KSMainWindow::showPrintDialog() { QPrinter *printer = new QPrinter(QPrinter::HighResolution); QPrintDialog printDialog(printer, this); if (printDialog.exec() == QDialog::Accepted) { ExportManager::instance()->doPrint(printer); return; } delete printer; } void KSMainWindow::openScreenshotsFolder() { // Highlight last screenshot in file manager if user saved at least once ever // (since last save and saveas file names are stored in spectaclerc), otherwise, // if in save mode, open default save location from configure > save > location // if in save as mode, open last save as files location // failsafe for either option is default save location from configure > save > location SpectacleConfig *cfgManager = SpectacleConfig::instance(); ExportManager *exportManager = ExportManager::instance(); QUrl location; switch(cfgManager->lastUsedSaveMode()) { case SaveMode::Save: location = cfgManager->lastSaveFile(); if (!exportManager->isFileExists(location)) { location = QUrl(cfgManager->defaultSaveLocation()); } break; case SaveMode::SaveAs: location = cfgManager->lastSaveAsFile(); // already has a "/" at the end if (!exportManager->isFileExists(location)) { location = cfgManager->lastSaveAsLocation(); } break; } KIO::highlightInFileManager({location}); } void KSMainWindow::quit(const QuitBehavior quitBehavior) { qApp->setQuitOnLastWindowClosed(false); hide(); if (quitBehavior == QuitBehavior::QuitImmediately) { // Allow some time for clipboard content to transfer // TODO: Find better solution QTimer::singleShot(250, qApp, &QApplication::quit); } // TODO for else case: // Currently it is expected that you emit forceNotify, and finally quit // via a callback through KNotification::action1Activated. However, that // is not working quite right, see Bug #389694 which needs fixing. } void KSMainWindow::showInlineMessage(const QString& message, const KMessageWidget::MessageType messageType, const MessageDuration messageDuration) { mMessageWidget->setText(message); mMessageWidget->setMessageType(messageType); switch (messageType) { case KMessageWidget::Error: mMessageWidget->setIcon(QIcon::fromTheme(QStringLiteral("dialog-error"))); break; case KMessageWidget::Warning: mMessageWidget->setIcon(QIcon::fromTheme(QStringLiteral("dialog-warning"))); break; case KMessageWidget::Positive: mMessageWidget->setIcon(QIcon::fromTheme(QStringLiteral("dialog-ok-apply"))); break; case KMessageWidget::Information: mMessageWidget->setIcon(QIcon::fromTheme(QStringLiteral("dialog-information"))); break; - default: - ; } mMessageWidget->animatedShow(); if (messageDuration == MessageDuration::AutoHide) { QTimer::singleShot(10000, mMessageWidget, &KMessageWidget::animatedHide); } } void KSMainWindow::showImageSharedFeedback(bool error, const QString &message) { if (error) { showInlineMessage(i18n("There was a problem sharing the image: %1", message), KMessageWidget::Error); } else { if (message.isEmpty()) { showInlineMessage(i18n("Image shared"), KMessageWidget::Positive); } else { showInlineMessage(i18n("The shared image link (%1) has been copied to the clipboard.", message), KMessageWidget::Positive, MessageDuration::Persistent); QApplication::clipboard()->setText(message); } } } void KSMainWindow::sendToClipboard() { bool notify = false; ExportManager::instance()->doCopyToClipboard(notify); SpectacleConfig::instance()->quitAfterSaveOrCopyChecked() ? quit() : showInlineMessage(i18n("The screenshot has been copied to the clipboard."), KMessageWidget::Information); } void KSMainWindow::showPreferencesDialog() { SettingsDialog prefDialog(this); prefDialog.exec(); } void KSMainWindow::setScreenshotWindowTitle(const QUrl &location) { setWindowTitle(location.fileName()); setWindowModified(false); } void KSMainWindow::save() { SpectacleConfig::instance()->setLastUsedSaveMode(SaveMode::Save); setDefaultSaveAction(); const bool quitChecked = SpectacleConfig::instance()->quitAfterSaveOrCopyChecked(); ExportManager::instance()->doSave(QUrl(), /* notify */ quitChecked); if (quitChecked) { quit(QuitBehavior::QuitExternally); } } void KSMainWindow::saveAs() { SpectacleConfig::instance()->setLastUsedSaveMode(SaveMode::SaveAs); setDefaultSaveAction(); const bool quitChecked = SpectacleConfig::instance()->quitAfterSaveOrCopyChecked(); if (ExportManager::instance()->doSaveAs(this, /* notify */ quitChecked) && quitChecked) { quit(QuitBehavior::QuitExternally); } } diff --git a/src/Gui/KSMainWindow.h b/src/Gui/KSMainWindow.h index 0862cf4..510106b 100644 --- a/src/Gui/KSMainWindow.h +++ b/src/Gui/KSMainWindow.h @@ -1,112 +1,113 @@ -/* - * Copyright (C) 2015 Boudhayan Gupta +/* This file is part of Spectacle, the KDE screenshot utility + * 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 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. + * 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. + * 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. + * + * SPDX-License-Identifier: LGPL-2.0-or-later */ -#ifndef KSMAINWINDOW_H -#define KSMAINWINDOW_H +#pragma once #include #include #include #include #include #include +#include -#include "PlatformBackends/ImageGrabber.h" -#include "ExportMenu.h" -#include "KSWidget.h" +#include "SpectacleCommon.h" #include "SpectacleConfig.h" +#include "KSWidget.h" +#include "ExportMenu.h" +#include "Platforms/Platform.h" -class KMoreToolsMenuFactory; +#include -class KSMainWindow : public QDialog +class KSMainWindow: public QDialog { Q_OBJECT public: - explicit KSMainWindow(const QVector& supportedModes, bool onClickAvailable, QWidget *parent = nullptr); - ~KSMainWindow() override; + explicit KSMainWindow(const Platform::GrabModes &theGrabModes, const Platform::ShutterModes &theShutterModes, QWidget *parent = nullptr); + virtual ~KSMainWindow() = default; private: enum class QuitBehavior { QuitImmediately, QuitExternally }; void quit(const QuitBehavior quitBehavior = QuitBehavior::QuitImmediately); enum class MessageDuration { AutoHide, Persistent }; void showInlineMessage(const QString& message, const KMessageWidget::MessageType messageType, const MessageDuration messageDuration = MessageDuration::AutoHide); private Q_SLOTS: - void captureScreenshot(ImageGrabber::GrabMode mode, int timeout, bool includePointer, bool includeDecorations); + void captureScreenshot(Spectacle::CaptureMode theCaptureMode, int theTimeout, bool theIncludePointer, bool theIncludeDecorations); void showPrintDialog(); void openScreenshotsFolder(); void showPreferencesDialog(); void showImageSharedFeedback(bool error, const QString &message); void sendToClipboard(); void init(); void setDefaultSaveAction(); void save(); void saveAs(); int windowWidth(const QPixmap &pixmap) const; public Q_SLOTS: void setScreenshotAndShow(const QPixmap &pixmap); void setScreenshotWindowTitle(const QUrl &location); Q_SIGNALS: - void newScreenshotRequest(ImageGrabber::GrabMode mode, int timeout, bool includePointer, bool includeDecorations); + void newScreenshotRequest(Spectacle::CaptureMode theCaptureMode, int theTimeout, bool theIncludePointer, bool theIncludeDecorations); void dragAndDropRequest(); protected: void moveEvent(QMoveEvent *event) override; private: KSWidget *mKSWidget; QFrame *mDivider; QDialogButtonBox *mDialogButtonBox; QToolButton *mConfigureButton; QPushButton *mToolsButton; QPushButton *mSendToButton; QToolButton *mClipboardButton; QToolButton *mSaveButton; QMenu *mSaveMenu; QAction *mSaveAsAction; QAction *mSaveAction; KMessageWidget *mMessageWidget; QMenu *mToolsMenu; QMenu *mScreenRecorderToolsMenu; - QScopedPointer mScreenrecorderToolsMenuFactory; + std::unique_ptr mScreenrecorderToolsMenuFactory; ExportMenu *mExportMenu; - bool mOnClickAvailable; + Platform::ShutterModes mShutterModes; }; - -#endif // KSMAINWINDOW_H diff --git a/src/Gui/KSWidget.cpp b/src/Gui/KSWidget.cpp index 6cd6fbb..dfff54d 100644 --- a/src/Gui/KSWidget.cpp +++ b/src/Gui/KSWidget.cpp @@ -1,252 +1,259 @@ /* * 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 "KSWidget.h" #include "spectacle_gui_debug.h" #include "KSImageWidget.h" #include "SmartSpinBox.h" #include "SpectacleConfig.h" -#include - +#include #include #include #include #include #include #include #include #include +#include -KSWidget::KSWidget(const QVector& supportedModes, QWidget *parent) : +KSWidget::KSWidget(const Platform::GrabModes &theGrabModes, QWidget *parent) : QWidget(parent) { // get a handle to the configuration manager - - SpectacleConfig *configManager = SpectacleConfig::instance(); + SpectacleConfig *lConfigMgr = SpectacleConfig::instance(); // we'll init the widget that holds the image first - mImageWidget = new KSImageWidget(this); mImageWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); connect(mImageWidget, &KSImageWidget::dragInitiated, this, &KSWidget::dragInitiated); // the capture mode options first - mCaptureModeLabel = new QLabel(i18n("Capture Mode"), this); - mCaptureArea = new QComboBox(this); - QString fullScreenLabel = QApplication::screens().count() == 1 + QString lFullScreenLabel = QApplication::screens().count() == 1 ? i18n("Full Screen") : i18n("Full Screen (All Monitors)"); - if (supportedModes.contains(ImageGrabber::FullScreen)) - mCaptureArea->insertItem(1, fullScreenLabel, ImageGrabber::FullScreen); - if (supportedModes.contains(ImageGrabber::CurrentScreen)) - mCaptureArea->insertItem(2, i18n("Current Screen"), ImageGrabber::CurrentScreen); - if (supportedModes.contains(ImageGrabber::ActiveWindow)) - mCaptureArea->insertItem(3, i18n("Active Window"), ImageGrabber::ActiveWindow); - if (supportedModes.contains(ImageGrabber::WindowUnderCursor)) - mCaptureArea->insertItem(4, i18n("Window Under Cursor"), ImageGrabber::WindowUnderCursor); - if (supportedModes.contains(ImageGrabber::RectangularRegion)) - mCaptureArea->insertItem(5, i18n("Rectangular Region"), ImageGrabber::RectangularRegion); + if (theGrabModes.testFlag(Platform::GrabMode::AllScreens)) + mCaptureArea->insertItem(1, lFullScreenLabel, Spectacle::CaptureMode::AllScreens); + if (theGrabModes.testFlag(Platform::GrabMode::CurrentScreen)) + mCaptureArea->insertItem(2, i18n("Current Screen"), Spectacle::CaptureMode::CurrentScreen); + if (theGrabModes.testFlag(Platform::GrabMode::ActiveWindow)) + mCaptureArea->insertItem(3, i18n("Active Window"), Spectacle::CaptureMode::ActiveWindow); + if (theGrabModes.testFlag(Platform::GrabMode::WindowUnderCursor)) + mCaptureArea->insertItem(4, i18n("Window Under Cursor"), Spectacle::CaptureMode::WindowUnderCursor); + if (theGrabModes.testFlag(Platform::GrabMode::TransientWithParent)) { + mTransientWithParentAvailable = true; + } + mCaptureArea->insertItem(5, i18n("Rectangular Region"), Spectacle::CaptureMode::RectangularRegion); mCaptureArea->setMinimumWidth(240); connect(mCaptureArea, static_cast(&QComboBox::currentIndexChanged), this, &KSWidget::captureModeChanged); mDelayMsec = new SmartSpinBox(this); mDelayMsec->setDecimals(1); mDelayMsec->setSingleStep(1.0); mDelayMsec->setMinimum(0.0); mDelayMsec->setMaximum(999.9); mDelayMsec->setSpecialValueText(i18n("No Delay")); mDelayMsec->setMinimumWidth(160); connect(mDelayMsec, static_cast(&SmartSpinBox::valueChanged), - configManager, &SpectacleConfig::setCaptureDelay); + lConfigMgr, &SpectacleConfig::setCaptureDelay); mCaptureOnClick = new QCheckBox(i18n("On Click"), this); mCaptureOnClick->setToolTip(i18n("Wait for a mouse click before capturing the screenshot image")); connect(mCaptureOnClick, &QCheckBox::stateChanged, this, &KSWidget::onClickStateChanged); - connect(mCaptureOnClick, &QCheckBox::clicked, configManager, &SpectacleConfig::setOnClickChecked); + connect(mCaptureOnClick, &QCheckBox::clicked, lConfigMgr, &SpectacleConfig::setOnClickChecked); mDelayLayout = new QHBoxLayout; mDelayLayout->addWidget(mDelayMsec); mDelayLayout->addWidget(mCaptureOnClick); mCaptureModeForm = new QFormLayout; mCaptureModeForm->addRow(i18n("Area:"), mCaptureArea); mCaptureModeForm->addRow(i18n("Delay:"), mDelayLayout); mCaptureModeForm->setContentsMargins(24, 0, 0, 0); // options (mouse pointer, window decorations, quit after saving or copying) - mContentOptionsLabel = new QLabel(this); mContentOptionsLabel->setText(i18n("Options")); mMousePointer = new QCheckBox(i18n("Include mouse pointer"), this); mMousePointer->setToolTip(i18n("Show the mouse cursor in the screenshot image")); - connect(mMousePointer, &QCheckBox::clicked, configManager, &SpectacleConfig::setIncludePointerChecked); + connect(mMousePointer, &QCheckBox::clicked, lConfigMgr, &SpectacleConfig::setIncludePointerChecked); mWindowDecorations = new QCheckBox(i18n("Include window titlebar and borders"), this); mWindowDecorations->setToolTip(i18n("Show the window title bar, the minimize/maximize/close buttons, and the window border")); mWindowDecorations->setEnabled(false); - connect(mWindowDecorations, &QCheckBox::clicked, configManager, &SpectacleConfig::setIncludeDecorationsChecked); + connect(mWindowDecorations, &QCheckBox::clicked, lConfigMgr, &SpectacleConfig::setIncludeDecorationsChecked); mCaptureTransientOnly = new QCheckBox(i18n("Capture the current pop-up only"), this); mCaptureTransientOnly->setToolTip(i18n("Capture only the current pop-up window (like a menu, tooltip etc).\n" "If disabled, the pop-up is captured along with the parent window")); mCaptureTransientOnly->setEnabled(false); - connect(mCaptureTransientOnly, &QCheckBox::clicked, configManager, &SpectacleConfig::setCaptureTransientWindowOnlyChecked); + connect(mCaptureTransientOnly, &QCheckBox::clicked, lConfigMgr, &SpectacleConfig::setCaptureTransientWindowOnlyChecked); mQuitAfterSaveOrCopy = new QCheckBox(i18n("Quit after Save or Copy"), this); mQuitAfterSaveOrCopy->setToolTip(i18n("Quit Spectacle after saving or copying the image")); - connect(mQuitAfterSaveOrCopy, &QCheckBox::clicked, configManager, &SpectacleConfig::setQuitAfterSaveOrCopyChecked); + connect(mQuitAfterSaveOrCopy, &QCheckBox::clicked, lConfigMgr, &SpectacleConfig::setQuitAfterSaveOrCopyChecked); mContentOptionsForm = new QVBoxLayout; mContentOptionsForm->addWidget(mMousePointer); mContentOptionsForm->addWidget(mWindowDecorations); mContentOptionsForm->addWidget(mCaptureTransientOnly); mContentOptionsForm->addWidget(mQuitAfterSaveOrCopy); mContentOptionsForm->setContentsMargins(24, 0, 0, 0); // the take a new screenshot button - mTakeScreenshotButton = new QPushButton(i18n("Take a New Screenshot"), this); mTakeScreenshotButton->setIcon(QIcon::fromTheme(QStringLiteral("spectacle"))); mTakeScreenshotButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); mTakeScreenshotButton->setFocus(); connect(mTakeScreenshotButton, &QPushButton::clicked, this, &KSWidget::newScreenshotClicked); QShortcut *takeScreenshotShortcut = new QShortcut(QKeySequence(QKeySequence::New), mTakeScreenshotButton); connect(takeScreenshotShortcut, &QShortcut::activated, [this]() { mTakeScreenshotButton->animateClick(100); }); // finally, finish up the layouts - mRightLayout = new QVBoxLayout; mRightLayout->addStretch(1); mRightLayout->addWidget(mCaptureModeLabel); mRightLayout->addLayout(mCaptureModeForm); mRightLayout->addStretch(1); mRightLayout->addWidget(mContentOptionsLabel); mRightLayout->addLayout(mContentOptionsForm); mRightLayout->addStretch(10); mRightLayout->addWidget(mTakeScreenshotButton, 1, Qt::AlignHCenter); mRightLayout->setContentsMargins(10, 0, 0, 10); mMainLayout = new QGridLayout(this); mMainLayout->addWidget(mImageWidget, 0, 0, 1, 1); mMainLayout->addLayout(mRightLayout, 0, 1, 1, 1); mMainLayout->setColumnMinimumWidth(0, 320); mMainLayout->setColumnMinimumWidth(1, 320); // and read in the saved checkbox states and capture mode indices - - mMousePointer->setChecked (configManager->includePointerChecked()); - mWindowDecorations->setChecked (configManager->includeDecorationsChecked()); - mCaptureOnClick->setChecked (configManager->onClickChecked()); - mCaptureTransientOnly->setChecked (configManager->captureTransientWindowOnlyChecked()); - mQuitAfterSaveOrCopy->setChecked (configManager->quitAfterSaveOrCopyChecked()); - if (configManager->captureMode()>=0) - mCaptureArea->setCurrentIndex (configManager->captureMode()); - mDelayMsec->setValue (configManager->captureDelay()); - - // done + mMousePointer->setChecked (lConfigMgr->includePointerChecked()); + mWindowDecorations->setChecked (lConfigMgr->includeDecorationsChecked()); + mCaptureOnClick->setChecked (lConfigMgr->onClickChecked()); + mCaptureTransientOnly->setChecked (lConfigMgr->captureTransientWindowOnlyChecked()); + mQuitAfterSaveOrCopy->setChecked (lConfigMgr->quitAfterSaveOrCopyChecked()); + if (lConfigMgr->captureMode() >= 0) { + mCaptureArea->setCurrentIndex (lConfigMgr->captureMode()); + } + mDelayMsec->setValue (lConfigMgr->captureDelay()); } int KSWidget::imagePaddingWidth() const { - int rightLayoutLeft = 0; - int rightLayoutRight = 0; - int mainLayoutRight = 0; + int lRightLayoutLeft = 0; + int lRightLayoutRight = 0; + int lMainLayoutRight = 0; - mRightLayout->getContentsMargins(&rightLayoutLeft, nullptr, &rightLayoutRight, nullptr); - mMainLayout->getContentsMargins(nullptr, nullptr, &mainLayoutRight, nullptr); + mRightLayout->getContentsMargins(&lRightLayoutLeft, nullptr, &lRightLayoutRight, nullptr); + mMainLayout->getContentsMargins(nullptr, nullptr, &lMainLayoutRight, nullptr); - int paddingWidth = (rightLayoutLeft + rightLayoutRight + mainLayoutRight); - paddingWidth += mRightLayout->contentsRect().width(); - paddingWidth += 2 * SpectacleImage::SHADOW_RADIUS; // image drop shadow + int lPaddingWidth = (lRightLayoutLeft + lRightLayoutRight + lMainLayoutRight); + lPaddingWidth += mRightLayout->contentsRect().width(); + lPaddingWidth += 2 * SpectacleImage::SHADOW_RADIUS; // image drop shadow - return paddingWidth; + return lPaddingWidth; } // public slots -void KSWidget::setScreenshotPixmap(const QPixmap &pixmap) +void KSWidget::setScreenshotPixmap(const QPixmap &thePixmap) +{ + mImageWidget->setScreenshot(thePixmap); +} + +void KSWidget::lockOnClickEnabled() { - mImageWidget->setScreenshot(pixmap); + mCaptureOnClick->setCheckState(Qt::Checked); + mCaptureOnClick->setEnabled(false); + mDelayMsec->setEnabled(false); } -void KSWidget::disableOnClick() +void KSWidget::lockOnClickDisabled() { + mCaptureOnClick->setCheckState(Qt::Unchecked); mCaptureOnClick->setEnabled(false); mDelayMsec->setEnabled(true); } // private slots void KSWidget::newScreenshotClicked() { - int delay = mCaptureOnClick->isChecked() ? -1 : (mDelayMsec->value() * 1000); - ImageGrabber::GrabMode mode = static_cast(mCaptureArea->currentData().toInt()); - if (mode == ImageGrabber::WindowUnderCursor && !(mCaptureTransientOnly->isChecked())) { - mode = ImageGrabber::TransientWithParent; + int lDelay = mCaptureOnClick->isChecked() ? -1 : (mDelayMsec->value() * 1000); + auto lMode = static_cast(mCaptureArea->currentData().toInt()); + if (mTransientWithParentAvailable && + lMode == Spectacle::CaptureMode::WindowUnderCursor && + !(mCaptureTransientOnly->isChecked())) { + lMode = Spectacle::CaptureMode::TransientWithParent; } - - emit newScreenshotRequest(mode, delay, mMousePointer->isChecked(), mWindowDecorations->isChecked()); + emit newScreenshotRequest(lMode, lDelay, mMousePointer->isChecked(), mWindowDecorations->isChecked()); } -void KSWidget::onClickStateChanged(int state) +void KSWidget::onClickStateChanged(int theState) { - if (state == Qt::Checked) { + if (theState == Qt::Checked) { mDelayMsec->setEnabled(false); - } else if (state == Qt::Unchecked) { + } else if (theState == Qt::Unchecked) { mDelayMsec->setEnabled(true); } } -void KSWidget::captureModeChanged(int index) +void KSWidget::captureModeChanged(int theIndex) { - SpectacleConfig::instance()->setCaptureMode(index); + SpectacleConfig::instance()->setCaptureMode(theIndex); + - ImageGrabber::GrabMode mode = static_cast(mCaptureArea->itemData(index).toInt()); - switch (mode) { - case ImageGrabber::WindowUnderCursor: + Spectacle::CaptureMode lCaptureMode = static_cast(mCaptureArea->itemData(theIndex).toInt()); + switch(lCaptureMode) { + case Spectacle::CaptureMode::WindowUnderCursor: mWindowDecorations->setEnabled(true); - mCaptureTransientOnly->setEnabled(true); + if (mTransientWithParentAvailable) { + mCaptureTransientOnly->setEnabled(true); + } else { + mCaptureTransientOnly->setEnabled(false); + } break; - case ImageGrabber::ActiveWindow: + case Spectacle::CaptureMode::ActiveWindow: mWindowDecorations->setEnabled(true); mCaptureTransientOnly->setEnabled(false); break; - case ImageGrabber::FullScreen: - case ImageGrabber::CurrentScreen: - case ImageGrabber::RectangularRegion: + case Spectacle::CaptureMode::AllScreens: + case Spectacle::CaptureMode::CurrentScreen: + case Spectacle::CaptureMode::RectangularRegion: mWindowDecorations->setEnabled(false); mCaptureTransientOnly->setEnabled(false); break; - case ImageGrabber::TransientWithParent: - case ImageGrabber::InvalidChoice: + case Spectacle::CaptureMode::TransientWithParent: + case Spectacle::CaptureMode::InvalidChoice: default: qCWarning(SPECTACLE_GUI_LOG) << "Skipping invalid or unreachable enum value"; break; } } diff --git a/src/Gui/KSWidget.h b/src/Gui/KSWidget.h index a898da2..e6f1b90 100644 --- a/src/Gui/KSWidget.h +++ b/src/Gui/KSWidget.h @@ -1,86 +1,89 @@ -/* - * Copyright (C) 2015 Boudhayan Gupta +/* This file is part of Spectacle, the KDE screenshot utility + * 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 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. + * 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. + * 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. + * + * SPDX-License-Identifier: LGPL-2.0-or-later */ -#ifndef KSWIDGET_H -#define KSWIDGET_H +#pragma once #include #include -#include "PlatformBackends/ImageGrabber.h" +#include "SpectacleCommon.h" +#include "Platforms/Platform.h" class QGridLayout; class QHBoxLayout; class QVBoxLayout; class QFormLayout; class QComboBox; class QCheckBox; class QLabel; class QPushButton; class KSImageWidget; class SmartSpinBox; class KSWidget : public QWidget { Q_OBJECT public: - explicit KSWidget(const QVector& supportedModes, QWidget *parent = nullptr); + explicit KSWidget(const Platform::GrabModes &theGrabModes, QWidget *parent = nullptr); + virtual ~KSWidget() = default; int imagePaddingWidth() const; Q_SIGNALS: void dragInitiated(); - void newScreenshotRequest(ImageGrabber::GrabMode mode, int captureDelay, bool capturePointer, bool captureDecorations); + void newScreenshotRequest(Spectacle::CaptureMode theCaptureMode, int theCaptureDelat, bool theIncludePointer, bool theIncludeDecorations); public Q_SLOTS: - void setScreenshotPixmap(const QPixmap &pixmap); - void disableOnClick(); + void setScreenshotPixmap(const QPixmap &thePixmap); + void lockOnClickDisabled(); + void lockOnClickEnabled(); private Q_SLOTS: void newScreenshotClicked(); - void onClickStateChanged(int state); - void captureModeChanged(int index); + void onClickStateChanged(int theState); + void captureModeChanged(int theIndex); private: - QGridLayout *mMainLayout; - QHBoxLayout *mDelayLayout; - QVBoxLayout *mRightLayout; - QFormLayout *mCaptureModeForm; - QVBoxLayout *mContentOptionsForm; - KSImageWidget *mImageWidget; - QPushButton *mTakeScreenshotButton; - QComboBox *mCaptureArea; - SmartSpinBox *mDelayMsec; - QCheckBox *mCaptureOnClick; - QCheckBox *mMousePointer; - QCheckBox *mWindowDecorations; - QCheckBox *mCaptureTransientOnly; - QCheckBox *mQuitAfterSaveOrCopy; - QLabel *mCaptureModeLabel; - QLabel *mContentOptionsLabel; + QGridLayout *mMainLayout { nullptr }; + QHBoxLayout *mDelayLayout { nullptr }; + QVBoxLayout *mRightLayout { nullptr }; + QFormLayout *mCaptureModeForm { nullptr }; + QVBoxLayout *mContentOptionsForm { nullptr }; + KSImageWidget *mImageWidget { nullptr }; + QPushButton *mTakeScreenshotButton { nullptr }; + QComboBox *mCaptureArea { nullptr }; + SmartSpinBox *mDelayMsec { nullptr }; + QCheckBox *mCaptureOnClick { nullptr }; + QCheckBox *mMousePointer { nullptr }; + QCheckBox *mWindowDecorations { nullptr }; + QCheckBox *mCaptureTransientOnly { nullptr }; + QCheckBox *mQuitAfterSaveOrCopy { nullptr }; + QLabel *mCaptureModeLabel { nullptr }; + QLabel *mContentOptionsLabel { nullptr }; + bool mTransientWithParentAvailable { false }; }; - -#endif // KSWIDGET_H diff --git a/src/Gui/SettingsDialog/SaveOptionsPage.cpp b/src/Gui/SettingsDialog/SaveOptionsPage.cpp index 930c3ee..f014f5c 100644 --- a/src/Gui/SettingsDialog/SaveOptionsPage.cpp +++ b/src/Gui/SettingsDialog/SaveOptionsPage.cpp @@ -1,221 +1,223 @@ /* * 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 "SaveOptionsPage.h" +#include "SpectacleCommon.h" #include "SpectacleConfig.h" #include "ExportManager.h" #include #include #include #include #include #include #include #include #include #include SaveOptionsPage::SaveOptionsPage(QWidget *parent) : SettingsPage(parent) { QFormLayout *mainLayout = new QFormLayout; setLayout(mainLayout); // Save location mUrlRequester = new KUrlRequester; mUrlRequester->setMode(KFile::Directory); connect(mUrlRequester, &KUrlRequester::textChanged, this, &SaveOptionsPage::markDirty); mainLayout->addRow(i18n("Save Location:"), mUrlRequester); // copy file location to clipboard after saving mCopyPathToClipboard = new QCheckBox(i18n("Copy file location to clipboard after saving"), this); connect(mCopyPathToClipboard, &QCheckBox::toggled, this, &SaveOptionsPage::markDirty); mainLayout->addRow(QString(), mCopyPathToClipboard); mainLayout->addItem(new QSpacerItem(0, 18, QSizePolicy::Fixed, QSizePolicy::Fixed)); // Compression quality slider and current value display QHBoxLayout *sliderHorizLayout = new QHBoxLayout(); QVBoxLayout *sliderVertLayout = new QVBoxLayout(); // Current value QLabel *qualityValue = new QLabel(); qualityValue->setNum(SpectacleConfig::instance()->compressionQuality()); qualityValue->setMinimumWidth(qualityValue->fontInfo().pointSize()*3); // Slider mQualitySlider = new QSlider(Qt::Horizontal); mQualitySlider->setRange(0, 100); mQualitySlider->setTickInterval(5); mQualitySlider->setSliderPosition(SpectacleConfig::instance()->compressionQuality()); mQualitySlider->setTickPosition(QSlider::TicksBelow); mQualitySlider->setTracking(true); connect(mQualitySlider, &QSlider::valueChanged, [=](int value) { qualityValue->setNum(value); markDirty(); }); sliderHorizLayout->addWidget(mQualitySlider); sliderHorizLayout->addWidget(qualityValue); sliderVertLayout->addLayout(sliderHorizLayout); QLabel *qualitySliderDescription = new QLabel(); qualitySliderDescription->setText(i18n("Choose the image quality when saving with lossy image formats like JPEG")); sliderVertLayout->addWidget(qualitySliderDescription); mainLayout->addRow(i18n("Compression Quality:"), sliderVertLayout); mainLayout->addItem(new QSpacerItem(0, 18, QSizePolicy::Fixed, QSizePolicy::Fixed)); // filename chooser text field QHBoxLayout *saveFieldLayout = new QHBoxLayout; mSaveNameFormat = new QLineEdit; connect(mSaveNameFormat, &QLineEdit::textEdited, this, &SaveOptionsPage::markDirty); connect(mSaveNameFormat, &QLineEdit::textEdited, [&](const QString &newText) { QString fmt; Q_FOREACH(auto item, QImageWriter::supportedImageFormats()) { fmt = QString::fromLocal8Bit(item); if (newText.endsWith(QLatin1Char('.') + fmt, Qt::CaseInsensitive)) { QString txtCopy = newText; txtCopy.chop(fmt.length() + 1); mSaveNameFormat->setText(txtCopy); mSaveImageFormat->setCurrentIndex(mSaveImageFormat->findText(fmt.toUpper())); } } }); connect(mSaveNameFormat, &QLineEdit::textChanged,this, &SaveOptionsPage::updateFilenamePreview); mSaveNameFormat->setPlaceholderText(QStringLiteral("%d")); saveFieldLayout->addWidget(mSaveNameFormat); mSaveImageFormat = new QComboBox; mSaveImageFormat->addItems([&](){ QStringList items; Q_FOREACH(auto fmt, QImageWriter::supportedImageFormats()) { items.append(QString::fromLocal8Bit(fmt).toUpper()); } return items; }()); connect(mSaveImageFormat, &QComboBox::currentTextChanged, this, &SaveOptionsPage::markDirty); connect(mSaveImageFormat, &QComboBox::currentTextChanged, this, &SaveOptionsPage::updateFilenamePreview); saveFieldLayout->addWidget(mSaveImageFormat); mainLayout->addRow(i18n("Filename:"), saveFieldLayout); mPreviewLabel = new QLabel(this); mainLayout->addRow(i18nc("Preview of the user configured filename", "Preview:"), mPreviewLabel); // now the save filename format layout QString helpText = i18n( "You can use the following placeholders in the filename, which will be replaced " "with actual text when the file is saved:
" ); for (auto option = ExportManager::filenamePlaceholders.cbegin(); option != ExportManager::filenamePlaceholders.cend(); ++option) { helpText += QStringLiteral("%1: %2
").arg(option.key(), option.value().toString()); } helpText += QStringLiteral("/: ") + i18n("To save to a sub-folder"); helpText += QStringLiteral("
"); QLabel *fmtHelpText = new QLabel(helpText, this); fmtHelpText->setWordWrap(true); fmtHelpText->setTextFormat(Qt::RichText); fmtHelpText->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); connect(fmtHelpText, &QLabel::linkActivated, [this](const QString& placeholder) { mSaveNameFormat->insert(placeholder); }); mainLayout->addWidget(fmtHelpText); // read in the data resetChanges(); } void SaveOptionsPage::markDirty() { mChangesMade = true; } void SaveOptionsPage::saveChanges() { // bring up the configuration reader SpectacleConfig *cfgManager = SpectacleConfig::instance(); // save the data cfgManager->setDefaultSaveLocation(mUrlRequester->url().toDisplayString(QUrl::PreferLocalFile)); cfgManager->setAutoSaveFilenameFormat(mSaveNameFormat->text()); cfgManager->setSaveImageFormat(mSaveImageFormat->currentText().toLower()); cfgManager->setCopySaveLocationToClipboard(mCopyPathToClipboard->checkState() == Qt::Checked); cfgManager->setCompressionQuality(mQualitySlider->value()); // done mChangesMade = false; } void SaveOptionsPage::resetChanges() { // bring up the configuration reader SpectacleConfig *cfgManager = SpectacleConfig::instance(); // read in the data mSaveNameFormat->setText(cfgManager->autoSaveFilenameFormat()); mUrlRequester->setUrl(QUrl::fromUserInput(cfgManager->defaultSaveLocation())); mCopyPathToClipboard->setChecked(cfgManager->copySaveLocationToClipboard()); mQualitySlider->setSliderPosition(cfgManager->compressionQuality()); // read in the save image format and calculate its index { int index = mSaveImageFormat->findText(cfgManager->saveImageFormat().toUpper()); if (index >= 0) { mSaveImageFormat->setCurrentIndex(index); } } // done mChangesMade = false; } void SaveOptionsPage::updateFilenamePreview() { - ExportManager *exportManager = ExportManager::instance(); - exportManager->setWindowTitle(QStringLiteral("Spectacle")); - using GrabMode = ImageGrabber::GrabMode; - GrabMode oldMode = exportManager->grabMode(); - /* If the grabMode is not one of those below we need to change it to have the placeholder - * replaced by the window title */ - bool changeAndRestoreGrabMode = !(oldMode == GrabMode::ActiveWindow - || oldMode == GrabMode::TransientWithParent || oldMode == GrabMode::WindowUnderCursor); - if (changeAndRestoreGrabMode) { - exportManager->setGrabMode(GrabMode::ActiveWindow); + auto lExportManager = ExportManager::instance(); + lExportManager->setWindowTitle(QStringLiteral("Spectacle")); + Spectacle::CaptureMode lOldMode = lExportManager->captureMode(); + + // If the grabMode is not one of those below we need to change it to have the placeholder + // replaced by the window title + bool lSwitchGrabMode = !(lOldMode == Spectacle::CaptureMode::ActiveWindow || + lOldMode == Spectacle::CaptureMode::TransientWithParent || + lOldMode == Spectacle::CaptureMode::WindowUnderCursor); + if (lSwitchGrabMode) { + lExportManager->setCaptureMode(Spectacle::CaptureMode::ActiveWindow); } - const QString filename = exportManager->formatFilename(mSaveNameFormat->text()); - mPreviewLabel->setText(xi18nc("@info", "%1.%2", filename, mSaveImageFormat->currentText().toLower())); - if (changeAndRestoreGrabMode) { - exportManager->setGrabMode(oldMode); + const QString lFileName = lExportManager->formatFilename(mSaveNameFormat->text()); + mPreviewLabel->setText(xi18nc("@info", "%1.%2", lFileName, mSaveImageFormat->currentText().toLower())); + if (lSwitchGrabMode) { + lExportManager->setCaptureMode(lOldMode); } } diff --git a/src/Main.cpp b/src/Main.cpp index 8e08b4e..9cfa871 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -1,165 +1,163 @@ /* * 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 "Config.h" +#include "SpectacleCommon.h" #include "SpectacleCore.h" #include "SpectacleDBusAdapter.h" +#include +#include +#include + #include #include #include -#include -#include - int main(int argc, char **argv) { // set up the application - QApplication app(argc, argv); + QApplication lApp(argc, argv); - app.setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true); - app.setAttribute(Qt::AA_UseHighDpiPixmaps, true); + lApp.setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true); + lApp.setAttribute(Qt::AA_UseHighDpiPixmaps, true); KLocalizedString::setApplicationDomain("spectacle"); KAboutData aboutData(QStringLiteral("spectacle"), i18n("Spectacle"), QStringLiteral(SPECTACLE_VERSION) + QStringLiteral(" - ") + QStringLiteral(SPECTACLE_CODENAME), i18n("KDE Screenshot Utility"), KAboutLicense::GPL_V2, i18n("(C) 2015 Boudhayan Gupta")); aboutData.addAuthor(QStringLiteral("Boudhayan Gupta"), QString(), QStringLiteral("bgupta@kde.org")); aboutData.setTranslator(i18nc("NAME OF TRANSLATORS", "Your names"), i18nc("EMAIL OF TRANSLATORS", "Your emails")); KAboutData::setApplicationData(aboutData); - app.setWindowIcon(QIcon::fromTheme(QStringLiteral("spectacle"))); + lApp.setWindowIcon(QIcon::fromTheme(QStringLiteral("spectacle"))); // set up the command line options parser - QCommandLineParser parser; - aboutData.setupCommandLine(&parser); + QCommandLineParser lCmdLineParser; + aboutData.setupCommandLine(&lCmdLineParser); - parser.addOptions({ + lCmdLineParser.addOptions({ {{QStringLiteral("f"), QStringLiteral("fullscreen")}, i18n("Capture the entire desktop (default)")}, {{QStringLiteral("m"), QStringLiteral("current")}, i18n("Capture the current monitor")}, {{QStringLiteral("a"), QStringLiteral("activewindow")}, i18n("Capture the active window")}, {{QStringLiteral("u"), QStringLiteral("windowundercursor")}, i18n("Capture the window currently under the cursor, including parents of pop-up menus")}, {{QStringLiteral("t"), QStringLiteral("transientonly")}, i18n("Capture the window currently under the cursor, excluding parents of pop-up menus")}, {{QStringLiteral("r"), QStringLiteral("region")}, i18n("Capture a rectangular region of the screen")}, {{QStringLiteral("g"), QStringLiteral("gui")}, i18n("Start in GUI mode (default)")}, {{QStringLiteral("b"), QStringLiteral("background")}, i18n("Take a screenshot and exit without showing the GUI")}, {{QStringLiteral("s"), QStringLiteral("dbus")}, i18n("Start in DBus-Activation mode")}, {{QStringLiteral("n"), QStringLiteral("nonotify")}, i18n("In background mode, do not pop up a notification when the screenshot is taken")}, {{QStringLiteral("o"), QStringLiteral("output")}, i18n("In background mode, save image to specified file"), QStringLiteral("fileName")}, {{QStringLiteral("d"), QStringLiteral("delay")}, i18n("In background mode, delay before taking the shot (in milliseconds)"), QStringLiteral("delayMsec")}, {{QStringLiteral("c"), QStringLiteral("clipboard")}, i18n("In background mode, copy screenshot to clipboard")}, {{QStringLiteral("w"), QStringLiteral("onclick")}, i18n("Wait for a click before taking screenshot. Invalidates delay")} }); - parser.process(app); - aboutData.processCommandLine(&parser); + lCmdLineParser.process(lApp); + aboutData.processCommandLine(&lCmdLineParser); // extract the capture mode - ImageGrabber::GrabMode grabMode = ImageGrabber::FullScreen; - if (parser.isSet(QStringLiteral("current"))) { - grabMode = ImageGrabber::CurrentScreen; - } else if (parser.isSet(QStringLiteral("activewindow"))) { - grabMode = ImageGrabber::ActiveWindow; - } else if (parser.isSet(QStringLiteral("region"))) { - grabMode = ImageGrabber::RectangularRegion; - } else if (parser.isSet(QStringLiteral("windowundercursor"))) { - grabMode = ImageGrabber::TransientWithParent; - } else if (parser.isSet(QStringLiteral("transientonly"))) { - grabMode = ImageGrabber::WindowUnderCursor; + Spectacle::CaptureMode lCaptureMode = Spectacle::CaptureMode::AllScreens; + if (lCmdLineParser.isSet(QStringLiteral("current"))) { + lCaptureMode = Spectacle::CaptureMode::CurrentScreen; + } else if (lCmdLineParser.isSet(QStringLiteral("activewindow"))) { + lCaptureMode = Spectacle::CaptureMode::ActiveWindow; + } else if (lCmdLineParser.isSet(QStringLiteral("region"))) { + lCaptureMode = Spectacle::CaptureMode::RectangularRegion; + } else if (lCmdLineParser.isSet(QStringLiteral("windowundercursor"))) { + lCaptureMode = Spectacle::CaptureMode::TransientWithParent; + } else if (lCmdLineParser.isSet(QStringLiteral("transientonly"))) { + lCaptureMode = Spectacle::CaptureMode::WindowUnderCursor; } // are we running in background or dbus mode? - SpectacleCore::StartMode startMode = SpectacleCore::GuiMode; - bool notify = true; - bool copyToClipboard = false; - qint64 delayMsec = 0; - QString fileName = QString(); + SpectacleCore::StartMode lStartMode = SpectacleCore::StartMode::Gui; + bool lNotify = true; + bool lCopyToClipboard = false; + qint64 lDelayMsec = 0; + QString lFileName = QString(); - if (parser.isSet(QStringLiteral("background"))) { - startMode = SpectacleCore::BackgroundMode; - } else if (parser.isSet(QStringLiteral("dbus"))) { - startMode = SpectacleCore::DBusMode; + if (lCmdLineParser.isSet(QStringLiteral("background"))) { + lStartMode = SpectacleCore::StartMode::Background; + } else if (lCmdLineParser.isSet(QStringLiteral("dbus"))) { + lStartMode = SpectacleCore::StartMode::DBus; } - switch (startMode) { - case SpectacleCore::BackgroundMode: - if (parser.isSet(QStringLiteral("nonotify"))) { - notify = false; + switch(lStartMode) { + case SpectacleCore::StartMode::Background: + if (lCmdLineParser.isSet(QStringLiteral("nonotify"))) { + lNotify = false; } - if (parser.isSet(QStringLiteral("output"))) { - fileName = parser.value(QStringLiteral("output")); + if (lCmdLineParser.isSet(QStringLiteral("output"))) { + lFileName = lCmdLineParser.value(QStringLiteral("output")); } - if (parser.isSet(QStringLiteral("delay"))) { - bool ok = false; - qint64 delayValue = parser.value(QStringLiteral("delay")).toLongLong(&ok); - if (ok) { - delayMsec = delayValue; + if (lCmdLineParser.isSet(QStringLiteral("delay"))) { + bool lParseOk = false; + qint64 lDelayValue = lCmdLineParser.value(QStringLiteral("delay")).toLongLong(&lParseOk); + if (lParseOk) { + lDelayMsec = lDelayValue; } } - if (parser.isSet(QStringLiteral("onclick"))) { - delayMsec = -1; + if (lCmdLineParser.isSet(QStringLiteral("onclick"))) { + lDelayMsec = -1; } - if (parser.isSet(QStringLiteral("clipboard"))) { - copyToClipboard = true; + if (lCmdLineParser.isSet(QStringLiteral("clipboard"))) { + lCopyToClipboard = true; } - app.setQuitOnLastWindowClosed(false); + lApp.setQuitOnLastWindowClosed(false); break; - - case SpectacleCore::DBusMode: - app.setQuitOnLastWindowClosed(false); + case SpectacleCore::StartMode::DBus: + lApp.setQuitOnLastWindowClosed(false); break; - case SpectacleCore::GuiMode: - default: + case SpectacleCore::StartMode::Gui: break; } // release the kraken - SpectacleCore core(startMode, grabMode, fileName, delayMsec, notify, copyToClipboard); - QObject::connect(&core, &SpectacleCore::allDone, qApp, &QApplication::quit); + SpectacleCore lCore(lStartMode, lCaptureMode, lFileName, lDelayMsec, lNotify, lCopyToClipboard); + QObject::connect(&lCore, &SpectacleCore::allDone, qApp, &QApplication::quit); // create the dbus connections - new KDBusService(KDBusService::Multiple, &core); - - SpectacleDBusAdapter *dbusAdapter = new SpectacleDBusAdapter(&core); - QObject::connect(&core, &SpectacleCore::grabFailed, dbusAdapter, &SpectacleDBusAdapter::ScreenshotFailed); + new KDBusService(KDBusService::Multiple, &lCore); + SpectacleDBusAdapter *lDBusAdapter = new SpectacleDBusAdapter(&lCore); + QObject::connect(&lCore, &SpectacleCore::grabFailed, lDBusAdapter, &SpectacleDBusAdapter::ScreenshotFailed); QObject::connect(ExportManager::instance(), &ExportManager::imageSaved, [&](const QUrl &savedAt) { - emit dbusAdapter->ScreenshotTaken(savedAt.toLocalFile()); + emit lDBusAdapter->ScreenshotTaken(savedAt.toLocalFile()); }); - - QDBusConnection::sessionBus().registerObject(QStringLiteral("/"), &core); + QDBusConnection::sessionBus().registerObject(QStringLiteral("/"), &lCore); QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.Spectacle")); // fire it up - return app.exec(); + return lApp.exec(); } diff --git a/src/PlatformBackends/DummyImageGrabber.cpp b/src/PlatformBackends/DummyImageGrabber.cpp deleted file mode 100644 index 4ccea75..0000000 --- a/src/PlatformBackends/DummyImageGrabber.cpp +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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 "DummyImageGrabber.h" - -DummyImageGrabber::DummyImageGrabber(QObject *parent): - ImageGrabber(parent) -{} - -DummyImageGrabber::~DummyImageGrabber() -{} - -bool DummyImageGrabber::onClickGrabSupported() const -{ - return false; -} - -QPixmap DummyImageGrabber::blendCursorImage(const QPixmap &pixmap, int x, int y, int width, int height) -{ - Q_UNUSED(pixmap); - Q_UNUSED(x); - Q_UNUSED(y); - Q_UNUSED(width); - Q_UNUSED(height); - return QPixmap(); -} - -void DummyImageGrabber::grabFullScreen() -{ - emit pixmapChanged(QPixmap()); -} - -void DummyImageGrabber::grabCurrentScreen() -{ - emit pixmapChanged(QPixmap()); -} - -void DummyImageGrabber::grabActiveWindow() -{ - emit pixmapChanged(QPixmap()); -} - -void DummyImageGrabber::grabRectangularRegion() -{ - emit pixmapChanged(QPixmap()); -} - -void DummyImageGrabber::grabWindowUnderCursor() -{ - emit pixmapChanged(QPixmap()); -} - -void DummyImageGrabber::grabTransientWithParent() -{ - emit pixmapChanged(QPixmap()); -} diff --git a/src/PlatformBackends/DummyImageGrabber.h b/src/PlatformBackends/DummyImageGrabber.h deleted file mode 100644 index 3f48853..0000000 --- a/src/PlatformBackends/DummyImageGrabber.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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 DUMMYIMAGEGRABBER_H -#define DUMMYIMAGEGRABBER_H - -#include -#include - -#include "ImageGrabber.h" - -class DummyImageGrabber : public ImageGrabber -{ - Q_OBJECT - - public: - - explicit DummyImageGrabber(QObject *parent = nullptr); - ~DummyImageGrabber() override; - - QVector supportedModes() const override { return {FullScreen, CurrentScreen, ActiveWindow, WindowUnderCursor, TransientWithParent, RectangularRegion}; } - bool onClickGrabSupported() const override; - - protected: - - QPixmap blendCursorImage(const QPixmap &pixmap, int x, int y, int width, int height) override; - void grabFullScreen() override; - void grabCurrentScreen() override; - void grabActiveWindow() override; - void grabRectangularRegion() override; - void grabWindowUnderCursor() override; - void grabTransientWithParent() override; -}; - -#endif // DUMMYIMAGEGRABBER_H diff --git a/src/PlatformBackends/ImageGrabber.cpp b/src/PlatformBackends/ImageGrabber.cpp deleted file mode 100644 index 3963439..0000000 --- a/src/PlatformBackends/ImageGrabber.cpp +++ /dev/null @@ -1,112 +0,0 @@ -/* - * 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 "ImageGrabber.h" - -ImageGrabber::ImageGrabber(QObject *parent) : - QObject(parent), - mCapturePointer(false), - mCaptureDecorations(true), - mGrabMode(InvalidChoice), - mPixmap(QPixmap()) -{ -} - -ImageGrabber::~ImageGrabber() -{ -} - -// - -bool ImageGrabber::onClickGrabSupported() const -{ - return false; -} - -// Q_PROPERTY Stuff - -QPixmap ImageGrabber::pixmap() const -{ - return mPixmap; -} - -bool ImageGrabber::capturePointer() const -{ - return mCapturePointer; -} - -bool ImageGrabber::captureDecorations() const -{ - return mCaptureDecorations; -} - -ImageGrabber::GrabMode ImageGrabber::grabMode() const -{ - return mGrabMode; -} - -void ImageGrabber::setCapturePointer(const bool newCapturePointer) -{ - mCapturePointer = newCapturePointer; -} - -void ImageGrabber::setCaptureDecorations(const bool newCaptureDecorations) -{ - mCaptureDecorations = newCaptureDecorations; -} - -void ImageGrabber::setGrabMode(const GrabMode newGrabMode) -{ - mGrabMode = newGrabMode; -} - -// Slots - -void ImageGrabber::doOnClickGrab() -{ - doImageGrab(); -} - -void ImageGrabber::doImageGrab() -{ - switch(mGrabMode) { - case FullScreen: - grabFullScreen(); - break; - case CurrentScreen: - grabCurrentScreen(); - break; - case ActiveWindow: - grabActiveWindow(); - break; - case WindowUnderCursor: - grabWindowUnderCursor(); - break; - case TransientWithParent: - grabTransientWithParent(); - break; - case RectangularRegion: - grabRectangularRegion(); - break; - case InvalidChoice: - default: - emit imageGrabFailed(); - return; - } -} diff --git a/src/PlatformBackends/ImageGrabber.h b/src/PlatformBackends/ImageGrabber.h deleted file mode 100644 index ec91277..0000000 --- a/src/PlatformBackends/ImageGrabber.h +++ /dev/null @@ -1,100 +0,0 @@ -/* - * 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 IMAGEGRABBER_H -#define IMAGEGRABBER_H - -#include -#include -#include -#include -#include -#include - -#include "QuickEditor/QuickEditor.h" - -class ImageGrabber : public QObject -{ - Q_OBJECT - - Q_PROPERTY(QPixmap pixmap READ pixmap NOTIFY pixmapChanged) - Q_PROPERTY(bool capturePointer READ capturePointer WRITE setCapturePointer NOTIFY capturePointerChanged) - Q_PROPERTY(bool captureDecorations READ captureDecorations WRITE setCaptureDecorations NOTIFY captureDecorationsChanged) - Q_PROPERTY(GrabMode grabMode READ grabMode WRITE setGrabMode NOTIFY grabModeChanged) - - public: - - enum GrabMode { - InvalidChoice = -1, - FullScreen = 0, - CurrentScreen = 1, - ActiveWindow = 2, - WindowUnderCursor = 3, - TransientWithParent = 4, - RectangularRegion = 5 - }; - - Q_ENUM(GrabMode) - - explicit ImageGrabber(QObject *parent = nullptr); - ~ImageGrabber(); - - QPixmap pixmap() const; - bool capturePointer() const; - bool captureDecorations() const; - GrabMode grabMode() const; - - virtual QVector supportedModes() const = 0; - virtual bool onClickGrabSupported() const; - - void setCapturePointer(const bool newCapturePointer); - void setCaptureDecorations(const bool newCaptureDecorations); - void setGrabMode(const GrabMode newGrabMode); - - Q_SIGNALS: - - void pixmapChanged(const QPixmap &pixmap); - void windowTitleChanged(const QString &windowTitle); - void imageGrabFailed(); - void capturePointerChanged(bool capturePointer); - void captureDecorationsChanged(bool captureDecorations); - void grabModeChanged(GrabMode grabMode); - - public Q_SLOTS: - - virtual void doImageGrab(); - virtual void doOnClickGrab(); - - protected: - - virtual void grabFullScreen() = 0; - virtual void grabCurrentScreen() = 0; - virtual void grabActiveWindow() = 0; - virtual void grabRectangularRegion() = 0; - virtual void grabWindowUnderCursor() = 0; - virtual void grabTransientWithParent() = 0; - virtual QPixmap blendCursorImage(const QPixmap &pixmap, int x, int y, int width, int height) = 0; - - bool mCapturePointer; - bool mCaptureDecorations; - GrabMode mGrabMode; - QPixmap mPixmap; -}; - -#endif // IMAGEGRABBER_H diff --git a/src/PlatformBackends/KWinWaylandImageGrabber.cpp b/src/PlatformBackends/KWinWaylandImageGrabber.cpp deleted file mode 100644 index 5851465..0000000 --- a/src/PlatformBackends/KWinWaylandImageGrabber.cpp +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (C) 2016 Martin Graesslin - * - * 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 "KWinWaylandImageGrabber.h" - -#include -#include -#include -#include -#include -#include -#include - -#include - -static int readData(int fd, QByteArray &data) -{ - // implementation based on QtWayland file qwaylanddataoffer.cpp - char buf[4096]; - int retryCount = 0; - int n; - while (true) { - n = QT_READ(fd, buf, sizeof buf); - // give user 30 sec to click a window, afterwards considered as error - if (n == -1 && (errno == EAGAIN) && ++retryCount < 30000) { - usleep(1000); - } else { - break; - } - } - if (n > 0) { - data.append(buf, n); - n = readData(fd, data); - } - return n; -} - -static QImage readImage(int pipeFd) -{ - QByteArray content; - if (readData(pipeFd, content) != 0) { - close(pipeFd); - return QImage(); - } - close(pipeFd); - QDataStream ds(content); - QImage image; - ds >> image; - return image; -} - -KWinWaylandImageGrabber::KWinWaylandImageGrabber(QObject *parent) : - ImageGrabber(parent) -{ -} - -KWinWaylandImageGrabber::~KWinWaylandImageGrabber() = default; - -bool KWinWaylandImageGrabber::onClickGrabSupported() const -{ - return true; -} - -void KWinWaylandImageGrabber::grabFullScreen() -{ - grab(Mode::FullScreen, mCapturePointer); -} - -void KWinWaylandImageGrabber::grabCurrentScreen() -{ - grab(Mode::CurrentScreen, mCapturePointer); -} - -void KWinWaylandImageGrabber::grabActiveWindow() -{ - // unsupported - emit pixmapChanged(QPixmap()); -} - -void KWinWaylandImageGrabber::grabRectangularRegion() -{ - // unsupported - emit pixmapChanged(QPixmap()); -} - -void KWinWaylandImageGrabber::grabWindowUnderCursor() -{ - int mask = 0; - if (mCaptureDecorations) { - mask = 1; - } - if (mCapturePointer) { - mask |= 1 << 1; - } - grab(Mode::Window, mask); -} - -void KWinWaylandImageGrabber::grabTransientWithParent() -{ - // unsupported, perform grab window under cursor - grabWindowUnderCursor(); -} - -QPixmap KWinWaylandImageGrabber::blendCursorImage(const QPixmap &pixmap, int x, int y, int width, int height) -{ - Q_UNUSED(x) - Q_UNUSED(y) - Q_UNUSED(width) - Q_UNUSED(height) - return pixmap; -} - -void KWinWaylandImageGrabber::startReadImage(int readPipe) -{ - QFutureWatcher *watcher = new QFutureWatcher(this); - QObject::connect(watcher, &QFutureWatcher::finished, this, - [watcher, this] { - watcher->deleteLater(); - const QImage img = watcher->result(); - emit pixmapChanged(QPixmap::fromImage(img)); - } - ); - watcher->setFuture(QtConcurrent::run(readImage, readPipe)); -} - -template -void KWinWaylandImageGrabber::callDBus(Mode mode, int writeFd, T argument) -{ - QDBusInterface interface(QStringLiteral("org.kde.KWin"), QStringLiteral("/Screenshot"), QStringLiteral("org.kde.kwin.Screenshot")); - static const QMap s_hash = { - {Mode::Window, QStringLiteral("interactive")}, - {Mode::CurrentScreen, QStringLiteral("screenshotScreen")}, - {Mode::FullScreen, QStringLiteral("screenshotFullscreen")} - }; - auto it = s_hash.find(mode); - Q_ASSERT(it != s_hash.end()); - interface.asyncCall(it.value(), QVariant::fromValue(QDBusUnixFileDescriptor(writeFd)), argument); -} - -template -void KWinWaylandImageGrabber::grab(Mode mode, T argument) -{ - int pipeFds[2]; - if (pipe2(pipeFds, O_CLOEXEC|O_NONBLOCK) != 0) { - emit imageGrabFailed(); - return; - } - - callDBus(mode, pipeFds[1], argument); - startReadImage(pipeFds[0]); - - close(pipeFds[1]); -} - -QVector KWinWaylandImageGrabber::supportedModes() const -{ - if (QApplication::screens().count() == 1) { - return {FullScreen, WindowUnderCursor, TransientWithParent}; - } - - return {FullScreen, CurrentScreen, WindowUnderCursor, TransientWithParent}; -} diff --git a/src/PlatformBackends/KWinWaylandImageGrabber.h b/src/PlatformBackends/KWinWaylandImageGrabber.h deleted file mode 100644 index e7fa442..0000000 --- a/src/PlatformBackends/KWinWaylandImageGrabber.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2016 Martin Graesslin - * - * 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 KWINWAYLANDIMAGEGRABBER_H -#define KWINWAYLANDIMAGEGRABBER_H - -#include "ImageGrabber.h" - -class KWinWaylandImageGrabber : public ImageGrabber -{ - Q_OBJECT - - public: - - explicit KWinWaylandImageGrabber(QObject * parent = nullptr); - ~KWinWaylandImageGrabber() override; - - QVector supportedModes() const override; - bool onClickGrabSupported() const override; - - protected: - - void grabFullScreen() override; - void grabCurrentScreen() override; - void grabActiveWindow() override; - void grabRectangularRegion() override; - void grabWindowUnderCursor() override; - void grabTransientWithParent() override; - QPixmap blendCursorImage(const QPixmap &pixmap, int x, int y, int width, int height) override; - - private: - - void startReadImage(int readPipe); - enum class Mode { - Window, - CurrentScreen, - FullScreen - }; - template - void callDBus(Mode mode, int writeFd, T argument); - template - void grab(Mode mode, T argument); -}; - -#endif diff --git a/src/PlatformBackends/X11ImageGrabber.cpp b/src/PlatformBackends/X11ImageGrabber.cpp deleted file mode 100644 index 4fbb89f..0000000 --- a/src/PlatformBackends/X11ImageGrabber.cpp +++ /dev/null @@ -1,762 +0,0 @@ -/* - * Copyright (C) 2015 Boudhayan Gupta - * - * Contains code from kxutils.cpp, part of KWindowSystem. Copyright - * notices reproduced below: - * - * Copyright (C) 2008 Lubos Lunak (l.lunak@kde.org) - * Copyright (C) 2013 Martin Gräßlin - * - * 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 "X11ImageGrabber.h" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include - -#include -#include - -X11ImageGrabber::X11ImageGrabber(QObject *parent) : - ImageGrabber(parent) -{ - mNativeEventFilter = new OnClickEventFilter(this); -} - -X11ImageGrabber::~X11ImageGrabber() -{ - delete mNativeEventFilter; -} - -// for onClick grab - -OnClickEventFilter::OnClickEventFilter(X11ImageGrabber *grabber) : - QAbstractNativeEventFilter(), - mImageGrabber(grabber) -{} - -bool OnClickEventFilter::nativeEventFilter(const QByteArray &eventType, void *message, long *result) -{ - Q_UNUSED(result); - - if (eventType == "xcb_generic_event_t") { - xcb_generic_event_t *ev = static_cast(message); - - switch (ev->response_type & ~0x80) { - case XCB_BUTTON_RELEASE: - - // uninstall the eventfilter and release the mouse - - qApp->removeNativeEventFilter(this); - xcb_ungrab_pointer(QX11Info::connection(), XCB_TIME_CURRENT_TIME); - - // decide whether to grab or abort. regrab the mouse - // on mouse-wheel events - - { - xcb_button_release_event_t *ev2 = static_cast(message); - if (ev2->detail == 1) { - QMetaObject::invokeMethod(mImageGrabber, "doImageGrab", Qt::QueuedConnection); - } else if (ev2->detail < 4) { - emit mImageGrabber->imageGrabFailed(); - } else { - QMetaObject::invokeMethod(mImageGrabber, "doOnClickGrab", Qt::QueuedConnection); - } - } - return true; - default: - return false; - } - } - return false; -} - -bool X11ImageGrabber::onClickGrabSupported() const -{ - return true; -} - -void X11ImageGrabber::doOnClickGrab() -{ - // get the cursor image - - xcb_cursor_t xcbCursor = XCB_CURSOR_NONE; - - xcb_cursor_context_t *xcbCursorCtx; - xcb_screen_t *xcbAppScreen = xcb_aux_get_screen(QX11Info::connection(), QX11Info::appScreen()); - - if (xcb_cursor_context_new(QX11Info::connection(), xcbAppScreen, &xcbCursorCtx) >= 0) { - - QVector cursorNames = { - QByteArrayLiteral("cross"), - QByteArrayLiteral("crosshair"), - QByteArrayLiteral("diamond-cross"), - QByteArrayLiteral("cross-reverse") - }; - - Q_FOREACH (const QByteArray &cursorName, cursorNames) { - xcb_cursor_t cursor = xcb_cursor_load_cursor(xcbCursorCtx, cursorName.constData()); - if (cursor != XCB_CURSOR_NONE) { - xcbCursor = cursor; - break; - } - } - } - - // grab the cursor - - xcb_grab_pointer_cookie_t grabPointerCookie = xcb_grab_pointer_unchecked( - QX11Info::connection(), // xcb connection - 0, // deliver events to owner? nope - QX11Info::appRootWindow(), // window to grab pointer for (root) - XCB_EVENT_MASK_BUTTON_RELEASE, // which events do I want - XCB_GRAB_MODE_SYNC, // pointer grab mode - XCB_GRAB_MODE_ASYNC, // keyboard grab mode (why is this even here) - XCB_NONE, // confine pointer to which window (none) - xcbCursor, // cursor to change to for the duration of grab - XCB_TIME_CURRENT_TIME // do this right now - ); - CScopedPointer grabPointerReply(xcb_grab_pointer_reply(QX11Info::connection(), grabPointerCookie, nullptr)); - - // if the grab failed, take the screenshot right away - - if (grabPointerReply->status != XCB_GRAB_STATUS_SUCCESS) { - doImageGrab(); - return; - } - - // fix things if our pointer grab causes a lockup - - xcb_allow_events(QX11Info::connection(), XCB_ALLOW_SYNC_POINTER, XCB_TIME_CURRENT_TIME); - - // and install our event filter - - qApp->installNativeEventFilter(mNativeEventFilter); - - // done. clean stuff up - - xcb_cursor_context_free(xcbCursorCtx); - xcb_free_cursor(QX11Info::connection(), xcbCursor); -} - -// image conversion routine - -QPixmap X11ImageGrabber::convertFromNative(xcb_image_t *xcbImage) -{ - QImage::Format format = QImage::Format_Invalid; - - switch (xcbImage->depth) { - case 1: - format = QImage::Format_MonoLSB; - break; - case 16: - format = QImage::Format_RGB16; - break; - case 24: - format = QImage::Format_RGB32; - break; - case 30: - format = QImage::Format_BGR30; - break; - case 32: - format = QImage::Format_ARGB32_Premultiplied; - break; - default: - return QPixmap(); // we don't know - } - - // The RGB32 format requires data format 0xffRRGGBB, ensure that this fourth byte really is 0xff - if (format == QImage::Format_RGB32) { - quint32 *data = reinterpret_cast(xcbImage->data); - for (int i = 0; i < xcbImage->width * xcbImage->height; i++) { - data[i] |= 0xff000000; - } - } - - QImage image(xcbImage->data, xcbImage->width, xcbImage->height, format); - - if (image.isNull()) { - return QPixmap(); - } - - // work around an abort in QImage::color - - if (image.format() == QImage::Format_MonoLSB) { - image.setColorCount(2); - image.setColor(0, QColor(Qt::white).rgb()); - image.setColor(1, QColor(Qt::black).rgb()); - } - - // Image is ready. Since the backing data from xcbImage could be freed - // before the QPixmap goes away, a deep copy is necessary. - return QPixmap::fromImage(image).copy(); -} - -// utility functions - -// Note: x, y, width and height are measured in device pixels -QPixmap X11ImageGrabber::blendCursorImage(const QPixmap &pixmap, int x, int y, int width, int height) -{ - // If the cursor position lies outside the area, do not bother drawing a cursor. - - QPoint cursorPos = getNativeCursorPosition(); - QRect screenRect(x, y, width, height); - - if (!screenRect.contains(cursorPos)) { - return pixmap; - } - - // now we can get the image and start processing - - xcb_connection_t *xcbConn = QX11Info::connection(); - - xcb_xfixes_get_cursor_image_cookie_t cursorCookie = xcb_xfixes_get_cursor_image_unchecked(xcbConn); - CScopedPointer cursorReply(xcb_xfixes_get_cursor_image_reply(xcbConn, cursorCookie, nullptr)); - if (cursorReply.isNull()) { - return pixmap; - } - - quint32 *pixelData = xcb_xfixes_get_cursor_image_cursor_image(cursorReply.data()); - if (!pixelData) { - return pixmap; - } - - // process the image into a QImage - - QImage cursorImage = QImage((quint8 *)pixelData, cursorReply->width, cursorReply->height, QImage::Format_ARGB32_Premultiplied); - - // a small fix for the cursor position for fancier cursors - - cursorPos -= QPoint(cursorReply->xhot, cursorReply->yhot); - - // now we translate the cursor point to our screen rectangle - - cursorPos -= QPoint(x, y); - - // and do the painting - - QPixmap blendedPixmap = pixmap; - QPainter painter(&blendedPixmap); - painter.drawImage(cursorPos, cursorImage); - - // and done - - return blendedPixmap; -} - -QPixmap X11ImageGrabber::getPixmapFromDrawable(xcb_drawable_t drawableId, const QRect &rect) -{ - xcb_connection_t *xcbConn = QX11Info::connection(); - - // proceed to get an image based on the geometry (in device pixels) - - QScopedPointer xcbImage( - xcb_image_get( - xcbConn, - drawableId, - rect.x(), - rect.y(), - rect.width(), - rect.height(), - ~0, - XCB_IMAGE_FORMAT_Z_PIXMAP - ) - ); - - // too bad, the capture failed. - if (xcbImage.isNull()) { - return QPixmap(); - } - - // now process the image - - QPixmap nativePixmap = convertFromNative(xcbImage.data()); - return nativePixmap; -} - -// finalize the grabbed pixmap where we know the absolute position -QPixmap X11ImageGrabber::postProcessPixmap(const QPixmap &pixmap, QRect rect, bool blendPointer) -{ - if (!(blendPointer)) { - // note: this may be the null pixmap if an error occurred. - return pixmap; - } - - return blendCursorImage(pixmap, rect.x(), rect.y(), rect.width(), rect.height()); -} - -QPixmap X11ImageGrabber::getToplevelPixmap(QRect rect, bool blendPointer) -{ - xcb_window_t rootWindow = QX11Info::appRootWindow(); - - // Treat a null rect as an alias for capturing fullscreen - if (!rect.isValid()) { - rect = getDrawableGeometry(rootWindow); - } else { - QRegion screenRegion; - for (auto screen : QGuiApplication::screens()) { - QRect screenRect = screen->geometry(); - - // Do not use setSize() here, because QSize::operator*=() - // performs qRound() which can result in xcb_image_get() failing - const qreal dpr = screen->devicePixelRatio(); - screenRect.setHeight(qFloor(screenRect.height() * dpr)); - screenRect.setWidth(qFloor(screenRect.width() * dpr)); - - screenRegion += screenRect; - } - - rect = (screenRegion & rect).boundingRect(); - } - - QPixmap nativePixmap = getPixmapFromDrawable(rootWindow, rect); - return postProcessPixmap(nativePixmap, rect, blendPointer); -} - -QPixmap X11ImageGrabber::getWindowPixmap(xcb_window_t window, bool blendPointer) -{ - xcb_connection_t *xcbConn = QX11Info::connection(); - - // first get geometry information for our window - - xcb_get_geometry_cookie_t geomCookie = xcb_get_geometry_unchecked(xcbConn, window); - CScopedPointer geomReply(xcb_get_geometry_reply(xcbConn, geomCookie, nullptr)); - QRect rect(geomReply->x, geomReply->y, geomReply->width, geomReply->height); - - // then proceed to get an image - - QPixmap nativePixmap = getPixmapFromDrawable(window, rect); - - // Translate window coordinates to global ones. - - xcb_get_geometry_cookie_t geomRootCookie = xcb_get_geometry_unchecked(xcbConn, geomReply->root); - CScopedPointer geomRootReply(xcb_get_geometry_reply(xcbConn, geomRootCookie, nullptr)); - - xcb_translate_coordinates_cookie_t translateCookie = xcb_translate_coordinates_unchecked( - xcbConn, window, geomReply->root, geomRootReply->x, geomRootReply->y); - CScopedPointer translateReply( - xcb_translate_coordinates_reply(xcbConn, translateCookie, nullptr)); - - // Adjust local to global coordinates. - rect.moveRight(rect.x() + translateReply->dst_x); - rect.moveTop(rect.y() + translateReply->dst_y); - - // If the window capture failed, try to obtain one from the full screen. - if (nativePixmap.isNull()) { - return getToplevelPixmap(rect, blendPointer); - } - - return postProcessPixmap(nativePixmap, rect, blendPointer); -} - -bool X11ImageGrabber::isKWinAvailable() -{ - if (QDBusConnection::sessionBus().interface()->isServiceRegistered(QStringLiteral("org.kde.KWin"))) { - QDBusInterface interface(QStringLiteral("org.kde.KWin"), QStringLiteral("/Effects"), QStringLiteral("org.kde.kwin.Effects")); - QDBusReply reply = interface.call(QStringLiteral("isEffectLoaded"), QStringLiteral("screenshot")); - - return reply.value(); - } - - return false; -} - -void X11ImageGrabber::KWinDBusScreenshotHelper(quint64 pixmapId) -{ - // obtain width and height and grab an image (x and y are always zero for pixmaps) - QRect rect = getDrawableGeometry((xcb_drawable_t)pixmapId); - mPixmap = getPixmapFromDrawable((xcb_drawable_t)pixmapId, rect); - if (!mPixmap.isNull()) { - emit pixmapChanged(mPixmap); - return; - } - - // Cannot retrieve pixmap from KWin, just fallback to fullscreen capture. We - // could try to detect the original action (window under cursor or active - // window), but that is too complex for this edge case. - grabFullScreen(); -} - -void X11ImageGrabber::rectangleSelectionCancelled() -{ - QObject *sender = QObject::sender(); - sender->disconnect(); - sender->deleteLater(); - - emit imageGrabFailed(); -} - -void X11ImageGrabber::rectangleSelectionConfirmed(const QPixmap &pixmap) -{ - QObject *sender = QObject::sender(); - sender->disconnect(); - sender->deleteLater(); - - mPixmap = pixmap; - emit pixmapChanged(mPixmap); -} - -// 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(); - updateWindowTitle(curWin); - - // grab the image early - - mPixmap = getToplevelPixmap(QRect(), false); - - // now that we know we have a transient window, let's - // find other possible transient windows and the app window itself. - QRegion clipRegion; - - QSet transients; - xcb_window_t parentWinId = curWin; - const QRect desktopRect(0, 0, 1, 1); - do { - // find parent window and add the window to the visible region - xcb_window_t winId = parentWinId; - QRect winRect; - parentWinId = getTransientWindowParent(winId, winRect); - transients << winId; - - // Don't include the 1x1 pixel sized desktop window in the top left corner that is present - // if the window is a QDialog without a parent. - // BUG: 376350 - if (winRect != desktopRect) { - clipRegion += winRect; - } - - // Continue walking only if this is a transient window (having a parent) - } while (parentWinId != XCB_WINDOW_NONE && !transients.contains(parentWinId)); - - - // All parents are known now, find other transient children. - // Assume that the lowest window is behind everything else, then if a new - // transient window is discovered, its children can then also be found. - - QList winList = KWindowSystem::stackingOrder(); - for (auto winId : winList) { - QRect winRect; - xcb_window_t parentWinId = getTransientWindowParent(winId, winRect); - - // if the parent should be displayed, then show the child too - if (transients.contains(parentWinId)) { - if (!transients.contains(winId)) { - transients << winId; - clipRegion += winRect; - } - } - } - - // we can probably go ahead and generate the image now - - QImage tempImage(mPixmap.size(), QImage::Format_ARGB32); - tempImage.fill(Qt::transparent); - - QPainter tempPainter(&tempImage); - tempPainter.setClipRegion(clipRegion); - tempPainter.drawPixmap(0, 0, mPixmap); - tempPainter.end(); - mPixmap = QPixmap::fromImage(tempImage).copy(clipRegion.boundingRect()); - - // why stop here, when we can render a 20px drop shadow all around it - - QGraphicsDropShadowEffect *effect = new QGraphicsDropShadowEffect; - effect->setOffset(0); - effect->setBlurRadius(20); - - QGraphicsPixmapItem *item = new QGraphicsPixmapItem; - item->setPixmap(mPixmap); - item->setGraphicsEffect(effect); - - QImage shadowImage(mPixmap.size() + QSize(40, 40), QImage::Format_ARGB32); - shadowImage.fill(Qt::transparent); - QPainter shadowPainter(&shadowImage); - - QGraphicsScene scene; - scene.addItem(item); - scene.render(&shadowPainter, QRectF(), QRectF(-20, -20, mPixmap.width() + 40, mPixmap.height() + 40)); - shadowPainter.end(); - - // we can finish up now - - mPixmap = QPixmap::fromImage(shadowImage); - if (mCapturePointer) { - QPoint topLeft = clipRegion.boundingRect().topLeft() - QPoint(20, 20); - mPixmap = blendCursorImage(mPixmap, topLeft.x(), topLeft.y(), mPixmap.width(), mPixmap.height()); - } - - emit pixmapChanged(mPixmap); -} - -void X11ImageGrabber::grabActiveWindow() -{ - xcb_window_t activeWindow = KWindowSystem::activeWindow(); - updateWindowTitle(activeWindow); - - // if KWin is available, use the KWin DBus interfaces - - if (mCaptureDecorations && isKWinAvailable()) { - QDBusConnection bus = QDBusConnection::sessionBus(); - bus.connect(QStringLiteral("org.kde.KWin"), - QStringLiteral("/Screenshot"), - QStringLiteral("org.kde.kwin.Screenshot"), - QStringLiteral("screenshotCreated"), - this, SLOT(KWinDBusScreenshotHelper(quint64))); - QDBusInterface interface(QStringLiteral("org.kde.KWin"), QStringLiteral("/Screenshot"), QStringLiteral("org.kde.kwin.Screenshot")); - - int mask = 1; - if (mCapturePointer) { - mask |= 1 << 1; - } - - interface.call(QStringLiteral("screenshotForWindow"), (quint64)activeWindow, mask); - return; - } - - // otherwise, use the native functionality - - return grabApplicationWindowHelper(activeWindow); -} - -void X11ImageGrabber::grabWindowUnderCursor() -{ - const xcb_window_t windowUnderCursor = getRealWindowUnderCursor(); - updateWindowTitle(windowUnderCursor); - - // if KWin is available, use the KWin DBus interfaces - - if (mCaptureDecorations && isKWinAvailable()) { - QDBusConnection bus = QDBusConnection::sessionBus(); - bus.connect(QStringLiteral("org.kde.KWin"), - QStringLiteral("/Screenshot"), - QStringLiteral("org.kde.kwin.Screenshot"), - QStringLiteral("screenshotCreated"), - this, SLOT(KWinDBusScreenshotHelper(quint64))); - QDBusInterface interface(QStringLiteral("org.kde.KWin"), QStringLiteral("/Screenshot"), QStringLiteral("org.kde.kwin.Screenshot")); - - int mask = 1; - if (mCapturePointer) { - mask |= 1 << 1; - } - - interface.call(QStringLiteral("screenshotWindowUnderCursor"), mask); - return; - } - - // else, go native - - return grabApplicationWindowHelper(windowUnderCursor); -} - -void X11ImageGrabber::grabApplicationWindowHelper(xcb_window_t window) -{ - // if the user doesn't want decorations captured, we're in luck. This is - // the easiest bit - - mPixmap = getWindowPixmap(window, mCapturePointer); - if (!mCaptureDecorations || window == QX11Info::appRootWindow()) { - emit pixmapChanged(mPixmap); - return; - } - - // if the user wants the window decorations, things get a little tricky. - // we can't simply get a handle to the window manager frame window and - // just grab it, because some compositing window managers (yes, kwin - // included) do not render the window onto the frame but keep it in a - // separate opengl buffer, so grabbing this window is going to simply - // give us a transparent image with the frame and titlebar. - - // all is not lost. what we need to do is grab the image of the entire - // desktop, find the geometry of the window including its frame, and - // crop the root image accordingly. - - KWindowInfo info(window, NET::WMFrameExtents); - if (info.valid()) { - QRect frameGeom = info.frameGeometry(); - mPixmap = getToplevelPixmap(frameGeom, mCapturePointer); - } - - // fallback is window without the frame - - emit pixmapChanged(mPixmap); -} - -QRect X11ImageGrabber::getDrawableGeometry(xcb_drawable_t drawable) -{ - xcb_connection_t *xcbConn = QX11Info::connection(); - - xcb_get_geometry_cookie_t geomCookie = xcb_get_geometry_unchecked(xcbConn, drawable); - CScopedPointer geomReply(xcb_get_geometry_reply(xcbConn, geomCookie, nullptr)); - if (geomReply.isNull()) { - return QRect(); - } - return QRect(geomReply->x, geomReply->y, geomReply->width, geomReply->height); -} - -void X11ImageGrabber::grabCurrentScreen() -{ - QPoint cursorPosition = QCursor::pos(); - for (auto screen : QGuiApplication::screens()) { - QRect screenRect = screen->geometry(); - if (!screenRect.contains(cursorPosition)) { - continue; - } - - // The screen origin is in native pixels, but the size is device-dependent. Convert these also to native pixels. - QRect nativeScreenRect(screenRect.topLeft(), screenRect.size() * screen->devicePixelRatio()); - mPixmap = getToplevelPixmap(nativeScreenRect, mCapturePointer); - emit pixmapChanged(mPixmap); - return; - } - - // No screen found with our cursor, fallback to capturing full screen - grabFullScreen(); -} - -void X11ImageGrabber::grabRectangularRegion() -{ - const auto pixmap = getToplevelPixmap(QRect(), mCapturePointer); - if (!pixmap.isNull()) { - QuickEditor *editor = new QuickEditor(pixmap); - - connect(editor, &QuickEditor::grabDone, this, &X11ImageGrabber::rectangleSelectionConfirmed); - connect(editor, &QuickEditor::grabCancelled, this, &X11ImageGrabber::rectangleSelectionCancelled); - } else { - emit pixmapChanged(pixmap); - } -} - -xcb_window_t X11ImageGrabber::getRealWindowUnderCursor() -{ - xcb_connection_t *xcbConn = QX11Info::connection(); - xcb_window_t curWin = QX11Info::appRootWindow(); - - const QByteArray atomName("WM_STATE"); - xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(xcbConn, 0, atomName.length(), atomName.constData()); - xcb_query_pointer_cookie_t pointerCookie = xcb_query_pointer_unchecked(xcbConn, curWin); - CScopedPointer atomReply(xcb_intern_atom_reply(xcbConn, atomCookie, nullptr)); - CScopedPointer pointerReply(xcb_query_pointer_reply(xcbConn, pointerCookie, nullptr)); - - if (atomReply->atom == XCB_ATOM_NONE) { - return QX11Info::appRootWindow(); - } - - // now start testing - - QStack windowStack; - windowStack.push(pointerReply->child); - - while (!windowStack.isEmpty()) { - curWin = windowStack.pop(); - - // next, check if our window has the WM_STATE property set on - // the window. if yes, return the window - we have found it - - xcb_get_property_cookie_t propertyCookie = xcb_get_property_unchecked(xcbConn, 0, curWin, atomReply->atom, XCB_ATOM_ANY, 0, 0); - CScopedPointer propertyReply(xcb_get_property_reply(xcbConn, propertyCookie, nullptr)); - - if (propertyReply->type != XCB_ATOM_NONE) { - return curWin; - } - - // if we're here, this means the window is not the real window - // we should start looking at its children - - xcb_query_tree_cookie_t treeCookie = xcb_query_tree_unchecked(xcbConn, curWin); - CScopedPointer treeReply(xcb_query_tree_reply(xcbConn, treeCookie, nullptr)); - xcb_window_t *winChildren = xcb_query_tree_children(treeReply.data()); - int winChildrenLength = xcb_query_tree_children_length(treeReply.data()); - - for (int i = winChildrenLength - 1; i >= 0; i--) { - windowStack.push(winChildren[i]); - } - } - - // return the window. it has geometry information for a crop - - return pointerReply->child; -} - - -// obtain the size of the given window, returning the window ID of the parent -xcb_window_t X11ImageGrabber::getTransientWindowParent(xcb_window_t winId, QRect &outRect) -{ - NET::Properties properties = mCaptureDecorations ? NET::WMFrameExtents : NET::WMGeometry; - KWindowInfo winInfo(winId, properties, NET::WM2TransientFor); - - // add the current window to the image - if (mCaptureDecorations) { - outRect = winInfo.frameGeometry(); - } else { - outRect = winInfo.geometry(); - } - return winInfo.transientFor(); -} - -QPoint X11ImageGrabber::getNativeCursorPosition() -{ - // QCursor::pos() is not used because it requires additional calculations. - // Its value is the offset to the origin of the current screen is in - // device-independent pixels while the origin itself uses native pixels. - - xcb_connection_t *xcbConn = QX11Info::connection(); - xcb_query_pointer_cookie_t pointerCookie = xcb_query_pointer_unchecked(xcbConn, QX11Info::appRootWindow()); - CScopedPointer pointerReply(xcb_query_pointer_reply(xcbConn, pointerCookie, nullptr)); - - return QPoint(pointerReply->root_x, pointerReply->root_y); -} - -QVector X11ImageGrabber::supportedModes() const -{ - if (QApplication::screens().count() == 1) { - return {FullScreen, ActiveWindow, WindowUnderCursor, TransientWithParent, RectangularRegion}; - } - - return {FullScreen, CurrentScreen, ActiveWindow, WindowUnderCursor, TransientWithParent, RectangularRegion}; -} diff --git a/src/PlatformBackends/X11ImageGrabber.h b/src/PlatformBackends/X11ImageGrabber.h deleted file mode 100644 index 78ac805..0000000 --- a/src/PlatformBackends/X11ImageGrabber.h +++ /dev/null @@ -1,105 +0,0 @@ -/* - * 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 X11IMAGEGRABBER_H -#define X11IMAGEGRABBER_H - -#include - -#include -#include - -#include "ImageGrabber.h" - -class X11ImageGrabber; - -class OnClickEventFilter : public QAbstractNativeEventFilter -{ - public: - - explicit OnClickEventFilter(X11ImageGrabber *grabber); - bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) override; - - private: - - X11ImageGrabber *mImageGrabber; -}; - -class X11ImageGrabber : public ImageGrabber -{ - Q_OBJECT - - public: - - explicit X11ImageGrabber(QObject * parent = nullptr); - ~X11ImageGrabber() override; - - QVector supportedModes() const override; - bool onClickGrabSupported() const override; - - protected: - - void grabFullScreen() override; - void grabCurrentScreen() override; - void grabActiveWindow() override; - void grabRectangularRegion() override; - void grabWindowUnderCursor() override; - void grabTransientWithParent() override; - QPixmap blendCursorImage(const QPixmap &pixmap, int x, int y, int width, int height) override; - - private Q_SLOTS: - - void KWinDBusScreenshotHelper(quint64 window); - void rectangleSelectionConfirmed(const QPixmap &pixmap); - void rectangleSelectionCancelled(); - - public Q_SLOTS: - - void doOnClickGrab() override; - - private: - - bool isKWinAvailable(); - xcb_window_t getRealWindowUnderCursor(); - void grabApplicationWindowHelper(xcb_window_t window); - QRect getDrawableGeometry(xcb_drawable_t drawable); - QPixmap postProcessPixmap(const QPixmap &pixmap, QRect rect, bool blendPointer); - QPixmap getPixmapFromDrawable(xcb_drawable_t drawableId, const QRect &rect); - QPixmap getToplevelPixmap(QRect rect, bool blendPointer); - QPixmap getWindowPixmap(xcb_window_t window, bool blendPointer); - QPixmap convertFromNative(xcb_image_t *xcbImage); - xcb_window_t getTransientWindowParent(xcb_window_t winId, QRect &outRect); - QPoint getNativeCursorPosition(); - - OnClickEventFilter *mNativeEventFilter; - void updateWindowTitle(xcb_window_t window); -}; - -template using CScopedPointer = QScopedPointer; - -struct ScopedPointerXcbImageDeleter -{ - static inline void cleanup(xcb_image_t *xcbImage) { - if (xcbImage) { - xcb_image_destroy(xcbImage); - } - } -}; - -#endif // X11IMAGEGRABBER_H diff --git a/src/Platforms/Platform.cpp b/src/Platforms/Platform.cpp new file mode 100644 index 0000000..6083c5e --- /dev/null +++ b/src/Platforms/Platform.cpp @@ -0,0 +1,26 @@ +/* This file is part of Spectacle, the KDE screenshot utility + * Copyright (C) 2019 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. + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#include "Platform.h" + +Platform::Platform(QObject *parent) : + QObject(parent) +{} diff --git a/src/Platforms/Platform.h b/src/Platforms/Platform.h new file mode 100644 index 0000000..c478f8f --- /dev/null +++ b/src/Platforms/Platform.h @@ -0,0 +1,72 @@ +/* This file is part of Spectacle, the KDE screenshot utility + * Copyright (C) 2019 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. + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#pragma once + +#include +#include + +class Platform: public QObject +{ + Q_OBJECT + + public: + + enum class GrabMode { + InvalidChoice = 0x00, + AllScreens = 0x01, + CurrentScreen = 0x02, + ActiveWindow = 0x04, + WindowUnderCursor = 0x08, + TransientWithParent = 0x10 + }; + using GrabModes = QFlags; + Q_FLAG(GrabModes) + + enum class ShutterMode { + Immediate = 0x00, + OnClick = 0x01 + }; + using ShutterModes = QFlags; + Q_FLAG(ShutterModes) + + explicit Platform(QObject *parent = nullptr); + virtual ~Platform() = default; + + virtual QString platformName() const = 0; + virtual GrabModes supportedGrabModes() const = 0; + virtual ShutterModes supportedShutterModes() const = 0; + + public Q_SLOTS: + + virtual void doGrab(ShutterMode theShutterMode, GrabMode theGrabMode, bool theIncludePointer, bool theIncludeDecorations) = 0; + + Q_SIGNALS: + + void newScreenshotTaken(const QPixmap &thePixmap); + void newScreenshotFailed(); + void windowTitleChanged(const QString &theWindowTitle); +}; + +Q_DECLARE_METATYPE(Platform::GrabMode) +Q_DECLARE_METATYPE(Platform::ShutterMode) +Q_DECLARE_OPERATORS_FOR_FLAGS(Platform::GrabModes) +Q_DECLARE_OPERATORS_FOR_FLAGS(Platform::ShutterModes) diff --git a/src/Platforms/PlatformKWinWayland.cpp b/src/Platforms/PlatformKWinWayland.cpp new file mode 100644 index 0000000..77e6c7b --- /dev/null +++ b/src/Platforms/PlatformKWinWayland.cpp @@ -0,0 +1,161 @@ +/* This file is part of Spectacle, the KDE screenshot utility + * Copyright (C) 2016 Martin Graesslin + * + * 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. + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#include "PlatformKWinWayland.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* -- Static Helpers --------------------------------------------------------------------------- */ + +static int readData(int theFile, QByteArray &theDataOut) +{ + // implementation based on QtWayland file qwaylanddataoffer.cpp + char lBuffer[4096]; + int lRetryCount = 0; + ssize_t lBytesRead = 0; + while (true) { + lBytesRead = QT_READ(theFile, lBuffer, sizeof lBuffer); + // give user 30 sec to click a window, afterwards considered as error + if (lBytesRead == -1 && (errno == EAGAIN) && ++lRetryCount < 30000) { + usleep(1000); + } else { + break; + } + } + + if (lBytesRead > 0) { + theDataOut.append(lBuffer, lBytesRead); + lBytesRead = readData(theFile, theDataOut); + } + return lBytesRead; +} + +static QImage readImage(int thePipeFd) +{ + QByteArray lContent; + if (readData(thePipeFd, lContent) != 0) { + close(thePipeFd); + return QImage(); + } + close(thePipeFd); + + QDataStream lDataStream(lContent); + QImage lImage; + lDataStream >> lImage; + return lImage; +} + +/* -- General Plumbing ------------------------------------------------------------------------- */ + +PlatformKWinWayland::PlatformKWinWayland(QObject *parent) : + Platform(parent) +{} + +QString PlatformKWinWayland::platformName() const +{ + return QStringLiteral("KWinWayland"); +} + +Platform::GrabModes PlatformKWinWayland::supportedGrabModes() const +{ + Platform::GrabModes lSupportedModes({ GrabMode::AllScreens, GrabMode::WindowUnderCursor }); + if (QApplication::screens().count() > 1) { + lSupportedModes |= Platform::GrabMode::CurrentScreen; + } + return lSupportedModes; +} + +Platform::ShutterModes PlatformKWinWayland::supportedShutterModes() const +{ + return { ShutterMode::OnClick }; +} + +void PlatformKWinWayland::doGrab(ShutterMode theShutterMode, GrabMode theGrabMode, bool theIncludePointer, bool theIncludeDecorations) +{ + if (theShutterMode != ShutterMode::OnClick) { + emit newScreenshotFailed(); + return; + } + + switch(theGrabMode) { + case GrabMode::AllScreens: + return doGrabHelper(QStringLiteral("screenshotFullscreen"), theIncludePointer); + case GrabMode::CurrentScreen: + return doGrabHelper(QStringLiteral("screenshotScreen"), theIncludePointer); + case GrabMode::WindowUnderCursor: { + int lOpMask = theIncludeDecorations ? 1 : 0; + if (theIncludePointer) { + lOpMask |= 1 << 1; + } + return doGrabHelper(QStringLiteral("interactive"), lOpMask); + } + case GrabMode::InvalidChoice: + case GrabMode::ActiveWindow: + case GrabMode::TransientWithParent: + emit newScreenshotFailed(); + return; + } +} + +/* -- Grab Helpers ----------------------------------------------------------------------------- */ + +void PlatformKWinWayland::startReadImage(int theReadPipe) +{ + auto lWatcher = new QFutureWatcher(this); + QObject::connect(lWatcher, &QFutureWatcher::finished, this, + [lWatcher, this] () { + lWatcher->deleteLater(); + const QImage lImage = lWatcher->result(); + emit newScreenshotTaken(QPixmap::fromImage(lImage)); + } + ); + lWatcher->setFuture(QtConcurrent::run(readImage, theReadPipe)); +} + +template +void PlatformKWinWayland::callDBus(const QString &theGrabMethod, ArgType theArgument, int theWriteFile) +{ + QDBusInterface lInterface(QStringLiteral("org.kde.KWin"), QStringLiteral("/Screenshot"), QStringLiteral("org.kde.kwin.Screenshot")); + lInterface.asyncCall(theGrabMethod, QVariant::fromValue(QDBusUnixFileDescriptor(theWriteFile)), theArgument); +} + +template +void PlatformKWinWayland::doGrabHelper(const QString &theGrabMethod, ArgType theArgument) +{ + int lPipeFds[2]; + if (pipe2(lPipeFds, O_CLOEXEC|O_NONBLOCK) != 0) { + emit newScreenshotFailed(); + return; + } + + callDBus(theGrabMethod, theArgument, lPipeFds[1]); + startReadImage(lPipeFds[0]); + + close(lPipeFds[1]); +} diff --git a/src/Platforms/PlatformKWinWayland.h b/src/Platforms/PlatformKWinWayland.h new file mode 100644 index 0000000..d0d21b0 --- /dev/null +++ b/src/Platforms/PlatformKWinWayland.h @@ -0,0 +1,48 @@ +/* This file is part of Spectacle, the KDE screenshot utility + * Copyright (C) 2016 Martin Graesslin + * + * 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. + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#pragma once + +#include "Platform.h" + +class PlatformKWinWayland final: public Platform +{ + Q_OBJECT + + public: + + explicit PlatformKWinWayland(QObject *parent = nullptr); + virtual ~PlatformKWinWayland() = default; + + QString platformName() const override final; + GrabModes supportedGrabModes() const override final; + ShutterModes supportedShutterModes() const override final; + + public Q_SLOTS: + + void doGrab(ShutterMode theShutterMode, GrabMode theGrabMode, bool theIncludePointer, bool theIncludeDecorations) override final; + + private: + + void startReadImage(int theReadPipe); + template void doGrabHelper(const QString &theGrabMethod, ArgType theArgument); + template void callDBus(const QString &theGrabMethod, ArgType theArgument, int theWriteFile); +}; diff --git a/src/Platforms/PlatformLoader.cpp b/src/Platforms/PlatformLoader.cpp new file mode 100644 index 0000000..648d0f9 --- /dev/null +++ b/src/Platforms/PlatformLoader.cpp @@ -0,0 +1,55 @@ +/* This file is part of Spectacle, the KDE screenshot utility + * Copyright (C) 2019 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. + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#include "Config.h" +#include "PlatformLoader.h" + +#include "PlatformNull.h" +#include "PlatformKWinWayland.h" + +#ifdef XCB_FOUND +#include "PlatformXcb.h" +#endif + +#include + +PlatformPtr loadPlatform() +{ + qRegisterMetaType(); + qRegisterMetaType(); + + // 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) { + return std::make_unique(); + } + + // Try checking if we're running under X11 now +#ifdef XCB_FOUND + if (KWindowSystem::isPlatformX11()) { + return std::make_unique(); + } +#endif + + // If nothing else worked, return the null platform + return std::make_unique(); +} diff --git a/src/Platforms/PlatformLoader.h b/src/Platforms/PlatformLoader.h new file mode 100644 index 0000000..d26080c --- /dev/null +++ b/src/Platforms/PlatformLoader.h @@ -0,0 +1,28 @@ +/* This file is part of Spectacle, the KDE screenshot utility + * Copyright (C) 2019 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. + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#pragma once + +#include "Platform.h" +#include + +using PlatformPtr = std::unique_ptr; +PlatformPtr loadPlatform(); diff --git a/src/Platforms/PlatformNull.cpp b/src/Platforms/PlatformNull.cpp new file mode 100644 index 0000000..1dbda49 --- /dev/null +++ b/src/Platforms/PlatformNull.cpp @@ -0,0 +1,54 @@ +/* This file is part of Spectacle, the KDE screenshot utility + * Copyright (C) 2019 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. + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#include "PlatformNull.h" + +#include + +/* -- Null Platform ---------------------------------------------------------------------------- */ + +PlatformNull::PlatformNull(QObject *parent) : + Platform(parent) +{} + +QString PlatformNull::platformName() const +{ + return QStringLiteral("Null"); +} + +Platform::GrabModes PlatformNull::supportedGrabModes() const +{ + return { GrabMode::AllScreens | GrabMode::CurrentScreen | GrabMode::ActiveWindow | GrabMode::WindowUnderCursor | GrabMode::TransientWithParent }; +} + +Platform::ShutterModes PlatformNull::supportedShutterModes() const +{ + return { ShutterMode::Immediate | ShutterMode::OnClick }; +} + +void PlatformNull::doGrab(ShutterMode theShutterMode, GrabMode theGrabMode, bool theIncludePointer, bool theIncludeDecorations) +{ + Q_UNUSED(theShutterMode) + Q_UNUSED(theGrabMode) + Q_UNUSED(theIncludePointer) + Q_UNUSED(theIncludeDecorations) + emit newScreenshotTaken(QPixmap()); +} diff --git a/src/Platforms/PlatformNull.h b/src/Platforms/PlatformNull.h new file mode 100644 index 0000000..6b55ba4 --- /dev/null +++ b/src/Platforms/PlatformNull.h @@ -0,0 +1,42 @@ +/* This file is part of Spectacle, the KDE screenshot utility + * Copyright (C) 2019 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. + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#pragma once + +#include "Platform.h" + +class PlatformNull final: public Platform +{ + Q_OBJECT + + public: + + explicit PlatformNull(QObject *parent = nullptr); + virtual ~PlatformNull() = default; + + QString platformName() const override final; + GrabModes supportedGrabModes() const override final; + ShutterModes supportedShutterModes() const override final; + + public Q_SLOTS: + + void doGrab(ShutterMode theShutterMode, GrabMode theGrabMode, bool theIncludePointer, bool theIncludeDecorations) override final; +}; diff --git a/src/Platforms/PlatformXcb.cpp b/src/Platforms/PlatformXcb.cpp new file mode 100644 index 0000000..05ad3b7 --- /dev/null +++ b/src/Platforms/PlatformXcb.cpp @@ -0,0 +1,734 @@ +/* This file is part of Spectacle, the KDE screenshot utility + * Copyright (C) 2019 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. + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#include "PlatformXcb.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +/* -- XCB Image Smart Pointer ------------------------------------------------------------------ */ + +struct XcbImagePtrDeleter +{ + void operator()(xcb_image_t *theXcbImage) const + { + if (theXcbImage) { + xcb_image_destroy(theXcbImage); + } + } +}; +using XcbImagePtr = std::unique_ptr; + +/* -- On Click Native Event Filter ------------------------------------------------------------- */ + +class PlatformXcb::OnClickEventFilter: public QAbstractNativeEventFilter +{ + public: + + explicit OnClickEventFilter(PlatformXcb *thePlatformPtr) : + mPlatformPtr(thePlatformPtr) + {} + + void setCaptureOptions(const Platform::GrabMode &theGrabMode, bool theIncludePointer, bool theIncludeDecorations) + { + mGrabMode = theGrabMode; + mIncludePointer = theIncludePointer; + mIncludeDecorations = theIncludeDecorations; + } + + bool nativeEventFilter(const QByteArray &theEventType, void *theMessage, long * /* theResult */) override + { + if (theEventType == "xcb_generic_event_t") { + auto lFirstEvent = static_cast(theMessage); + + switch (lFirstEvent->response_type & ~0x80) { + case XCB_BUTTON_RELEASE: { + // uninstall the eventfilter and release the mouse + qApp->removeNativeEventFilter(this); + xcb_ungrab_pointer(QX11Info::connection(), XCB_TIME_CURRENT_TIME); + + // decide whether to grab or abort. regrab the mouse on mouse-wheel events + { + auto lSecondEvent = static_cast(theMessage); + if (lSecondEvent->detail == 1) { + QTimer::singleShot(0, [this]() { + mPlatformPtr->doGrabNow(mGrabMode, mIncludePointer, mIncludeDecorations); + }); + } else if (lSecondEvent->detail < 4) { + emit mPlatformPtr->newScreenshotFailed(); + } else { + QTimer::singleShot(0, [this]() { + mPlatformPtr->doGrabOnClick(mGrabMode, mIncludePointer, mIncludeDecorations); + }); + } + } + return true; + } + default: + return false; + } + } + return false; + } + + private: + + PlatformXcb *mPlatformPtr; + Platform::GrabMode mGrabMode { GrabMode::AllScreens }; + bool mIncludePointer { true }; + bool mIncludeDecorations { true }; +}; + +/* -- General Plumbing ------------------------------------------------------------------------- */ + +PlatformXcb::PlatformXcb(QObject *theParent) : + Platform(theParent), + mNativeEventFilter(new OnClickEventFilter(this)) +{} + +PlatformXcb::~PlatformXcb() +{ + delete mNativeEventFilter; +} + +QString PlatformXcb::platformName() const +{ + return QStringLiteral("Xcb"); +} + +Platform::GrabModes PlatformXcb::supportedGrabModes() const +{ + Platform::GrabModes lSupportedModes({ GrabMode::AllScreens, GrabMode::ActiveWindow, GrabMode::WindowUnderCursor, GrabMode::TransientWithParent }); + if (QApplication::screens().count() > 1) { + lSupportedModes |= Platform::GrabMode::CurrentScreen; + } + return lSupportedModes; +} + +Platform::ShutterModes PlatformXcb::supportedShutterModes() const +{ + return { ShutterMode::Immediate | ShutterMode::OnClick }; +} + +void PlatformXcb::doGrab(ShutterMode theShutterMode, GrabMode theGrabMode, bool theIncludePointer, bool theIncludeDecorations) +{ + switch(theShutterMode) { + case ShutterMode::Immediate: + return doGrabNow(theGrabMode, theIncludePointer, theIncludeDecorations); + case ShutterMode::OnClick: + return doGrabOnClick(theGrabMode, theIncludePointer, theIncludeDecorations); + } +} + +/* -- Platform Utilities ----------------------------------------------------------------------- */ + +bool PlatformXcb::isKWinAvailable() +{ + if (QDBusConnection::sessionBus().interface()->isServiceRegistered(QStringLiteral("org.kde.KWin"))) { + QDBusInterface lIface(QStringLiteral("org.kde.KWin"), QStringLiteral("/Effects"), QStringLiteral("org.kde.kwin.Effects")); + QDBusReply lReply = lIface.call(QStringLiteral("isEffectLoaded"), QStringLiteral("screenshot")); + return lReply.value(); + } + return false; +} + +/* -- XCB Utilities ---------------------------------------------------------------------------- */ + +QPoint PlatformXcb::getCursorPosition() +{ + // QCursor::pos() is not used because it requires additional calculations. + // Its value is the offset to the origin of the current screen is in + // device-independent pixels while the origin itself uses native pixels. + + auto lXcbConn = QX11Info::connection(); + auto lPointerCookie = xcb_query_pointer_unchecked(lXcbConn, QX11Info::appRootWindow()); + std::unique_ptr lPointerReply(xcb_query_pointer_reply(lXcbConn, lPointerCookie, nullptr)); + + return QPoint(lPointerReply->root_x, lPointerReply->root_y); +} + +QRect PlatformXcb::getDrawableGeometry(xcb_drawable_t theDrawable) +{ + auto lXcbConn = QX11Info::connection(); + auto lGeoCookie = xcb_get_geometry_unchecked(lXcbConn, theDrawable); + std::unique_ptr lGeoReply(xcb_get_geometry_reply(lXcbConn, lGeoCookie, nullptr)); + if (!lGeoReply) { + return QRect(); + } + return QRect(lGeoReply->x, lGeoReply->y, lGeoReply->width, lGeoReply->height); +} + +xcb_window_t PlatformXcb::getWindowUnderCursor() +{ + auto lXcbConn = QX11Info::connection(); + auto lAppWin = QX11Info::appRootWindow(); + + const QByteArray lAtomName("WM_STATE"); + auto lAtomCookie = xcb_intern_atom_unchecked(lXcbConn, 0, lAtomName.length(), lAtomName.constData()); + auto lPointerCookie = xcb_query_pointer_unchecked(lXcbConn, lAppWin); + std::unique_ptr lAtomReply(xcb_intern_atom_reply(lXcbConn, lAtomCookie, nullptr)); + std::unique_ptr lPointerReply(xcb_query_pointer_reply(lXcbConn, lPointerCookie, nullptr)); + + if (lAtomReply->atom == XCB_ATOM_NONE) { + return QX11Info::appRootWindow(); + } + + // now start testing + QStack lWindowStack; + lWindowStack.push(lPointerReply->child); + + while (!lWindowStack.isEmpty()) { + lAppWin = lWindowStack.pop(); + + // next, check if our window has the WM_STATE property set on + // the window. if yes, return the window - we have found it + auto lPropCookie = xcb_get_property_unchecked(lXcbConn, 0, lAppWin, lAtomReply->atom, XCB_ATOM_ANY, 0, 0); + std::unique_ptr lPropReply(xcb_get_property_reply(lXcbConn, lPropCookie, nullptr)); + + if (lPropReply->type != XCB_ATOM_NONE) { + return lAppWin; + } + + // if we're here, this means the window is not the real window + // we should start looking at its children + auto lTreeCookie = xcb_query_tree_unchecked(lXcbConn, lAppWin); + std::unique_ptr lTreeReply(xcb_query_tree_reply(lXcbConn, lTreeCookie, nullptr)); + auto lWindowChildren = xcb_query_tree_children(lTreeReply.get()); + auto lWindowChildrenLength = xcb_query_tree_children_length(lTreeReply.get()); + + for (int iIdx = lWindowChildrenLength - 1; iIdx >= 0; iIdx--) { + lWindowStack.push(lWindowChildren[iIdx]); + } + } + + // return the window. it has geometry information for a crop + return lPointerReply->child; +} + +xcb_window_t PlatformXcb::getTransientWindowParent(xcb_window_t theChildWindow, QRect &theWindowRectOut, bool theIncludeDectorations) +{ + NET::Properties lNetProp = theIncludeDectorations ? NET::WMFrameExtents : NET::WMGeometry; + KWindowInfo lWindowInfo(theChildWindow, lNetProp, NET::WM2TransientFor); + + // add the current window to the image + if (theIncludeDectorations) { + theWindowRectOut = lWindowInfo.frameGeometry(); + } else { + theWindowRectOut = lWindowInfo.geometry(); + } + return lWindowInfo.transientFor(); +} + +/* -- Image Processing Utilities --------------------------------------------------------------- */ + +QPixmap PlatformXcb::convertFromNative(xcb_image_t *theXcbImage) +{ + auto lImageFormat = QImage::Format_Invalid; + switch (theXcbImage->depth) { + case 1: + lImageFormat = QImage::Format_MonoLSB; + break; + case 16: + lImageFormat = QImage::Format_RGB16; + break; + case 24: + lImageFormat = QImage::Format_RGB32; + break; + case 30: + lImageFormat = QImage::Format_BGR30; + break; + case 32: + lImageFormat = QImage::Format_ARGB32_Premultiplied; + break; + default: + return QPixmap(); // we don't know + } + + // the RGB32 format requires data format 0xffRRGGBB, ensure that this fourth byte really is 0xff + if (lImageFormat == QImage::Format_RGB32) { + auto lData = reinterpret_cast(theXcbImage->data); + for (size_t iIter = 0; iIter < theXcbImage->width * theXcbImage->height; iIter++) { + lData[iIter] |= 0xff000000; + } + } + + QImage lImage(theXcbImage->data, theXcbImage->width, theXcbImage->height, lImageFormat); + if (lImage.isNull()) { + return QPixmap(); + } + + // work around an abort in QImage::color + if (lImage.format() == QImage::Format_MonoLSB) { + lImage.setColorCount(2); + lImage.setColor(0, QColor(Qt::white).rgb()); + lImage.setColor(1, QColor(Qt::black).rgb()); + } + + // the image is ready. Since the backing data from xcbImage could be freed + // before the QPixmap goes away, a deep copy is necessary. + return QPixmap::fromImage(lImage).copy(); +} + +QPixmap PlatformXcb::blendCursorImage(QPixmap &thePixmap, const QRect theRect) +{ + // If the cursor position lies outside the area, do not bother drawing a cursor. + + auto lCursorPos = getCursorPosition(); + if (!theRect.contains(lCursorPos)) { + return thePixmap; + } + + // now we can get the image and start processing + auto lXcbConn = QX11Info::connection(); + + auto lCursorCookie = xcb_xfixes_get_cursor_image_unchecked(lXcbConn); + std::unique_ptr lCursorReply(xcb_xfixes_get_cursor_image_reply(lXcbConn, lCursorCookie, nullptr)); + if (!lCursorReply) { + return thePixmap; + } + + // get the image and process it into a qimage + auto lPixelData = xcb_xfixes_get_cursor_image_cursor_image(lCursorReply.get()); + if (!lPixelData) { + return thePixmap; + } + QImage lCursorImage(reinterpret_cast(lPixelData), lCursorReply->width, lCursorReply->height, QImage::Format_ARGB32_Premultiplied); + + // a small fix for the cursor position for fancier cursors + lCursorPos -= QPoint(lCursorReply->xhot, lCursorReply->yhot); + + // now we translate the cursor point to our screen rectangle and do the painting + lCursorPos -= QPoint(theRect.x(), theRect.y()); + QPainter lPainter(&thePixmap); + lPainter.drawImage(lCursorPos, lCursorImage); + return thePixmap; +} + +QPixmap PlatformXcb::postProcessPixmap(QPixmap &thePixmap, QRect theRect, bool theBlendPointer) +{ + if (!(theBlendPointer)) { + // note: this may be the null pixmap if an error occurred. + return thePixmap; + } + return blendCursorImage(thePixmap, theRect); +} + +/* -- Capture Helpers -------------------------------------------------------------------------- */ + +QPixmap PlatformXcb::getPixmapFromDrawable(xcb_drawable_t theXcbDrawable, const QRect &theRect) +{ + auto lXcbConn = QX11Info::connection(); + + // proceed to get an image based on the geometry (in device pixels) + XcbImagePtr lXcbImage( + xcb_image_get( + lXcbConn, + theXcbDrawable, + theRect.x(), + theRect.y(), + theRect.width(), + theRect.height(), + ~0, + XCB_IMAGE_FORMAT_Z_PIXMAP + ) + ); + + // too bad, the capture failed. + if (lXcbImage) { + return QPixmap(); + } + + // now process the image + auto lPixmap = convertFromNative(lXcbImage.get()); + return lPixmap; +} + +QPixmap PlatformXcb::getToplevelPixmap(QRect theRect, bool theBlendPointer) +{ + auto lRootWindow = QX11Info::appRootWindow(); + + // treat a null rect as an alias for capturing fullscreen + if (!theRect.isValid()) { + theRect = getDrawableGeometry(lRootWindow); + } else { + QRegion lScreenRegion; + for (auto lScreen: QGuiApplication::screens()) { + auto lScreenRect = lScreen->geometry(); + + // Do not use setSize() here, because QSize::operator*=() + // performs qRound() which can result in xcb_image_get() failing + const auto lPixelRatio = lScreen->devicePixelRatio(); + lScreenRect.setHeight(qFloor(lScreenRect.height() * lPixelRatio)); + lScreenRect.setWidth(qFloor(lScreenRect.width() * lPixelRatio)); + + lScreenRegion += lScreenRect; + } + theRect = (lScreenRegion & theRect).boundingRect(); + } + + auto lPixmap = getPixmapFromDrawable(lRootWindow, theRect); + return postProcessPixmap(lPixmap, theRect, theBlendPointer); +} + +QPixmap PlatformXcb::getWindowPixmap(xcb_window_t theWindow, bool theBlendPointer) +{ + auto lXcbConn = QX11Info::connection(); + + // first get geometry information for our window + auto lGeoCookie = xcb_get_geometry_unchecked(lXcbConn, theWindow); + std::unique_ptr lGeoReply(xcb_get_geometry_reply(lXcbConn, lGeoCookie, nullptr)); + QRect lWindowRect(lGeoReply->x, lGeoReply->y, lGeoReply->width, lGeoReply->height); + + // then proceed to get an image + auto lPixmap = getPixmapFromDrawable(theWindow, lWindowRect); + + // translate window coordinates to global ones. + auto lRootGeoCookie = xcb_get_geometry_unchecked(lXcbConn, lGeoReply->root); + std::unique_ptr lRootGeoReply(xcb_get_geometry_reply(lXcbConn, lRootGeoCookie, nullptr)); + auto lTranslateCookie = xcb_translate_coordinates_unchecked(lXcbConn, theWindow, lGeoReply->root, lRootGeoReply->x, lRootGeoReply->y); + std::unique_ptr lTranslateReply(xcb_translate_coordinates_reply(lXcbConn, lTranslateCookie, nullptr)); + + // adjust local to global coordinates. + lWindowRect.moveRight(lWindowRect.x() + lTranslateReply->dst_x); + lWindowRect.moveTop(lWindowRect.y() + lTranslateReply->dst_y); + + // if the window capture failed, try to obtain one from the full screen. + if (lPixmap.isNull()) { + return getToplevelPixmap(lWindowRect, theBlendPointer); + } + return postProcessPixmap(lPixmap, lWindowRect, theBlendPointer); +} + +void PlatformXcb::handleKWinScreenshotReply(quint64 theDrawable) +{ + // obtain width and height and grab an image (x and y are always zero for pixmaps) + auto lDrawable = static_cast(theDrawable); + auto lRect = getDrawableGeometry(lDrawable); + auto lPixmap = getPixmapFromDrawable(lDrawable, lRect); + + if (!lPixmap.isNull()) { + emit newScreenshotTaken(lPixmap); + return; + } + emit newScreenshotFailed(); +} + +/* -- Grabber Methods -------------------------------------------------------------------------- */ + +void PlatformXcb::grabAllScreens(bool theIncludePointer) +{ + auto lPixmap = getToplevelPixmap(QRect(), theIncludePointer); + emit newScreenshotTaken(lPixmap); +} + +void PlatformXcb::grabCurrentScreen(bool theIncludePointer) +{ + auto lCursorPosition = QCursor::pos(); + for (auto lScreen: QGuiApplication::screens()) { + auto lScreenRect = lScreen->geometry(); + if (!lScreenRect.contains(lCursorPosition)) { + continue; + } + + // the screen origin is in native pixels, but the size is device-dependent. + // convert these also to native pixels. + QRect lNativeScreenRect(lScreenRect.topLeft(), lScreenRect.size() * lScreen->devicePixelRatio()); + auto lPixmap = getToplevelPixmap(lNativeScreenRect, theIncludePointer); + emit newScreenshotTaken(lPixmap); + return; + } + + // no screen found with our cursor, fallback to capturing all screens + grabAllScreens(theIncludePointer); +} + +void PlatformXcb::grabApplicationWindow(xcb_window_t theWindow, bool theIncludePointer, bool theIncludeDecorations) +{ + // if the user doesn't want decorations captured, we're in luck. This is + // the easiest bit + + auto lPixmap = getWindowPixmap(theWindow, theIncludePointer); + if (!theIncludeDecorations || theWindow == QX11Info::appRootWindow()) { + emit newScreenshotTaken(lPixmap); + return; + } + + // if the user wants the window decorations, things get a little tricky. + // we can't simply get a handle to the window manager frame window and + // just grab it, because some compositing window managers (yes, kwin + // included) do not render the window onto the frame but keep it in a + // separate opengl buffer, so grabbing this window is going to simply + // give us a transparent image with the frame and titlebar. + + // all is not lost. what we need to do is grab the image of the entire + // desktop, find the geometry of the window including its frame, and + // crop the root image accordingly. + + KWindowInfo lWindowInfo(theWindow, NET::WMFrameExtents); + if (lWindowInfo.valid()) { + auto lFrameGeom = lWindowInfo.frameGeometry(); + lPixmap = getToplevelPixmap(lFrameGeom, theIncludePointer); + } + + // fallback is window without the frame + emit newScreenshotTaken(lPixmap); +} + +void PlatformXcb::grabActiveWindow(bool theIncludePointer, bool theIncludeDecorations) +{ + auto lActiveWindow = KWindowSystem::activeWindow(); + //FIXME: updateWindowTitle(activeWindow); + + // if KWin is available, use the KWin DBus interfaces + if (theIncludeDecorations && isKWinAvailable()) { + auto lBus = QDBusConnection::sessionBus(); + lBus.connect(QStringLiteral("org.kde.KWin"), + QStringLiteral("/Screenshot"), + QStringLiteral("org.kde.kwin.Screenshot"), + QStringLiteral("screenshotCreated"), + this, SLOT(handleKWinScreenshotReply(quint64))); + QDBusInterface lIface(QStringLiteral("org.kde.KWin"), QStringLiteral("/Screenshot"), QStringLiteral("org.kde.kwin.Screenshot")); + + int lOpMask = 1; + if (theIncludePointer) { + lOpMask |= 1 << 1; + } + lIface.call(QStringLiteral("screenshotForWindow"), static_cast(lActiveWindow), lOpMask); + + return; + } + + // otherwise, use the native functionality + grabApplicationWindow(lActiveWindow, theIncludePointer, theIncludeDecorations); +} + +void PlatformXcb::grabWindowUnderCursor(bool theIncludePointer, bool theIncludeDecorations) +{ + auto lWindow = getWindowUnderCursor(); + //FIXME: updateWindowTitle(windowUnderCursor); + + // if KWin is available, use the KWin DBus interfaces + if (theIncludeDecorations && isKWinAvailable()) { + auto lBus = QDBusConnection::sessionBus(); + lBus.connect(QStringLiteral("org.kde.KWin"), + QStringLiteral("/Screenshot"), + QStringLiteral("org.kde.kwin.Screenshot"), + QStringLiteral("screenshotCreated"), + this, SLOT(handleKWinScreenshotReply(quint64))); + QDBusInterface lInterface(QStringLiteral("org.kde.KWin"), QStringLiteral("/Screenshot"), QStringLiteral("org.kde.kwin.Screenshot")); + + int lOpMask = 1; + if (theIncludePointer) { + lOpMask |= 1 << 1; + } + lInterface.call(QStringLiteral("screenshotWindowUnderCursor"), lOpMask); + + return; + } + + // otherwise, use the native functionality + grabApplicationWindow(lWindow, theIncludePointer, theIncludeDecorations); +} + +void PlatformXcb::grabTransientWithParent(bool theIncludePointer, bool theIncludeDecorations) +{ + auto lWindow = getWindowUnderCursor(); + //FIXME: updateWindowTitle(curWin); + + // grab the image early + auto lPixmap = getToplevelPixmap(QRect(), false); + + // now that we know we have a transient window, let's + // find other possible transient windows and the app window itself. + QRegion lClipRegion; + QSet lTransientWindows; + auto lParentWindow = lWindow; + const QRect lDesktopRect(0, 0, 1, 1); + do { + // find parent window and add the window to the visible region + auto lWinId = lParentWindow; + QRect lWinRect; + lParentWindow = getTransientWindowParent(lWinId, lWinRect, theIncludeDecorations); + lTransientWindows << lWinId; + + // Don't include the 1x1 pixel sized desktop window in the top left corner that is present + // if the window is a QDialog without a parent. + // BUG: 376350 + if (lWinRect != lDesktopRect) { + lClipRegion += lWinRect; + } + + // Continue walking only if this is a transient window (having a parent) + } while (lParentWindow != XCB_WINDOW_NONE && !lTransientWindows.contains(lParentWindow)); + + + // All parents are known now, find other transient children. + // Assume that the lowest window is behind everything else, then if a new + // transient window is discovered, its children can then also be found. + auto lWinList = KWindowSystem::stackingOrder(); + for (auto lWinId: lWinList) { + QRect lWinRect; + auto lParentWindow = getTransientWindowParent(lWinId, lWinRect, theIncludeDecorations); + + // if the parent should be displayed, then show the child too + if (lTransientWindows.contains(lParentWindow)) { + if (!lTransientWindows.contains(lWinId)) { + lTransientWindows << lWinId; + lClipRegion += lWinRect; + } + } + } + + // we can probably go ahead and generate the image now + QImage lImage(lPixmap.size(), QImage::Format_ARGB32); + lImage.fill(Qt::transparent); + + QPainter lPainter(&lImage); + lPainter.setClipRegion(lClipRegion); + lPainter.drawPixmap(0, 0, lPixmap); + lPainter.end(); + lPixmap = QPixmap::fromImage(lImage).copy(lClipRegion.boundingRect()); + + // why stop here, when we can render a 20px drop shadow all around it + auto lShadowEffect = new QGraphicsDropShadowEffect; + lShadowEffect->setOffset(0); + lShadowEffect->setBlurRadius(20); + + auto lPixmapItem = new QGraphicsPixmapItem; + lPixmapItem->setPixmap(lPixmap); + lPixmapItem->setGraphicsEffect(lShadowEffect); + + QImage lShadowImage(lPixmap.size() + QSize(40, 40), QImage::Format_ARGB32); + lShadowImage.fill(Qt::transparent); + QPainter lShadowPainter(&lShadowImage); + + QGraphicsScene lGraphicsScene; + lGraphicsScene.addItem(lPixmapItem); + lGraphicsScene.render(&lShadowPainter, QRectF(), QRectF(-20, -20, lPixmap.width() + 40, lPixmap.height() + 40)); + lShadowPainter.end(); + + // we can finish up now + lPixmap = QPixmap::fromImage(lShadowImage); + if (theIncludePointer) { + auto lTopLeft = lClipRegion.boundingRect().topLeft() - QPoint(20, 20); + lPixmap = blendCursorImage(lPixmap, QRect(lTopLeft, QSize(lPixmap.width(), lPixmap.height()))); + } + emit newScreenshotTaken(lPixmap); +} + +void PlatformXcb::doGrabNow(const GrabMode &theGrabMode, bool theIncludePointer, bool theIncludeDecorations) +{ + switch(theGrabMode) { + case GrabMode::AllScreens: + grabAllScreens(theIncludePointer); + break; + case GrabMode::CurrentScreen: + grabCurrentScreen(theIncludePointer); + break; + case GrabMode::ActiveWindow: + grabActiveWindow(theIncludePointer, theIncludeDecorations); + break; + case GrabMode::WindowUnderCursor: + grabWindowUnderCursor(theIncludePointer, theIncludeDecorations); + break; + case GrabMode::TransientWithParent: + grabTransientWithParent(theIncludePointer, theIncludeDecorations); + break; + case GrabMode::InvalidChoice: + emit newScreenshotFailed(); + } +} + +void PlatformXcb::doGrabOnClick(const GrabMode &theGrabMode, bool theIncludePointer, bool theIncludeDecorations) +{ + // get the cursor image + xcb_cursor_t lXcbCursor = XCB_CURSOR_NONE; + xcb_cursor_context_t *lXcbCursorCtx = nullptr; + xcb_screen_t *lXcbAppScreen = xcb_aux_get_screen(QX11Info::connection(), QX11Info::appScreen()); + + if (xcb_cursor_context_new(QX11Info::connection(), lXcbAppScreen, &lXcbCursorCtx) >= 0) { + QVector lCursorNames = { + QByteArrayLiteral("cross"), + QByteArrayLiteral("crosshair"), + QByteArrayLiteral("diamond-cross"), + QByteArrayLiteral("cross-reverse") + }; + + for(const auto &lCursorName: lCursorNames) { + xcb_cursor_t lCursor = xcb_cursor_load_cursor(lXcbCursorCtx, lCursorName.constData()); + if (lCursor != XCB_CURSOR_NONE) { + lXcbCursor = lCursor; + break; + } + } + } + + // grab the cursor + xcb_grab_pointer_cookie_t grabPointerCookie = xcb_grab_pointer_unchecked( + QX11Info::connection(), // xcb connection + 0, // deliver events to owner? nope + QX11Info::appRootWindow(), // window to grab pointer for (root) + XCB_EVENT_MASK_BUTTON_RELEASE, // which events do I want + XCB_GRAB_MODE_SYNC, // pointer grab mode + XCB_GRAB_MODE_ASYNC, // keyboard grab mode (why is this even here) + XCB_NONE, // confine pointer to which window (none) + lXcbCursor, // cursor to change to for the duration of grab + XCB_TIME_CURRENT_TIME // do this right now + ); + std::unique_ptr lGrabPointerReply(xcb_grab_pointer_reply(QX11Info::connection(), grabPointerCookie, nullptr)); + + // if the grab failed, take the screenshot right away + if (lGrabPointerReply->status != XCB_GRAB_STATUS_SUCCESS) { + doGrabNow(theGrabMode, theIncludePointer, theIncludeDecorations); + return; + } + + // fix things if our pointer grab causes a lockup and install our event filter + mNativeEventFilter->setCaptureOptions(theGrabMode, theIncludePointer, theIncludeDecorations); + xcb_allow_events(QX11Info::connection(), XCB_ALLOW_SYNC_POINTER, XCB_TIME_CURRENT_TIME); + qApp->installNativeEventFilter(mNativeEventFilter); + + // done. clean stuff up + xcb_cursor_context_free(lXcbCursorCtx); + xcb_free_cursor(QX11Info::connection(), lXcbCursor); +} diff --git a/src/Platforms/PlatformXcb.h b/src/Platforms/PlatformXcb.h new file mode 100644 index 0000000..5f978aa --- /dev/null +++ b/src/Platforms/PlatformXcb.h @@ -0,0 +1,78 @@ +/* This file is part of Spectacle, the KDE screenshot utility + * Copyright (C) 2019 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. + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#pragma once + +#include "Platform.h" + +#include +#include + +#include + +class PlatformXcb final: public Platform +{ + Q_OBJECT + + public: + + explicit PlatformXcb(QObject *parent = nullptr); + virtual ~PlatformXcb(); + + QString platformName() const override final; + GrabModes supportedGrabModes() const override final; + ShutterModes supportedShutterModes() const override final; + + public Q_SLOTS: + + void doGrab(ShutterMode theShutterMode, GrabMode theGrabMode, bool theIncludePointer, bool theIncludeDecorations) override final; + + private Q_SLOTS: + + void handleKWinScreenshotReply(quint64 theDrawable); + void doGrabNow(const GrabMode &theGrabMode, bool theIncludePointer, bool theIncludeDecorations); + void doGrabOnClick(const GrabMode &theGrabMode, bool theIncludePointer, bool theIncludeDecorations); + + private: + + bool isKWinAvailable(); + QPoint getCursorPosition(); + QRect getDrawableGeometry(xcb_drawable_t theDrawable); + xcb_window_t getWindowUnderCursor(); + xcb_window_t getTransientWindowParent(xcb_window_t theChildWindow, QRect &theWindowRectOut, bool theIncludeDectorations); + QPixmap convertFromNative(xcb_image_t *theXcbImage); + QPixmap blendCursorImage(QPixmap &thePixmap, const QRect theRect); + QPixmap postProcessPixmap(QPixmap &thePixmap, QRect theRect, bool theBlendPointer); + QPixmap getPixmapFromDrawable(xcb_drawable_t theXcbDrawable, const QRect &theRect); + QPixmap getToplevelPixmap(QRect theRect, bool theBlendPointer); + QPixmap getWindowPixmap(xcb_window_t theWindow, bool theBlendPointer); + + void grabAllScreens(bool theIncludePointer); + void grabCurrentScreen(bool theIncludePointer); + void grabApplicationWindow(xcb_window_t theWindow, bool theIncludePointer, bool theIncludeDecorations); + void grabActiveWindow(bool theIncludePointer, bool theIncludeDecorations); + void grabWindowUnderCursor(bool theIncludePointer, bool theIncludeDecorations); + void grabTransientWithParent(bool theIncludePointer, bool theIncludeDecorations); + + // on-click screenshot shutter support needs a native event filter in xcb + class OnClickEventFilter; + OnClickEventFilter *mNativeEventFilter; +}; diff --git a/src/SpectacleCommon.h b/src/SpectacleCommon.h new file mode 100644 index 0000000..8645e62 --- /dev/null +++ b/src/SpectacleCommon.h @@ -0,0 +1,34 @@ +/* This file is part of Spectacle, the KDE screenshot utility + * Copyright (C) 2019 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. + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#pragma once + +namespace Spectacle { + enum CaptureMode { + InvalidChoice = -1, + AllScreens = 0, + CurrentScreen = 1, + ActiveWindow = 2, + WindowUnderCursor = 3, + TransientWithParent = 4, + RectangularRegion = 5 + }; +} diff --git a/src/SpectacleCore.cpp b/src/SpectacleCore.cpp index 043dc07..8784a78 100644 --- a/src/SpectacleCore.cpp +++ b/src/SpectacleCore.cpp @@ -1,349 +1,355 @@ /* * 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 "spectacle_core_debug.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 #include #include -SpectacleCore::SpectacleCore(StartMode startMode, ImageGrabber::GrabMode grabMode, QString &saveFileName, - qint64 delayMsec, bool notifyOnGrab, bool copyToClipboard, QObject *parent) : +SpectacleCore::SpectacleCore(StartMode theStartMode, + Spectacle::CaptureMode theCaptureMode, + QString &theSaveFileName, + qint64 theDelayMsec, + bool theNotifyOnGrab, + bool theCopyToClipboard, + QObject *parent) : QObject(parent), - mExportManager(ExportManager::instance()), - mStartMode(startMode), - mNotify(notifyOnGrab), - mImageGrabber(nullptr), + mStartMode(theStartMode), + mNotify(theNotifyOnGrab), + mPlatform(loadPlatform()), mMainWindow(nullptr), - isGuiInited(false), - copyToClipboard(copyToClipboard) + mIsGuiInited(false), + mCopyToClipboard(theCopyToClipboard) { - KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("spectaclerc")); - KConfigGroup guiConfig(config, "GuiConfig"); + auto lConfig = KSharedConfig::openConfig(QStringLiteral("spectaclerc")); + KConfigGroup lGuiConfig(lConfig, "GuiConfig"); - if (!(saveFileName.isEmpty() || saveFileName.isNull())) { - if (QDir::isRelativePath(saveFileName)) { - saveFileName = QDir::current().absoluteFilePath(saveFileName); + if (!(theSaveFileName.isEmpty() || theSaveFileName.isNull())) { + if (QDir::isRelativePath(theSaveFileName)) { + theSaveFileName = QDir::current().absoluteFilePath(theSaveFileName); } - setFilename(saveFileName); + setFilename(theSaveFileName); } - // 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 + // 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); - else { - mImageGrabber = new DummyImageGrabber; + auto lImmediateAvailable = mPlatform->supportedShutterModes().testFlag(Platform::ShutterMode::Immediate); + auto lOnClickAvailable = mPlatform->supportedShutterModes().testFlag(Platform::ShutterMode::OnClick); + if ((!lOnClickAvailable) && (theDelayMsec < 0)) { + theDelayMsec = 0; } - setGrabMode(grabMode); - mImageGrabber->setCapturePointer(guiConfig.readEntry("includePointer", true)); - mImageGrabber->setCaptureDecorations(guiConfig.readEntry("includeDecorations", true)); - - if ((!(mImageGrabber->onClickGrabSupported())) && (delayMsec < 0)) { - delayMsec = 0; + // reset last region if it should not be remembered across restarts + auto lSpectacleConfig = SpectacleConfig::instance(); + if(!lSpectacleConfig->alwaysRememberRegion()) { + lSpectacleConfig->setCropRegion(QRect()); } - //Reset last region if it should not be remembered across restarts - SpectacleConfig* cfg = SpectacleConfig::instance(); - if(!cfg->alwaysRememberRegion()) { - cfg->setCropRegion(QRect()); - } + // 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); - 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: + switch (theStartMode) { + case StartMode::DBus: break; - case BackgroundMode: { - int msec = (KWindowSystem::compositingActive() ? 200 : 50) + delayMsec; - QTimer::singleShot(msec, mImageGrabber, &ImageGrabber::doImageGrab); + 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); + QTimer::singleShot(lMsec, [ this, lCaptureMode, lShutterMode, lIncludePointer, lIncludeDecorations ]() { + mPlatform->doGrab(lShutterMode, lCaptureMode, lIncludePointer, lIncludeDecorations); + }); } break; - case GuiMode: - initGui(); + case StartMode::Gui: + initGui(lGuiConfig.readEntry("includePointer", true), lGuiConfig.readEntry("includeDecorations", true)); 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(ImageGrabber::GrabMode grabMode) -{ - mImageGrabber->setGrabMode(grabMode); - mExportManager->setGrabMode(grabMode); -} - // Slots void SpectacleCore::dbusStartAgent() { qApp->setQuitOnLastWindowClosed(true); - if (!(mStartMode == GuiMode)) { - mStartMode = GuiMode; - initGui(); + + 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); } else { using Actions = SpectacleConfig::PrintKeyActionRunning; switch (SpectacleConfig::instance()->printKeyActionRunning()) { - case Actions::TakeNewScreenshot: - QTimer::singleShot(KWindowSystem::compositingActive() ? 200 : 50, mImageGrabber, &ImageGrabber::doImageGrab); + case Actions::TakeNewScreenshot: { + auto lShutterMode = mPlatform->supportedShutterModes().testFlag(Platform::ShutterMode::Immediate) ? Platform::ShutterMode::Immediate : Platform::ShutterMode::OnClick; + auto lGrabMode = toPlatformGrabMode(ExportManager::instance()->captureMode()); + QTimer::singleShot(KWindowSystem::compositingActive() ? 200 : 50, [this, lShutterMode, lGrabMode, lIncludePointer, lIncludeDecorations]() { + mPlatform->doGrab(lShutterMode, lGrabMode, lIncludePointer, lIncludeDecorations); + }); break; + } case Actions::FocusWindow: - KWindowSystem::forceActiveWindow(mMainWindow->winId());; + KWindowSystem::forceActiveWindow(mMainWindow->winId()); break; case Actions::StartNewInstance: QProcess newInstance; newInstance.setProgram(QStringLiteral("spectacle")); newInstance.startDetached(); break; } } } -void SpectacleCore::takeNewScreenshot(const ImageGrabber::GrabMode &mode, - const int &timeout, const bool &includePointer, const bool &includeDecorations) +void SpectacleCore::takeNewScreenshot(Spectacle::CaptureMode theCaptureMode, + int theTimeout, + bool theIncludePointer, + bool theIncludeDecorations) { - setGrabMode(mode); - mImageGrabber->setCapturePointer(includePointer); - mImageGrabber->setCaptureDecorations(includeDecorations); + ExportManager::instance()->setCaptureMode(theCaptureMode); + auto lGrabMode = toPlatformGrabMode(theCaptureMode); - if (timeout < 0) { - mImageGrabber->doOnClickGrab(); - return; + if (theTimeout < 0) { + mPlatform->doGrab(Platform::ShutterMode::OnClick, lGrabMode, theIncludePointer, theIncludeDecorations); } // 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); + auto lMsec = KWindowSystem::compositingActive() ? 200 : 50; + QTimer::singleShot(theTimeout + lMsec, [this, lGrabMode, theIncludePointer, theIncludeDecorations]() { + mPlatform->doGrab(Platform::ShutterMode::Immediate, lGrabMode, theIncludePointer, theIncludeDecorations); + }); } -void SpectacleCore::showErrorMessage(const QString &errString) +void SpectacleCore::showErrorMessage(const QString &theErrString) { - qCDebug(SPECTACLE_CORE_LOG) << "ERROR: " << errString; + qCDebug(SPECTACLE_CORE_LOG) << "ERROR: " << theErrString; - if (mStartMode == GuiMode) { - KMessageBox::error(nullptr, errString); + if (mStartMode == StartMode::Gui) { + KMessageBox::error(nullptr, theErrString); } } -void SpectacleCore::screenshotUpdated(const QPixmap &pixmap) +void SpectacleCore::screenshotUpdated(const QPixmap &thePixmap) { - mExportManager->setPixmap(pixmap); - mExportManager->updatePixmapTimestamp(); + auto lExportManager = ExportManager::instance(); + lExportManager->setPixmap(thePixmap); + lExportManager->updatePixmapTimestamp(); switch (mStartMode) { - case BackgroundMode: - case DBusMode: - default: + case StartMode::Background: + case StartMode::DBus: { if (mNotify) { - connect(mExportManager, &ExportManager::imageSaved, this, &SpectacleCore::doNotify); + connect(lExportManager, &ExportManager::imageSaved, this, &SpectacleCore::doNotify); } - if (copyToClipboard) { - mExportManager->doCopyToClipboard(mNotify); + if (mCopyToClipboard) { + lExportManager->doCopyToClipboard(mNotify); } else { - QUrl savePath = (mStartMode == BackgroundMode && mFileNameUrl.isValid() && mFileNameUrl.isLocalFile()) ? + QUrl lSavePath = (mStartMode == StartMode::Background && mFileNameUrl.isValid() && mFileNameUrl.isLocalFile()) ? mFileNameUrl : QUrl(); - mExportManager->doSave(savePath); + lExportManager->doSave(lSavePath); } // 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); + case StartMode::Gui: + mMainWindow->setScreenshotAndShow(thePixmap); } } void SpectacleCore::screenshotFailed() { switch (mStartMode) { - case BackgroundMode: + case StartMode::Background: showErrorMessage(i18n("Screenshot capture canceled or failed")); emit allDone(); return; - case DBusMode: - default: + case StartMode::DBus: emit grabFailed(); emit allDone(); return; - case GuiMode: + case StartMode::Gui: mMainWindow->show(); } } -void SpectacleCore::doNotify(const QUrl &savedAt) +void SpectacleCore::doNotify(const QUrl &theSavedAt) { - KNotification *notify = new KNotification(QStringLiteral("newScreenshotSaved")); + KNotification *lNotify = new KNotification(QStringLiteral("newScreenshotSaved")); - switch(mImageGrabber->grabMode()) { - case ImageGrabber::GrabMode::FullScreen: - notify->setTitle(i18nc("The entire screen area was captured, heading", "Full Screen Captured")); + switch(ExportManager::instance()->captureMode()) { + case Spectacle::CaptureMode::AllScreens: + lNotify->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")); + case Spectacle::CaptureMode::CurrentScreen: + lNotify->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")); + case Spectacle::CaptureMode::ActiveWindow: + lNotify->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")); + case Spectacle::CaptureMode::WindowUnderCursor: + case Spectacle::CaptureMode::TransientWithParent: + lNotify->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")); + case Spectacle::CaptureMode::RectangularRegion: + lNotify->setTitle(i18nc("A rectangular region was captured, heading", "Rectangular Region Captured")); break; - case ImageGrabber::GrabMode::InvalidChoice: - default: + case Spectacle::CaptureMode::InvalidChoice: break; } - const QString &path = savedAt.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path(); - // a speaking message is prettier than a URL, special case for copy to clipboard and the default pictures location - if (copyToClipboard) { - notify->setText(i18n("A screenshot was saved to your clipboard.")); - } else if (path == QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)) { - notify->setText(i18nc("Placeholder is filename", "A screenshot was saved as '%1' to your Pictures folder.", savedAt.fileName())); + const QString &lSavePath = theSavedAt.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path(); + if (mCopyToClipboard) { + 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())); } else { - notify->setText(i18n("A screenshot was saved as '%1' to '%2'.", savedAt.fileName(), path)); + lNotify->setText(i18n("A screenshot was saved as '%1' to '%2'.", theSavedAt.fileName(), lSavePath)); } - if (!copyToClipboard) { - notify->setUrls({savedAt}); - - notify->setDefaultAction(i18nc("Open the screenshot we just saved", "Open")); - connect(notify, QOverload::of(&KNotification::activated), this, [this, savedAt](uint index) { + if (!mCopyToClipboard) { + lNotify->setUrls({theSavedAt}); + lNotify->setDefaultAction(i18nc("Open the screenshot we just saved", "Open")); + connect(lNotify, QOverload::of(&KNotification::activated), this, [this, theSavedAt](uint index) { if (index == 0) { - new KRun(savedAt, nullptr); + new KRun(theSavedAt, nullptr); QTimer::singleShot(250, this, &SpectacleCore::allDone); } }); } - connect(notify, &QObject::destroyed, this, &SpectacleCore::allDone); - - notify->sendEvent(); + connect(lNotify, &QObject::destroyed, this, &SpectacleCore::allDone); + lNotify->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()) { + auto lExportManager = ExportManager::instance(); + QUrl lTempFile = lExportManager->tempSave(); + if (!lTempFile.isValid()) { return; } - QMimeData *mimeData = new QMimeData; - mimeData->setUrls(QList { tempFile }); - mimeData->setData(QStringLiteral("application/x-kde-suggestedfilename"), QFile::encodeName(tempFile.fileName())); + auto lMimeData = new QMimeData; + lMimeData->setUrls(QList { lTempFile }); + lMimeData->setData(QStringLiteral("application/x-kde-suggestedfilename"), QFile::encodeName(lTempFile.fileName())); - QDrag *dragHandler = new QDrag(this); - dragHandler->setMimeData(mimeData); - dragHandler->setPixmap(mExportManager->pixmap().scaled(256, 256, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation)); - dragHandler->exec(Qt::CopyAction); + auto lDragHandler = new QDrag(this); + lDragHandler->setMimeData(lMimeData); + lDragHandler->setPixmap(lExportManager->pixmap().scaled(256, 256, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation)); + lDragHandler->exec(Qt::CopyAction); } // Private -void SpectacleCore::initGui() +Platform::GrabMode SpectacleCore::toPlatformGrabMode(Spectacle::CaptureMode theCaptureMode) { - if (!isGuiInited) { - mMainWindow = new KSMainWindow(mImageGrabber->supportedModes(), mImageGrabber->onClickGrabSupported()); + 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(mPlatform->supportedGrabModes(), mPlatform->supportedShutterModes()); - connect(mMainWindow, &KSMainWindow::newScreenshotRequest, this, &SpectacleCore::takeNewScreenshot); - connect(mMainWindow, &KSMainWindow::dragAndDropRequest, this, &SpectacleCore::doStartDragAndDrop); + connect(mMainWindow.get(), &KSMainWindow::newScreenshotRequest, this, &SpectacleCore::takeNewScreenshot); + connect(mMainWindow.get(), &KSMainWindow::dragAndDropRequest, this, &SpectacleCore::doStartDragAndDrop); - isGuiInited = true; - QMetaObject::invokeMethod(mImageGrabber, "doImageGrab", Qt::QueuedConnection); + mIsGuiInited = true; + + auto lShutterMode = mPlatform->supportedShutterModes().testFlag(Platform::ShutterMode::Immediate) ? Platform::ShutterMode::Immediate : Platform::ShutterMode::OnClick; + auto lGrabMode = toPlatformGrabMode(ExportManager::instance()->captureMode()); + + QTimer::singleShot(0, [this, lShutterMode, lGrabMode, theIncludePointer, theIncludeDecorations]() { + mPlatform->doGrab(lShutterMode, lGrabMode, theIncludePointer, theIncludeDecorations); + }); } } diff --git a/src/SpectacleCore.h b/src/SpectacleCore.h index 7601f76..e4f7579 100644 --- a/src/SpectacleCore.h +++ b/src/SpectacleCore.h @@ -1,87 +1,88 @@ -/* - * Copyright (C) 2015 Boudhayan Gupta +/* This file is part of Spectacle, the KDE screenshot utility + * Copyright (C) 2019 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 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. + * 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. + * 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. + * + * SPDX-License-Identifier: LGPL-2.0-or-later */ -#ifndef KSCORE_H -#define KSCORE_H +#pragma once #include #include "ExportManager.h" #include "Gui/KSMainWindow.h" -#include "PlatformBackends/ImageGrabber.h" +#include "Platforms/PlatformLoader.h" + +#include +using MainWindowPtr = std::unique_ptr; -class SpectacleCore : public QObject +class SpectacleCore: public QObject { Q_OBJECT - Q_PROPERTY(QString filename READ filename WRITE setFilename NOTIFY filenameChanged) - Q_PROPERTY(ImageGrabber::GrabMode grabMode READ grabMode WRITE setGrabMode NOTIFY grabModeChanged) - public: - enum StartMode { - GuiMode = 0, - DBusMode = 1, - BackgroundMode = 2 + enum class StartMode { + Gui = 0, + DBus = 1, + Background = 2 }; - explicit SpectacleCore(StartMode startMode, ImageGrabber::GrabMode grabMode, QString &saveFileName, - qint64 delayMsec, bool notifyOnGrab, bool copyToClipboard, QObject *parent = nullptr); - ~SpectacleCore(); + explicit SpectacleCore(StartMode theStartMode, + Spectacle::CaptureMode theCaptureMode, + QString &theSaveFileName, + qint64 theDelayMsec, + bool theNotifyOnGrab, + bool theCopyToClipboard, + QObject *parent = nullptr); + virtual ~SpectacleCore() = default; QString filename() const; void setFilename(const QString &filename); - ImageGrabber::GrabMode grabMode() const; - void setGrabMode(ImageGrabber::GrabMode grabMode); Q_SIGNALS: void errorMessage(const QString &errString); void allDone(); void filenameChanged(const QString &filename); - void grabModeChanged(ImageGrabber::GrabMode mode); void grabFailed(); public Q_SLOTS: - void takeNewScreenshot(const ImageGrabber::GrabMode &mode, const int &timeout, const bool &includePointer, const bool &includeDecorations); - void showErrorMessage(const QString &errString); - void screenshotUpdated(const QPixmap &pixmap); + void takeNewScreenshot(Spectacle::CaptureMode theCaptureMode, int theTimeout, bool theIncludePointer, bool theIncludeDecorations); + void showErrorMessage(const QString &theErrString); + void screenshotUpdated(const QPixmap &thePixmap); void screenshotFailed(); void dbusStartAgent(); void doStartDragAndDrop(); - void doNotify(const QUrl &savedAt); + void doNotify(const QUrl &theSavedAt); void doCopyPath(const QUrl &savedAt); private: - void initGui(); + void initGui(bool theIncludePointer, bool theIncludeDecorations); + Platform::GrabMode toPlatformGrabMode(Spectacle::CaptureMode theCaptureMode); - ExportManager *mExportManager; StartMode mStartMode; bool mNotify; QString mFileNameString; QUrl mFileNameUrl; - ImageGrabber *mImageGrabber; - KSMainWindow *mMainWindow; - bool isGuiInited; - bool copyToClipboard; + PlatformPtr mPlatform; + MainWindowPtr mMainWindow; + bool mIsGuiInited; + bool mCopyToClipboard; }; - -#endif // KSCORE_H diff --git a/src/SpectacleDBusAdapter.cpp b/src/SpectacleDBusAdapter.cpp index 131664a..f847c7a 100644 --- a/src/SpectacleDBusAdapter.cpp +++ b/src/SpectacleDBusAdapter.cpp @@ -1,64 +1,64 @@ -/* - * Copyright (C) 2015 Boudhayan Gupta +/* This file is part of Spectacle, the KDE screenshot utility + * 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 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. + * 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. + * 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. + * + * SPDX-License-Identifier: LGPL-2.0-or-later */ #include "SpectacleDBusAdapter.h" +#include "SpectacleCommon.h" SpectacleDBusAdapter::SpectacleDBusAdapter(SpectacleCore *parent) : QDBusAbstractAdaptor(parent) { setAutoRelaySignals(false); } -SpectacleDBusAdapter::~SpectacleDBusAdapter() -{} - inline SpectacleCore *SpectacleDBusAdapter::parent() const { return static_cast(QObject::parent()); } Q_NOREPLY void SpectacleDBusAdapter::StartAgent() { parent()->dbusStartAgent(); } Q_NOREPLY void SpectacleDBusAdapter::FullScreen(bool includeMousePointer) { - parent()->takeNewScreenshot(ImageGrabber::FullScreen, 0, includeMousePointer, true); + parent()->takeNewScreenshot(Spectacle::CaptureMode::AllScreens, 0, includeMousePointer, true); } Q_NOREPLY void SpectacleDBusAdapter::CurrentScreen(bool includeMousePointer) { - parent()->takeNewScreenshot(ImageGrabber::CurrentScreen, 0, includeMousePointer, true); + parent()->takeNewScreenshot(Spectacle::CaptureMode::CurrentScreen, 0, includeMousePointer, true); } Q_NOREPLY void SpectacleDBusAdapter::ActiveWindow(bool includeWindowDecorations, bool includeMousePointer) { - parent()->takeNewScreenshot(ImageGrabber::ActiveWindow, 0, includeMousePointer, includeWindowDecorations); + parent()->takeNewScreenshot(Spectacle::CaptureMode::ActiveWindow, 0, includeMousePointer, includeWindowDecorations); } Q_NOREPLY void SpectacleDBusAdapter::WindowUnderCursor(bool includeWindowDecorations, bool includeMousePointer) { - parent()->takeNewScreenshot(ImageGrabber::WindowUnderCursor, 0, includeMousePointer, includeWindowDecorations); + parent()->takeNewScreenshot(Spectacle::CaptureMode::WindowUnderCursor, 0, includeMousePointer, includeWindowDecorations); } Q_NOREPLY void SpectacleDBusAdapter::RectangularRegion(bool includeMousePointer) { - parent()->takeNewScreenshot(ImageGrabber::RectangularRegion, 0, includeMousePointer, false); + parent()->takeNewScreenshot(Spectacle::CaptureMode::RectangularRegion, 0, includeMousePointer, false); } diff --git a/src/SpectacleDBusAdapter.h b/src/SpectacleDBusAdapter.h index 0d5ad39..f4e2dbd 100644 --- a/src/SpectacleDBusAdapter.h +++ b/src/SpectacleDBusAdapter.h @@ -1,82 +1,81 @@ -/* - * Copyright (C) 2015 Boudhayan Gupta +/* This file is part of Spectacle, the KDE screenshot utility + * 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 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. + * 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. + * 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. + * + * SPDX-License-Identifier: LGPL-2.0-or-later */ -#ifndef SPECTACLEDBUSADAPTER_H -#define SPECTACLEDBUSADAPTER_H +#pragma once #include #include "SpectacleCore.h" class SpectacleDBusAdapter: public QDBusAbstractAdaptor { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.Spectacle") Q_CLASSINFO("D-Bus Introspection", "" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "" ) public: SpectacleDBusAdapter(SpectacleCore *parent); - virtual ~SpectacleDBusAdapter(); + virtual ~SpectacleDBusAdapter() = default; inline SpectacleCore *parent() const; public Q_SLOTS: Q_NOREPLY void StartAgent(); Q_NOREPLY void FullScreen(bool includeMousePointer); Q_NOREPLY void CurrentScreen(bool includeMousePointer); Q_NOREPLY void ActiveWindow(bool includeWindowDecorations, bool includeMousePointer); Q_NOREPLY void WindowUnderCursor(bool includeWindowDecorations, bool includeMousePointer); Q_NOREPLY void RectangularRegion(bool includeMousePointer); Q_SIGNALS: void ScreenshotTaken(const QString &fileName); void ScreenshotFailed(); }; - -#endif // SPECTACLEDBUSADAPTER_H