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