diff --git a/CMakeLists.txt b/CMakeLists.txt index bb0b97e..3631511 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,86 +1,88 @@ # Spectacle project project(Spectacle) # KDE Application Version, managed by release script set(KDE_APPLICATIONS_VERSION_MAJOR "15") set(KDE_APPLICATIONS_VERSION_MINOR "08") set(KDE_APPLICATIONS_VERSION_MICRO "999") 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 2.8.12 FATAL_ERROR) set(QT_MIN_VERSION "5.4.0") set(KF5_MIN_VERSION "5.6.0") set(PLASMA_MIN_VERSION "5.2.0") find_package(ECM 1.2.0 REQUIRED NO_MODULE) set( CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR} ) # set up kf5 include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings) include(ECMInstallIcons) include(ECMSetupVersion) include(FeatureSummary) find_package( Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED Core Widgets DBus PrintSupport ) find_package( KF5 ${KF5_MIN_VERSION} REQUIRED CoreAddons + WidgetsAddons + DBusAddons Notifications Config I18n KIO XmlGui - WidgetsAddons WindowSystem DocTools ) # optional components find_package(KF5Kipi) if (KF5Kipi_FOUND) set(KIPI_FOUND 1) endif () find_package(XCB COMPONENTS XFIXES IMAGE UTIL CURSOR) if (XCB_FOUND) find_package(Qt5X11Extras ${QT_MIN_VERSION} REQUIRED) find_package(KF5Screen ${PLASMA_MIN_VERSION} REQUIRED) endif() # fail build if none of the platform backends can be found if (!XCB_FOUND) message(FATAL_ERROR "No suitable backend platform was found. Currenty supported platforms are: XCB") endif() # hand off to subdirectories add_subdirectory(src) +add_subdirectory(dbus) add_subdirectory(desktop) add_subdirectory(icons) add_subdirectory(doc) # summaries feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/dbus/CMakeLists.txt b/dbus/CMakeLists.txt new file mode 100644 index 0000000..70112f8 --- /dev/null +++ b/dbus/CMakeLists.txt @@ -0,0 +1,6 @@ +# install the DBus service and interface files in the correct place + +configure_file(org.freedesktop.Screenshot.service.in ${CMAKE_CURRENT_BINARY_DIR}/org.freedesktop.Screenshot.service) + +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.freedesktop.Screenshot.service DESTINATION ${DBUS_SERVICES_INSTALL_DIR}) +install(FILES org.freedesktop.Screenshot.xml DESTINATION ${DBUS_INTERFACES_INSTALL_DIR}) diff --git a/dbus/org.freedesktop.Screenshot.service.in b/dbus/org.freedesktop.Screenshot.service.in new file mode 100644 index 0000000..38031f6 --- /dev/null +++ b/dbus/org.freedesktop.Screenshot.service.in @@ -0,0 +1,3 @@ +[D-BUS Service] +Name=org.freedesktop.Screenshot +Exec=@CMAKE_INSTALL_PREFIX@/bin/spectacle diff --git a/dbus/org.freedesktop.Screenshot.xml b/dbus/org.freedesktop.Screenshot.xml new file mode 100644 index 0000000..0d90616 --- /dev/null +++ b/dbus/org.freedesktop.Screenshot.xml @@ -0,0 +1,107 @@ + + + + + + + + + + Starts the screenshot agent (the application for taking screenshots). + + + + + + + + Whether to include an image of the mouse pointer. + + + + + Takes a full-screen screenshot. + If the agent was activated using D-Bus, it takes a screenshot in the background without spawning the GUI and exits after the shot has been taken. + + + + + + + + Whether to include an image of the mouse pointer. + + + + + Takes a screenshot of the current screen. + If the agent was activated using D-Bus, it takes a screenshot in the background without spawning the GUI and exits after the shot has been taken. + + + + + + + + Whether to include the window titlebars and frames. + + + + + Whether to include an image of the mouse pointer. + + + + + Takes a screenshot of the window that currently has window focus. + If the agent was activated using D-Bus, it takes a screenshot in the background without spawning the GUI and exits after the shot has been taken. + + + + + + + + Whether to include the window titlebars and frames. + + + + + Whether to include an image of the mouse pointer. + + + + + Takes a screenshot of the window that is currently under the mouse cursor. + If the agent was activated using D-Bus, it takes a screenshot in the background without spawning the GUI and exits after the shot has been taken. + + + + + + + + The file name with which the screenshot was saved. + + + + + Emitted if the screenshot was successfully taken. + + + + + + + Emitted if the screenshot capture failed. + + + + + diff --git a/desktop/org.kde.spectacle.desktop b/desktop/org.kde.spectacle.desktop index 00777c7..d4d9c42 100644 --- a/desktop/org.kde.spectacle.desktop +++ b/desktop/org.kde.spectacle.desktop @@ -1,28 +1,30 @@ [Desktop Entry] GenericName=Screenshot Capture Utility GenericName[bg]=Инструмент за заснемане на екрана GenericName[ca]=Utilitat de captura de pantalla GenericName[cs]=Nástroj na snímání obrazovky GenericName[de]=Dienstprogramm für Bildschirmfotos GenericName[en_GB]=Screenshot Capture Utility GenericName[es]=Utilidad de capturas de pantalla GenericName[fi]=Kuvankaappausohjelma GenericName[gl]=Utilidade para facer capturas de pantalla GenericName[nl]=Hulpmiddel voor het vangen van een schermafdruk GenericName[pl]=Narzędzie do przechwytywania ekranu GenericName[pt]=Utilitário de Captura de Imagens GenericName[pt_BR]=Utilitário de captura de tela GenericName[ro]=Utilitar de capturare a ecranului GenericName[sk]=Nástroj na snímanie obrazovky GenericName[sv]=Verktyg för att ta skärmbilder GenericName[uk]=Програма для створення знімків екрана GenericName[x-test]=xxScreenshot Capture Utilityxx GenericName[zh_CN]=屏幕截图工具 Name=Spectacle Categories=Qt;KDE;Utility; MimeType= Exec=spectacle Icon=spectacle Type=Application Terminal=false StartupNotify=false +X-DBUS-StartupType=Multi +X-DBUS-ServiceName=org.freedesktop.Screenshot diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0dcac08..5191910 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,85 +1,87 @@ # common - configure file and version definitions configure_file(Config.h.in ${CMAKE_CURRENT_BINARY_DIR}/Config.h) # target set( SPECTACLE_SRCS_DEFAULT Main.cpp KSCore.cpp + SpectacleDBusAdapter.cpp ScreenClipper.cpp PlatformBackends/ImageGrabber.cpp PlatformBackends/DummyImageGrabber.cpp Gui/KSMainWindow.cpp Gui/KSWidget.cpp Gui/KSImageWidget.cpp Gui/KSSaveConfigDialog.cpp Gui/KSSendToMenu.cpp ) if(XCB_FOUND) set( SPECTACLE_SRCS_X11 PlatformBackends/X11ImageGrabber.cpp ) endif() if(KF5Kipi_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::ConfigGui + KF5::ConfigCore KF5::I18n KF5::KIOWidgets KF5::WindowSystem KF5::XmlGui - KF5::WidgetsAddons ) if(XCB_FOUND) target_link_libraries( spectacle XCB::XFIXES XCB::IMAGE XCB::CURSOR XCB::UTIL Qt5::X11Extras KF5::Screen ) endif() if(KF5Kipi_FOUND) target_link_libraries ( spectacle KF5::Kipi ) endif() install(TARGETS spectacle ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/src/KSCore.cpp b/src/KSCore.cpp index efe5aac..d19881a 100644 --- a/src/KSCore.cpp +++ b/src/KSCore.cpp @@ -1,569 +1,597 @@ /* * Copyright (C) 2015 Boudhayan Gupta * * Includes code from ksnapshot.cpp, part of KSnapshot. Copyright notices * reproduced below: * * Copyright (C) 1997-2008 Richard J. Moore * Copyright (C) 2000 Matthias Ettrich * Copyright (C) 2002 Aaron J. Seigo * Copyright (C) 2003 Nadeem Hasan * Copyright (C) 2004 Bernd Brandstetter * Copyright (C) 2006 Urs Wolfer * Copyright (C) 2010 Martin Gräßlin * Copyright (C) 2010, 2011 Pau Garcia i Quiles * * 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 "KSCore.h" -KSCore::KSCore(bool backgroundMode, ImageGrabber::GrabMode grabMode, QString &saveFileName, +KSCore::KSCore(StartMode startMode, ImageGrabber::GrabMode grabMode, QString &saveFileName, qint64 delayMsec, bool sendToClipboard, bool notifyOnGrab, QObject *parent) : QObject(parent), - mBackgroundMode(backgroundMode), + mStartMode(startMode), mNotify(notifyOnGrab), mOverwriteOnSave(true), mBackgroundSendToClipboard(sendToClipboard), mLocalPixmap(QPixmap()), mImageGrabber(nullptr), - mMainWindow(nullptr) + mMainWindow(nullptr), + isGuiInited(false) { KSharedConfigPtr config = KSharedConfig::openConfig("spectaclerc"); KConfigGroup guiConfig(config, "GuiConfig"); if (!(saveFileName.isEmpty() || saveFileName.isNull())) { if (QDir::isRelativePath(saveFileName)) { saveFileName = QDir::current().absoluteFilePath(saveFileName); } setFilename(saveFileName); } #ifdef XCB_FOUND if (qApp->platformName() == QStringLiteral("xcb")) { mImageGrabber = new X11ImageGrabber; } #endif if (!mImageGrabber) { mImageGrabber = new DummyImageGrabber; } mImageGrabber->setGrabMode(grabMode); mImageGrabber->setCapturePointer(guiConfig.readEntry("includePointer", true)); mImageGrabber->setCaptureDecorations(guiConfig.readEntry("includeDecorations", true)); if ((!(mImageGrabber->onClickGrabSupported())) && (delayMsec < 0)) { delayMsec = 0; } connect(this, &KSCore::errorMessage, this, &KSCore::showErrorMessage); connect(mImageGrabber, &ImageGrabber::pixmapChanged, this, &KSCore::screenshotUpdated); connect(mImageGrabber, &ImageGrabber::imageGrabFailed, this, &KSCore::screenshotFailed); - if (backgroundMode) { - int msec = (KWindowSystem::compositingActive() ? 200 : 50) + delayMsec; - QTimer::singleShot(msec, mImageGrabber, &ImageGrabber::doImageGrab); - return; + switch (startMode) { + case DBusMode: + break; + case BackgroundMode: { + int msec = (KWindowSystem::compositingActive() ? 200 : 50) + delayMsec; + QTimer::singleShot(msec, mImageGrabber, &ImageGrabber::doImageGrab); + } + break; + case GuiMode: + initGui(); + break; } - - // if we aren't in background mode, this would be a good time to - // init the gui - - mMainWindow = new KSMainWindow(mImageGrabber->onClickGrabSupported()); - - connect(mMainWindow, &KSMainWindow::newScreenshotRequest, this, &KSCore::takeNewScreenshot); - connect(mMainWindow, &KSMainWindow::save, this, &KSCore::doGuiSave); - connect(mMainWindow, &KSMainWindow::saveAndExit, this, &KSCore::doAutoSave); - connect(mMainWindow, &KSMainWindow::saveAsClicked, this, &KSCore::doGuiSaveAs); - connect(mMainWindow, &KSMainWindow::sendToKServiceRequest, this, &KSCore::doSendToService); - connect(mMainWindow, &KSMainWindow::sendToOpenWithRequest, this, &KSCore::doSendToOpenWith); - connect(mMainWindow, &KSMainWindow::sendToClipboardRequest, this, &KSCore::doSendToClipboard); - connect(mMainWindow, &KSMainWindow::dragAndDropRequest, this, &KSCore::doStartDragAndDrop); - connect(mMainWindow, &KSMainWindow::printRequest, this, &KSCore::doPrint); - - connect(this, &KSCore::imageSaved, mMainWindow, &KSMainWindow::setScreenshotWindowTitle); - QMetaObject::invokeMethod(mImageGrabber, "doImageGrab", Qt::QueuedConnection); } KSCore::~KSCore() { if (mMainWindow) { delete mMainWindow; } } // Q_PROPERTY stuff QString KSCore::filename() const { return mFileNameString; } void KSCore::setFilename(const QString &filename) { mFileNameString = filename; mFileNameUrl = QUrl::fromUserInput(filename); } ImageGrabber::GrabMode KSCore::grabMode() const { return mImageGrabber->grabMode(); } void KSCore::setGrabMode(const ImageGrabber::GrabMode &grabMode) { mImageGrabber->setGrabMode(grabMode); } bool KSCore::overwriteOnSave() const { return mOverwriteOnSave; } void KSCore::setOverwriteOnSave(const bool &overwrite) { mOverwriteOnSave = overwrite; } QString KSCore::saveLocation() const { KSharedConfigPtr config = KSharedConfig::openConfig("spectaclerc"); KConfigGroup generalConfig = KConfigGroup(config, "General"); QString savePath = generalConfig.readPathEntry( "default-save-location", QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); if (savePath.isEmpty() || savePath.isNull()) { savePath = QDir::homePath(); } savePath = QDir::cleanPath(savePath); QDir savePathDir(savePath); if (!(savePathDir.exists())) { savePathDir.mkpath("."); generalConfig.writePathEntry("last-saved-to", savePath); } return savePath; } void KSCore::setSaveLocation(const QString &savePath) { KSharedConfigPtr config = KSharedConfig::openConfig("spectaclerc"); KConfigGroup generalConfig = KConfigGroup(config, "General"); generalConfig.writePathEntry("last-saved-to", savePath); } // Slots +void KSCore::dbusStartAgent() +{ + if (!(mStartMode == GuiMode)) { + mStartMode = GuiMode; + return initGui(); + } +} + void KSCore::takeNewScreenshot(const ImageGrabber::GrabMode &mode, const int &timeout, const bool &includePointer, const bool &includeDecorations) { mImageGrabber->setGrabMode(mode); mImageGrabber->setCapturePointer(includePointer); mImageGrabber->setCaptureDecorations(includeDecorations); if (timeout < 0) { mImageGrabber->doOnClickGrab(); return; } // when compositing is enabled, we need to give it enough time for the window // to disappear and all the effects are complete before we take the shot. there's // no way of knowing how long the disappearing effects take, but as per default // settings (and unless the user has set an extremely slow effect), 200 // milliseconds is a good amount of wait time. const int msec = KWindowSystem::compositingActive() ? 200 : 50; QTimer::singleShot(timeout + msec, mImageGrabber, &ImageGrabber::doImageGrab); } void KSCore::showErrorMessage(const QString &errString) { qDebug() << "ERROR: " << errString; - if (!mBackgroundMode) { + if (mStartMode == GuiMode) { KMessageBox::error(0, errString); } } void KSCore::screenshotUpdated(const QPixmap &pixmap) { mLocalPixmap = pixmap; - if (mBackgroundMode) { + switch (mStartMode) { + case BackgroundMode: if (mBackgroundSendToClipboard) { qApp->clipboard()->setPixmap(pixmap); qDebug() << i18n("Copied image to clipboard"); - } else { - doAutoSave(); } - } else { + case DBusMode: + doAutoSave(); + break; + case GuiMode: mMainWindow->setScreenshotAndShow(pixmap); tempFileSave(); } } void KSCore::screenshotFailed() { - if (mBackgroundMode) { - qDebug() << i18n("Screenshot capture canceled or failed"); + switch (mStartMode) { + case BackgroundMode: + showErrorMessage(i18n("Screenshot capture canceled or failed")); + case DBusMode: + emit grabFailed(); emit allDone(); return; + case GuiMode: + mMainWindow->show(); } - - mMainWindow->show(); } void KSCore::doGuiSave() { if (mLocalPixmap.isNull()) { emit errorMessage(i18n("Cannot save an empty screenshot image.")); return; } QUrl savePath = getAutosaveFilename(); if (doSave(savePath)) { emit imageSaved(savePath); + emit imageSaved(savePath.toLocalFile()); } } void KSCore::doAutoSave() { if (mLocalPixmap.isNull()) { emit errorMessage(i18n("Cannot save an empty screenshot image.")); return; } QUrl savePath; - if (mBackgroundMode && mFileNameUrl.isValid() && mFileNameUrl.isLocalFile()) { + if (mStartMode == BackgroundMode && mFileNameUrl.isValid() && mFileNameUrl.isLocalFile()) { savePath = mFileNameUrl; } else { savePath = getAutosaveFilename(); } if (doSave(savePath)) { QDir dir(savePath.path()); dir.cdUp(); setSaveLocation(dir.absolutePath()); - if (mBackgroundMode && mNotify) { + if ((mStartMode == BackgroundMode || mStartMode == DBusMode) && mNotify) { KNotification *notify = new KNotification("newScreenshotSaved"); notify->setText(i18n("A new screenshot was captured and saved to %1", savePath.toLocalFile())); notify->setPixmap(QIcon::fromTheme("spectacle").pixmap(QSize(32, 32))); notify->sendEvent(); // unfortunately we can't quit just yet, emitting allDone right away // quits the application before the notification DBus message gets sent. // a token timeout seems to fix this though. Any better ideas? QTimer::singleShot(50, this, &KSCore::allDone); } else { emit allDone(); } return; } } void KSCore::doStartDragAndDrop() { QMimeData *mimeData = new QMimeData; mimeData->setUrls(QList { getTempSaveFilename() }); mimeData->setImageData(mLocalPixmap); mimeData->setData("application/x-kde-suggestedfilename", QFile::encodeName(makeAutosaveFilename() + ".png")); QDrag *dragHandler = new QDrag(this); dragHandler->setMimeData(mimeData); dragHandler->setPixmap(mLocalPixmap.scaled(256, 256, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation)); dragHandler->start(); dragHandler->deleteLater(); } void KSCore::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 = mLocalPixmap.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; } void KSCore::doGuiSaveAs() { QString selectedFilter; QStringList supportedFilters; QMimeDatabase db; const QUrl autoSavePath = getAutosaveFilename(); const QMimeType mimeTypeForFilename = db.mimeTypeForUrl(autoSavePath); for (auto mimeTypeName: QImageWriter::supportedMimeTypes()) { QMimeType mimetype = db.mimeTypeForName(mimeTypeName); if (mimetype.preferredSuffix() != "") { QString filterString = mimetype.comment() + " (*." + mimetype.preferredSuffix() + ")"; qDebug() << filterString; supportedFilters.append(filterString); if (mimetype == mimeTypeForFilename) { selectedFilter = supportedFilters.last(); } } } QFileDialog dialog(mMainWindow); dialog.setAcceptMode(QFileDialog::AcceptSave); dialog.setFileMode(QFileDialog::AnyFile); dialog.setNameFilters(supportedFilters); dialog.selectNameFilter(selectedFilter); dialog.setDirectoryUrl(autoSavePath); if (dialog.exec() == QFileDialog::Accepted) { const QUrl saveUrl = dialog.selectedUrls().first(); if (saveUrl.isValid()) { if (doSave(saveUrl)) { emit imageSaved(saveUrl); + emit imageSaved(saveUrl.toLocalFile()); } } } } void KSCore::doSendToService(KService::Ptr service) { QUrl tempFile; QList tempFileList; tempFile = getTempSaveFilename(); if (!tempFile.isValid()) { emit errorMessage(i18n("Cannot send screenshot to the application")); return; } tempFileList.append(tempFile); KRun::runService(*service, tempFileList, mMainWindow, true); } void KSCore::doSendToOpenWith() { QUrl tempFile; QList tempFileList; tempFile = getTempSaveFilename(); if (!tempFile.isValid()) { emit errorMessage(i18n("Cannot send screenshot to the application")); return; } tempFileList.append(tempFile); KRun::displayOpenWithDialog(tempFileList, mMainWindow, true); } void KSCore::doSendToClipboard() { QApplication::clipboard()->setPixmap(mLocalPixmap); } // Private +void KSCore::initGui() +{ + if (!isGuiInited) { + mMainWindow = new KSMainWindow(mImageGrabber->onClickGrabSupported()); + + connect(mMainWindow, &KSMainWindow::newScreenshotRequest, this, &KSCore::takeNewScreenshot); + connect(mMainWindow, &KSMainWindow::save, this, &KSCore::doGuiSave); + connect(mMainWindow, &KSMainWindow::saveAndExit, this, &KSCore::doAutoSave); + connect(mMainWindow, &KSMainWindow::saveAsClicked, this, &KSCore::doGuiSaveAs); + connect(mMainWindow, &KSMainWindow::sendToKServiceRequest, this, &KSCore::doSendToService); + connect(mMainWindow, &KSMainWindow::sendToOpenWithRequest, this, &KSCore::doSendToOpenWith); + connect(mMainWindow, &KSMainWindow::sendToClipboardRequest, this, &KSCore::doSendToClipboard); + connect(mMainWindow, &KSMainWindow::dragAndDropRequest, this, &KSCore::doStartDragAndDrop); + connect(mMainWindow, &KSMainWindow::printRequest, this, &KSCore::doPrint); + + connect(this, static_cast(&KSCore::imageSaved), + mMainWindow, &KSMainWindow::setScreenshotWindowTitle); + + isGuiInited = true; + QMetaObject::invokeMethod(mImageGrabber, "doImageGrab", Qt::QueuedConnection); + } +} + QUrl KSCore::getAutosaveFilename() { const QString baseDir = saveLocation(); const QDir baseDirPath(baseDir); const QString filename = makeAutosaveFilename(); const QString fullpath = autoIncrementFilename(baseDirPath.filePath(filename), "png"); const QUrl fileNameUrl = QUrl::fromUserInput(fullpath); if (fileNameUrl.isValid()) { return fileNameUrl; } else { return QUrl(); } } QString KSCore::makeAutosaveFilename() { KSharedConfigPtr config = KSharedConfig::openConfig("spectaclerc"); KConfigGroup generalConfig = KConfigGroup(config, "General"); const QDateTime timestamp = QDateTime::currentDateTime(); QString baseName = generalConfig.readEntry("save-filename-format", "Screenshot_%Y%M%D_%H%m%S"); return baseName.replace("%Y", timestamp.toString("yyyy")) .replace("%y", timestamp.toString("yy")) .replace("%M", timestamp.toString("MM")) .replace("%D", timestamp.toString("dd")) .replace("%H", timestamp.toString("hh")) .replace("%m", timestamp.toString("mm")) .replace("%S", timestamp.toString("ss")); } QString KSCore::autoIncrementFilename(const QString &baseName, const QString &extension) { if (!(isFileExists(QUrl::fromUserInput(baseName + '.' + extension)))) { return baseName + '.' + extension; } QString fileNameFmt(baseName + "-%1." + extension); for (quint64 i = 1; i < std::numeric_limits::max(); i++) { if (!(isFileExists(QUrl::fromUserInput(fileNameFmt.arg(i))))) { return fileNameFmt.arg(i); } } // unlikely this will ever happen, but just in case we've run // out of numbers return fileNameFmt.arg("OVERFLOW-" + (qrand() % 10000)); } QString KSCore::makeSaveMimetype(const QUrl &url) { QMimeDatabase mimedb; QString type = mimedb.mimeTypeForUrl(url).preferredSuffix(); if (type.isEmpty()) { return QString("png"); } return type; } bool KSCore::writeImage(QIODevice *device, const QByteArray &format) { QImageWriter imageWriter(device, format); if (!(imageWriter.canWrite())) { emit errorMessage(i18n("QImageWriter cannot write image: ") + imageWriter.errorString()); return false; } return imageWriter.write(mLocalPixmap.toImage()); } bool KSCore::localSave(const QUrl &url, const QString &mimetype) { 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 KSCore::remoteSave(const QUrl &url, const QString &mimetype) { 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 KSCore::getTempSaveFilename() const { QDir tempDir = QDir::temp(); return QUrl::fromLocalFile(tempDir.absoluteFilePath("KSTempScreenshot.png")); } bool KSCore::tempFileSave() { if (!(mLocalPixmap.isNull())) { const QUrl savePath = getTempSaveFilename(); if (localSave(savePath, "png")) { return QFile::setPermissions(savePath.toLocalFile(), QFile::ReadUser | QFile::WriteUser); } } return false; } QUrl KSCore::tempFileSave(const QString &mimetype) { QTemporaryFile tmpFile; tmpFile.setAutoRemove(false); if (tmpFile.open()) { if(!writeImage(&tmpFile, mimetype.toLatin1())) { emit errorMessage(i18n("Cannot save screenshot. Error while writing temporary local file.")); return QUrl(); } return QUrl::fromLocalFile(tmpFile.fileName()); } return QUrl(); } bool KSCore::doSave(const QUrl &url) { if (!(url.isValid())) { emit errorMessage(i18n("Cannot save screenshot. The save filename is invalid.")); return false; } if (isFileExists(url) && (mOverwriteOnSave == false)) { emit errorMessage((i18n("Cannot save screenshot. The file already exists."))); return false; } QString mimetype = makeSaveMimetype(url); if (url.isLocalFile()) { return localSave(url, mimetype); } return remoteSave(url, mimetype); } bool KSCore::isFileExists(const QUrl &url) { if (!(url.isValid())) { return false; } KIO::StatJob * existsJob = KIO::stat(url, KIO::StatJob::DestinationSide, 0); existsJob->exec(); return (existsJob->error() == KJob::NoError); } diff --git a/src/KSCore.h b/src/KSCore.h index fad0cf5..d572d28 100644 --- a/src/KSCore.h +++ b/src/KSCore.h @@ -1,145 +1,155 @@ /* * 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 KSCORE_H #define KSCORE_H -#include - #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Config.h" #include "PlatformBackends/ImageGrabber.h" #include "PlatformBackends/DummyImageGrabber.h" #ifdef XCB_FOUND #include "PlatformBackends/X11ImageGrabber.h" #endif #include "Gui/KSMainWindow.h" class KSCore : public QObject { Q_OBJECT Q_PROPERTY(QString filename READ filename WRITE setFilename NOTIFY filenameChanged) Q_PROPERTY(bool overwriteOnSave READ overwriteOnSave WRITE setOverwriteOnSave NOTIFY overwriteOnSaveChanged) Q_PROPERTY(ImageGrabber::GrabMode grabMode READ grabMode WRITE setGrabMode NOTIFY grabModeChanged) Q_PROPERTY(QString saveLocation READ saveLocation WRITE setSaveLocation NOTIFY saveLocationChanged) public: - explicit KSCore(bool backgroundMode, ImageGrabber::GrabMode grabMode, QString &saveFileName, qint64 delayMsec, bool sendToClipboard, bool notifyOnGrab, QObject *parent = 0); + enum StartMode { + GuiMode = 0, + DBusMode = 1, + BackgroundMode = 2 + }; + + explicit KSCore(StartMode startMode, ImageGrabber::GrabMode grabMode, QString &saveFileName, + qint64 delayMsec, bool sendToClipboard, bool notifyOnGrab, QObject *parent = 0); ~KSCore(); QString filename() const; void setFilename(const QString &filename); ImageGrabber::GrabMode grabMode() const; void setGrabMode(const ImageGrabber::GrabMode &grabMode); bool overwriteOnSave() const; void setOverwriteOnSave(const bool &overwrite); QString saveLocation() const; void setSaveLocation(const QString &savePath); signals: void errorMessage(const QString errString); void allDone(); void filenameChanged(QString filename); void grabModeChanged(ImageGrabber::GrabMode mode); void overwriteOnSaveChanged(bool overwriteOnSave); void saveLocationChanged(QString savePath); void imageSaved(QUrl location); + void imageSaved(QString location); + void grabFailed(); public 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 screenshotFailed(); + void dbusStartAgent(); void doStartDragAndDrop(); void doPrint(QPrinter *printer); void doGuiSaveAs(); void doGuiSave(); void doAutoSave(); void doSendToService(KService::Ptr service); void doSendToOpenWith(); void doSendToClipboard(); private: + void initGui(); QUrl getAutosaveFilename(); QString makeAutosaveFilename(); QString autoIncrementFilename(const QString &baseName, const QString &extension); QString makeSaveMimetype(const QUrl &url); bool writeImage(QIODevice *device, const QByteArray &format); bool localSave(const QUrl &url, const QString &mimetype); bool remoteSave(const QUrl &url, const QString &mimetype); bool tempFileSave(); QUrl tempFileSave(const QString &mimetype); bool doSave(const QUrl &url); bool isFileExists(const QUrl &url); QUrl getTempSaveFilename() const; - bool mBackgroundMode; - bool mNotify; - bool mOverwriteOnSave; - bool mBackgroundSendToClipboard; - QPixmap mLocalPixmap; - QString mFileNameString; - QUrl mFileNameUrl; - ImageGrabber *mImageGrabber; - KSMainWindow *mMainWindow; + StartMode mStartMode; + bool mNotify; + bool mOverwriteOnSave; + bool mBackgroundSendToClipboard; + QPixmap mLocalPixmap; + QString mFileNameString; + QUrl mFileNameUrl; + ImageGrabber *mImageGrabber; + KSMainWindow *mMainWindow; + bool isGuiInited; }; #endif // KSCORE_H diff --git a/src/Main.cpp b/src/Main.cpp index 0384bc8..1118c6b 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -1,141 +1,167 @@ /* * 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 #include #include #include #include +#include #include #include #include +#include #include "KSCore.h" +#include "SpectacleDBusAdapter.h" #include "Config.h" int main(int argc, char **argv) { // set up the application QApplication app(argc, argv); app.setOrganizationDomain("kde.org"); app.setApplicationName("spectacle"); app.setWindowIcon(QIcon::fromTheme("spectacle")); app.setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true); app.setAttribute(Qt::AA_UseHighDpiPixmaps, true); // set up the about data KLocalizedString::setApplicationDomain("spectacle"); KAboutData aboutData("spectacle", i18n("Spectacle"), SPECTACLE_VERSION, i18n("KDE Screenshot Utility"), KAboutLicense::GPL_V2, i18n("(C) 2015 Boudhayan Gupta")); aboutData.addAuthor("Boudhayan Gupta", QString(), "bgupta@kde.org"); KAboutData::setApplicationData(aboutData); // set up the command line options parser QCommandLineParser parser; parser.addVersionOption(); parser.addHelpOption(); aboutData.setupCommandLine(&parser); parser.addOptions({ {{"f", "fullscreen"}, i18n("Capture the entire desktop (default)")}, {{"m", "current"}, i18n("Capture the current monitor")}, {{"a", "activewindow"}, i18n("Capture the active window")}, {{"u", "windowundercursor"}, i18n("Capture the window currently under the cursor, including parents of pop-up menus")}, {{"t", "transientonly"}, i18n("Capture the window currently under the cursor, excluding parents of pop-up menus")}, {{"r", "region"}, i18n("Capture a rectangular region of the screen")}, + {{"g", "gui"}, i18n("Start in GUI mode (default)")}, {{"b", "background"}, i18n("Take a screenshot and exit without showing the GUI")}, - {{"n", "notify"}, i18n("In background mode, pop up a notification when the screenshot is taken")}, + {{"s", "dbus"}, i18n("Start in DBus-Activation mode")}, + {{"n", "nonotify"}, i18n("In background mode, do not pop up a notification when the screenshot is taken")}, {{"c", "clipboard"}, i18n("In background mode, send image to clipboard without saving to file")}, {{"o", "output"}, i18n("In background mode, save image to specified file"), "fileName"}, {{"d", "delay"}, i18n("In background mode, delay before taking the shot (in milliseconds)"), "delayMsec"}, {{"w", "onclick"}, i18n("Wait for a click before taking screenshot. Invalidates delay")} }); parser.process(app); aboutData.processCommandLine(&parser); // extract the capture mode ImageGrabber::GrabMode grabMode = ImageGrabber::FullScreen; if (parser.isSet("current")) { grabMode = ImageGrabber::CurrentScreen; } else if (parser.isSet("activewindow")) { grabMode = ImageGrabber::ActiveWindow; } else if (parser.isSet("region")) { grabMode = ImageGrabber::RectangularRegion; } else if (parser.isSet("windowundercursor")) { grabMode = ImageGrabber::TransientWithParent; } else if (parser.isSet("transientonly")) { grabMode = ImageGrabber::WindowUnderCursor; } - // are we running in background mode? + // are we running in background or dbus mode? - bool backgroundMode = false; + KSCore::StartMode startMode = KSCore::GuiMode; bool sendToClipboard = false; - bool notify = false; + bool notify = true; qint64 delayMsec = 0; QString fileName = QString(); if (parser.isSet("background")) { - backgroundMode = true; - app.setQuitOnLastWindowClosed(false); + startMode = KSCore::BackgroundMode; + } else if (parser.isSet("dbus")) { + startMode = KSCore::DBusMode; + } - if (parser.isSet("notify")) { - notify = true; + switch (startMode) { + case KSCore::BackgroundMode: + if (parser.isSet("nonotify")) { + notify = false; } if (parser.isSet("output")) { fileName = parser.value("output"); } if (parser.isSet("delay")) { bool ok = false; qint64 delayValue = parser.value("delay").toLongLong(&ok); if (ok) { delayMsec = delayValue; } } if (parser.isSet("onclick")) { delayMsec = -1; } if (parser.isSet("clipboard")) { sendToClipboard = true; } + case KSCore::DBusMode: + app.setQuitOnLastWindowClosed(false); + case KSCore::GuiMode: + break; } // release the kraken - KSCore genie(backgroundMode, grabMode, fileName, delayMsec, sendToClipboard, notify); + KSCore genie(startMode, grabMode, fileName, delayMsec, sendToClipboard, notify); QObject::connect(&genie, &KSCore::allDone, qApp, &QApplication::quit); + // create the dbus connections + + new KDBusService(KDBusService::Multiple, &genie); + + SpectacleDBusAdapter *dbusAdapter = new SpectacleDBusAdapter(&genie); + QObject::connect(&genie, static_cast(&KSCore::imageSaved), dbusAdapter, &SpectacleDBusAdapter::ScreenshotTaken); + QObject::connect(&genie, &KSCore::grabFailed, dbusAdapter, &SpectacleDBusAdapter::ScreenshotFailed); + + QDBusConnection::sessionBus().registerObject("/", &genie); + QDBusConnection::sessionBus().registerService("org.freedesktop.Screenshot"); + + // fire it up + return app.exec(); } diff --git a/src/SpectacleDBusAdapter.cpp b/src/SpectacleDBusAdapter.cpp new file mode 100644 index 0000000..e47b0e2 --- /dev/null +++ b/src/SpectacleDBusAdapter.cpp @@ -0,0 +1,60 @@ +/* + * 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 "SpectacleDBusAdapter.h" + +SpectacleDBusAdapter::SpectacleDBusAdapter(KSCore *parent) + : QDBusAbstractAdaptor(parent) +{ + setAutoRelaySignals(false); +} + +SpectacleDBusAdapter::~SpectacleDBusAdapter() +{} + +inline KSCore * 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); +} + +Q_NOREPLY void SpectacleDBusAdapter::CurrentScreen(bool includeMousePointer) +{ + parent()->takeNewScreenshot(ImageGrabber::CurrentScreen, 0, includeMousePointer, true); +} + +Q_NOREPLY void SpectacleDBusAdapter::ActiveWindow(bool includeWindowDecorations, bool includeMousePointer) +{ + parent()->takeNewScreenshot(ImageGrabber::ActiveWindow, 0, includeMousePointer, includeWindowDecorations); +} + +Q_NOREPLY void SpectacleDBusAdapter::WindowUnderCursor(bool includeWindowDecorations, bool includeMousePointer) +{ + parent()->takeNewScreenshot(ImageGrabber::WindowUnderCursor, 0, includeMousePointer, includeWindowDecorations); +} + diff --git a/src/SpectacleDBusAdapter.h b/src/SpectacleDBusAdapter.h new file mode 100644 index 0000000..141818f --- /dev/null +++ b/src/SpectacleDBusAdapter.h @@ -0,0 +1,78 @@ +/* + * 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 SPECTACLEDBUSADAPTER_H +#define SPECTACLEDBUSADAPTER_H + +#include +#include "KSCore.h" + +class SpectacleDBusAdapter: public QDBusAbstractAdaptor +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.freedesktop.Screenshot") + 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" + "" + ) + + public: + + SpectacleDBusAdapter(KSCore *parent); + virtual ~SpectacleDBusAdapter(); + + inline KSCore *parent() const; + + public 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); + + signals: + + void ScreenshotTaken(const QString &fileName); + void ScreenshotFailed(); +}; + +#endif // SPECTACLEDBUSADAPTER_H