diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ 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}) @@ -23,6 +23,12 @@ ${ECM_MODULE_PATH} ) +# require c++14 + +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + # set up kf5 include(KDEInstallDirs) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,6 +5,22 @@ # 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 @@ -12,9 +28,7 @@ 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 @@ -30,13 +44,6 @@ 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 @@ -51,7 +58,6 @@ SPECTACLE_SRCS_ALL ${SPECTACLE_SRCS_DEFAULT} ${SPECTACLE_SRCS_KIPI} - ${SPECTACLE_SRCS_X11} ) add_executable( diff --git a/src/ExportManager.h b/src/ExportManager.h --- a/src/ExportManager.h +++ b/src/ExportManager.h @@ -1,24 +1,27 @@ -/* - * 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 @@ -27,14 +30,11 @@ #include #include #include - #include -#include "PlatformBackends/ImageGrabber.h" - class QTemporaryDir; -class ExportManager : public QObject +class ExportManager: public QObject { Q_OBJECT @@ -56,20 +56,15 @@ 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; @@ -86,6 +81,7 @@ QUrl getAutosaveFilename(); QUrl tempSave(); + 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); @@ -111,7 +107,5 @@ QTemporaryDir *mTempDir; QList mUsedTempFileNames; QString mWindowTitle; - ImageGrabber::GrabMode mGrabMode; + Spectacle::CaptureMode mCaptureMode { Spectacle::CaptureMode::AllScreens }; }; - -#endif // EXPORTMANAGER_H diff --git a/src/ExportManager.cpp b/src/ExportManager.cpp --- a/src/ExportManager.cpp +++ b/src/ExportManager.cpp @@ -1,20 +1,22 @@ -/* - * 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" @@ -83,14 +85,14 @@ 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) @@ -175,9 +177,9 @@ 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 diff --git a/src/Gui/KSImageWidget.h b/src/Gui/KSImageWidget.h --- a/src/Gui/KSImageWidget.h +++ b/src/Gui/KSImageWidget.h @@ -1,24 +1,25 @@ -/* - * 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 @@ -64,5 +65,3 @@ QPixmap mPixmap; QPoint mDragStartPosition; }; - -#endif // KSIMAGEWIDGET_H diff --git a/src/Gui/KSImageWidget.cpp b/src/Gui/KSImageWidget.cpp --- a/src/Gui/KSImageWidget.cpp +++ b/src/Gui/KSImageWidget.cpp @@ -1,20 +1,22 @@ -/* - * 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" @@ -85,7 +87,7 @@ void KSImageWidget::resizeEvent(QResizeEvent *event) { - Q_UNUSED(event); + Q_UNUSED(event) setScaledPixmap(); } diff --git a/src/Gui/KSMainWindow.h b/src/Gui/KSMainWindow.h --- a/src/Gui/KSMainWindow.h +++ b/src/Gui/KSMainWindow.h @@ -1,24 +1,25 @@ -/* - * 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 @@ -27,22 +28,24 @@ #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: @@ -62,7 +65,7 @@ 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(); @@ -81,7 +84,7 @@ 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: @@ -104,9 +107,7 @@ 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/KSMainWindow.cpp b/src/Gui/KSMainWindow.cpp --- a/src/Gui/KSMainWindow.cpp +++ b/src/Gui/KSMainWindow.cpp @@ -1,37 +1,29 @@ -/* - * 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 @@ -39,19 +31,28 @@ #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)), @@ -66,7 +67,7 @@ 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 @@ -86,7 +87,6 @@ } // do the xcb shenanigans - xcb_connection_t *xcbConn = QX11Info::connection(); const QByteArray effectName = QByteArrayLiteral("_KDE_NET_WM_SKIP_CLOSE_ANIMATION"); @@ -105,9 +105,6 @@ QMetaObject::invokeMethod(this, "init", Qt::QueuedConnection); } -KSMainWindow::~KSMainWindow() -{} - // GUI init void KSMainWindow::init() @@ -191,7 +188,6 @@ connect(mMessageWidget, &KMessageWidget::linkActivated, this, [](const QString &str) { QDesktopServices::openUrl(QUrl(str)); } ); // layouts - mDivider->setFrameShape(QFrame::HLine); mDivider->setLineWidth(2); @@ -203,14 +199,14 @@ 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())); @@ -256,7 +252,7 @@ void KSMainWindow::moveEvent(QMoveEvent *event) { - Q_UNUSED(event); + Q_UNUSED(event) KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("spectaclerc")); KConfigGroup guiConfig(config, "GuiConfig"); @@ -267,11 +263,11 @@ // 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) @@ -361,8 +357,6 @@ case KMessageWidget::Information: mMessageWidget->setIcon(QIcon::fromTheme(QStringLiteral("dialog-information"))); break; - default: - ; } mMessageWidget->animatedShow(); diff --git a/src/Gui/KSWidget.h b/src/Gui/KSWidget.h --- a/src/Gui/KSWidget.h +++ b/src/Gui/KSWidget.h @@ -1,29 +1,31 @@ -/* - * 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; @@ -43,44 +45,45 @@ 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/KSWidget.cpp b/src/Gui/KSWidget.cpp --- a/src/Gui/KSWidget.cpp +++ b/src/Gui/KSWidget.cpp @@ -24,8 +24,7 @@ #include "SmartSpinBox.h" #include "SpectacleConfig.h" -#include - +#include #include #include #include @@ -35,39 +34,38 @@ #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); @@ -79,12 +77,12 @@ 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); @@ -96,28 +94,27 @@ 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); @@ -127,7 +124,6 @@ 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); @@ -140,7 +136,6 @@ }); // finally, finish up the layouts - mRightLayout = new QVBoxLayout; mRightLayout->addStretch(1); mRightLayout->addWidget(mCaptureModeLabel); @@ -159,44 +154,50 @@ 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); } @@ -205,46 +206,52 @@ 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/SettingsDialog/SaveOptionsPage.cpp b/src/Gui/SettingsDialog/SaveOptionsPage.cpp --- a/src/Gui/SettingsDialog/SaveOptionsPage.cpp +++ b/src/Gui/SettingsDialog/SaveOptionsPage.cpp @@ -19,6 +19,7 @@ #include "SaveOptionsPage.h" +#include "SpectacleCommon.h" #include "SpectacleConfig.h" #include "ExportManager.h" @@ -202,20 +203,21 @@ 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 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -18,24 +18,26 @@ */ #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"); @@ -48,14 +50,14 @@ 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")}, @@ -72,94 +74,90 @@ {{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.h b/src/PlatformBackends/DummyImageGrabber.h deleted file mode 100644 --- 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/DummyImageGrabber.cpp b/src/PlatformBackends/DummyImageGrabber.cpp deleted file mode 100644 --- 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/ImageGrabber.h b/src/PlatformBackends/ImageGrabber.h deleted file mode 100644 --- 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/ImageGrabber.cpp b/src/PlatformBackends/ImageGrabber.cpp deleted file mode 100644 --- 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/KWinWaylandImageGrabber.h b/src/PlatformBackends/KWinWaylandImageGrabber.h deleted file mode 100644 --- 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/KWinWaylandImageGrabber.cpp b/src/PlatformBackends/KWinWaylandImageGrabber.cpp deleted file mode 100644 --- 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/X11ImageGrabber.h b/src/PlatformBackends/X11ImageGrabber.h deleted file mode 100644 --- 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/PlatformBackends/X11ImageGrabber.cpp b/src/PlatformBackends/X11ImageGrabber.cpp deleted file mode 100644 --- 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/Platforms/Platform.h b/src/Platforms/Platform.h new file mode 100644 --- /dev/null +++ b/src/Platforms/Platform.h @@ -0,0 +1,70 @@ +/* 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 = 0x01, + OnClick = 0x02 + }; + 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_OPERATORS_FOR_FLAGS(Platform::GrabModes) +Q_DECLARE_OPERATORS_FOR_FLAGS(Platform::ShutterModes) diff --git a/src/Platforms/Platform.cpp b/src/Platforms/Platform.cpp new file mode 100644 --- /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/PlatformKWinWayland.h b/src/Platforms/PlatformKWinWayland.h new file mode 100644 --- /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/PlatformKWinWayland.cpp b/src/Platforms/PlatformKWinWayland.cpp new file mode 100644 --- /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/PlatformLoader.h b/src/Platforms/PlatformLoader.h new file mode 100644 --- /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/PlatformLoader.cpp b/src/Platforms/PlatformLoader.cpp new file mode 100644 --- /dev/null +++ b/src/Platforms/PlatformLoader.cpp @@ -0,0 +1,52 @@ +/* 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() +{ + // 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/PlatformNull.h b/src/Platforms/PlatformNull.h new file mode 100644 --- /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/PlatformNull.cpp b/src/Platforms/PlatformNull.cpp new file mode 100644 --- /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/PlatformXcb.h b/src/Platforms/PlatformXcb.h new file mode 100644 --- /dev/null +++ b/src/Platforms/PlatformXcb.h @@ -0,0 +1,79 @@ +/* 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: + + inline void updateWindowTitle(xcb_window_t theWindow); + 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/Platforms/PlatformXcb.cpp b/src/Platforms/PlatformXcb.cpp new file mode 100644 --- /dev/null +++ b/src/Platforms/PlatformXcb.cpp @@ -0,0 +1,743 @@ +/* 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 +#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 ----------------------------------------------------------------------- */ + +void PlatformXcb::updateWindowTitle(xcb_window_t theWindow) +{ + auto lTitle = KWindowSystem::readNameProperty(theWindow, XA_WM_NAME); + emit windowTitleChanged(lTitle); +} + +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(); + updateWindowTitle(lActiveWindow); + + // 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(); + updateWindowTitle(lWindow); + + // 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(); + updateWindowTitle(lWindow); + + // 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/QuickEditor/QuickEditor.h b/src/QuickEditor/QuickEditor.h --- a/src/QuickEditor/QuickEditor.h +++ b/src/QuickEditor/QuickEditor.h @@ -31,14 +31,17 @@ class QMouseEvent; -class QuickEditor : public QWidget +class QuickEditor: public QWidget { Q_OBJECT -public: - explicit QuickEditor(const QPixmap &pixmap); + public: + + explicit QuickEditor(const QPixmap &thePixmap, QWidget *parent = nullptr); + virtual ~QuickEditor() = default; + + private: -private: enum MouseState : short { None = 0, // 0000 Inside = 1 << 0, // 0001 @@ -130,8 +133,9 @@ QRect mPrimaryScreenGeo; int mbottomHelpLength; -Q_SIGNALS: - void grabDone(const QPixmap &pixmap); + Q_SIGNALS: + + void grabDone(const QPixmap &thePixmap); void grabCancelled(); }; diff --git a/src/QuickEditor/QuickEditor.cpp b/src/QuickEditor/QuickEditor.cpp --- a/src/QuickEditor/QuickEditor.cpp +++ b/src/QuickEditor/QuickEditor.cpp @@ -44,7 +44,8 @@ const int QuickEditor::magPixels = 16; const int QuickEditor::magOffset = 32; -QuickEditor::QuickEditor(const QPixmap& pixmap) : +QuickEditor::QuickEditor(const QPixmap& thePixmap, QWidget *parent) : + QWidget(parent), mMaskColor(QColor::fromRgbF(0, 0, 0, 0.15)), mStrokeColor(palette().highlight().color()), mCrossColor(QColor::fromRgbF(mStrokeColor.redF(), mStrokeColor.greenF(), mStrokeColor.blueF(), 0.7)), @@ -60,7 +61,7 @@ mBottomHelpTextFont(font()), mBottomHelpGridLeftWidth(0), mMouseDragState(MouseState::None), - mPixmap(pixmap), + mPixmap(thePixmap), mMagnifierAllowed(false), mShowMagnifier(SpectacleConfig::instance()->showMagnifierChecked()), mToggleMagnifier(false), @@ -78,7 +79,6 @@ setMouseTracking(true); setAttribute(Qt::WA_StaticContents); setWindowFlags(Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint | Qt::Popup | Qt::WindowStaysOnTopHint); - show(); dprI = 1.0 / devicePixelRatioF(); setGeometry(0, 0, static_cast(mPixmap.width() * dprI), static_cast(mPixmap.height() * dprI)); @@ -429,7 +429,9 @@ setCursor(Qt::OpenHandCursor); } else if(mMouseDragState == MouseState::Outside && mReleaseToCapture) { - acceptSelection(); + event->accept(); + mMouseDragState = MouseState::None; + return acceptSelection(); } } else if (button == Qt::RightButton) { mSelection.setWidth(0); diff --git a/src/SpectacleCommon.h b/src/SpectacleCommon.h new file mode 100644 --- /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.h b/src/SpectacleCore.h --- a/src/SpectacleCore.h +++ b/src/SpectacleCore.h @@ -1,87 +1,92 @@ -/* - * 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 "QuickEditor/QuickEditor.h" +#include "Platforms/PlatformLoader.h" + +#include + +using MainWindowPtr = std::unique_ptr; +using EditorPtr = 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; + EditorPtr mQuickEditor; + bool mIsGuiInited; + bool mCopyToClipboard; }; - -#endif // KSCORE_H diff --git a/src/SpectacleCore.cpp b/src/SpectacleCore.cpp --- a/src/SpectacleCore.cpp +++ b/src/SpectacleCore.cpp @@ -21,11 +21,6 @@ #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 @@ -33,6 +28,7 @@ #include #include +#include #include #include #include @@ -41,90 +37,76 @@ #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; @@ -136,33 +118,33 @@ 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; @@ -173,15 +155,16 @@ } } -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(); + if (theTimeout < 0) { + mPlatform->doGrab(Platform::ShutterMode::OnClick, lGrabMode, theIncludePointer, theIncludeDecorations); return; } @@ -191,39 +174,58 @@ // 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(); + + // if we were running in rectangular crop mode, now would be + // the time to further process the image + + if (lExportManager->captureMode() == Spectacle::CaptureMode::RectangularRegion) { + if(!mQuickEditor) { + mQuickEditor = std::make_unique(thePixmap); + connect(mQuickEditor.get(), &QuickEditor::grabDone, this, &SpectacleCore::screenshotUpdated); + connect(mQuickEditor.get(), &QuickEditor::grabCancelled, this, &SpectacleCore::screenshotFailed); + mQuickEditor->showFullScreen(); + return; + } else { + mQuickEditor->hide(); + mQuickEditor.reset(nullptr); + } + } + + 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 @@ -233,80 +235,80 @@ } } break; - case GuiMode: - mMainWindow->setScreenshotAndShow(pixmap); + case StartMode::Gui: + mMainWindow->setScreenshotAndShow(thePixmap); } } void SpectacleCore::screenshotFailed() { + if (ExportManager::instance()->captureMode() == Spectacle::CaptureMode::RectangularRegion && mQuickEditor) { + mQuickEditor->hide(); + mQuickEditor.reset(nullptr); + } + 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) @@ -318,32 +320,59 @@ 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) +{ + 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 (!isGuiInited) { - mMainWindow = new KSMainWindow(mImageGrabber->supportedModes(), mImageGrabber->onClickGrabSupported()); + if (!mIsGuiInited) { + mMainWindow = std::make_unique(mPlatform->supportedGrabModes(), mPlatform->supportedShutterModes()); + + connect(mMainWindow.get(), &KSMainWindow::newScreenshotRequest, this, &SpectacleCore::takeNewScreenshot); + connect(mMainWindow.get(), &KSMainWindow::dragAndDropRequest, this, &SpectacleCore::doStartDragAndDrop); - connect(mMainWindow, &KSMainWindow::newScreenshotRequest, this, &SpectacleCore::takeNewScreenshot); - connect(mMainWindow, &KSMainWindow::dragAndDropRequest, this, &SpectacleCore::doStartDragAndDrop); + mIsGuiInited = true; - isGuiInited = true; - QMetaObject::invokeMethod(mImageGrabber, "doImageGrab", Qt::QueuedConnection); + 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/SpectacleDBusAdapter.h b/src/SpectacleDBusAdapter.h --- a/src/SpectacleDBusAdapter.h +++ b/src/SpectacleDBusAdapter.h @@ -1,24 +1,25 @@ -/* - * 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" @@ -60,7 +61,7 @@ public: SpectacleDBusAdapter(SpectacleCore *parent); - virtual ~SpectacleDBusAdapter(); + virtual ~SpectacleDBusAdapter() = default; inline SpectacleCore *parent() const; @@ -78,5 +79,3 @@ void ScreenshotTaken(const QString &fileName); void ScreenshotFailed(); }; - -#endif // SPECTACLEDBUSADAPTER_H diff --git a/src/SpectacleDBusAdapter.cpp b/src/SpectacleDBusAdapter.cpp --- a/src/SpectacleDBusAdapter.cpp +++ b/src/SpectacleDBusAdapter.cpp @@ -1,23 +1,26 @@ -/* - * 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) @@ -25,9 +28,6 @@ setAutoRelaySignals(false); } -SpectacleDBusAdapter::~SpectacleDBusAdapter() -{} - inline SpectacleCore *SpectacleDBusAdapter::parent() const { return static_cast(QObject::parent()); @@ -40,25 +40,25 @@ 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/tests/CMakeLists.txt b/tests/CMakeLists.txt --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -2,7 +2,7 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR}) ecm_add_test(FilenameTest.cpp - ../src/ExportManager.cpp ../src/SpectacleConfig.cpp ../src/PlatformBackends/ImageGrabber.cpp + ../src/ExportManager.cpp ../src/SpectacleConfig.cpp ../src/Platforms/Platform.cpp TEST_NAME "filename_test" LINK_LIBRARIES Qt5::Test Qt5::PrintSupport KF5::I18n KF5::ConfigCore KF5::KIOCore KF5::WindowSystem diff --git a/tests/FilenameTest.cpp b/tests/FilenameTest.cpp --- a/tests/FilenameTest.cpp +++ b/tests/FilenameTest.cpp @@ -3,14 +3,19 @@ #include #include +#include "SpectacleCommon.h" #include "ExportManager.h" -class FilenameTest : public QObject +class FilenameTest: public QObject { Q_OBJECT -private: - ExportManager* em; -private Q_SLOTS: + + private: + + ExportManager* mExportManager; + + private Q_SLOTS: + void initTestCase(); void testStrings(); void testDateTokens(); @@ -21,68 +26,69 @@ void FilenameTest::initTestCase() { - em = ExportManager::instance(); - em->setTimestamp(QDateTime::fromString(QStringLiteral("2019-03-22T10:43:25"), Qt::ISODate)); - em->setWindowTitle(QStringLiteral("Spectacle")); + mExportManager = ExportManager::instance(); + mExportManager->setTimestamp(QDateTime::fromString(QStringLiteral("2019-03-22T10:43:25"), Qt::ISODate)); + mExportManager->setWindowTitle(QStringLiteral("Spectacle")); } void FilenameTest::testStrings() { - QCOMPARE(em->formatFilename(QStringLiteral("Screenshot")), QStringLiteral("Screenshot")); + QCOMPARE(mExportManager->formatFilename(QStringLiteral("Screenshot")), QStringLiteral("Screenshot")); // empty string produces Screenshot per default - QCOMPARE(em->formatFilename(QStringLiteral("")), QStringLiteral("Screenshot")); + QCOMPARE(mExportManager->formatFilename(QStringLiteral("")), QStringLiteral("Screenshot")); // not a placeholder - QCOMPARE(em->formatFilename(QStringLiteral("%")), QStringLiteral("%")); - QCOMPARE(em->formatFilename(QStringLiteral("%K")), QStringLiteral("%K")); + QCOMPARE(mExportManager->formatFilename(QStringLiteral("%")), QStringLiteral("%")); + QCOMPARE(mExportManager->formatFilename(QStringLiteral("%K")), QStringLiteral("%K")); } void FilenameTest::testDateTokens() { - QCOMPARE(em->formatFilename(QStringLiteral("%Y")), QStringLiteral("2019")); - QCOMPARE(em->formatFilename(QStringLiteral("%y")), QStringLiteral("19")); - QCOMPARE(em->formatFilename(QStringLiteral("%M")), QStringLiteral("03")); - QCOMPARE(em->formatFilename(QStringLiteral("%D")), QStringLiteral("22")); - QCOMPARE(em->formatFilename(QStringLiteral("%H")), QStringLiteral("10")); - QCOMPARE(em->formatFilename(QStringLiteral("%m")), QStringLiteral("43")); - QCOMPARE(em->formatFilename(QStringLiteral("%S")), QStringLiteral("25")); + QCOMPARE(mExportManager->formatFilename(QStringLiteral("%Y")), QStringLiteral("2019")); + QCOMPARE(mExportManager->formatFilename(QStringLiteral("%y")), QStringLiteral("19")); + QCOMPARE(mExportManager->formatFilename(QStringLiteral("%M")), QStringLiteral("03")); + QCOMPARE(mExportManager->formatFilename(QStringLiteral("%D")), QStringLiteral("22")); + QCOMPARE(mExportManager->formatFilename(QStringLiteral("%H")), QStringLiteral("10")); + QCOMPARE(mExportManager->formatFilename(QStringLiteral("%m")), QStringLiteral("43")); + QCOMPARE(mExportManager->formatFilename(QStringLiteral("%S")), QStringLiteral("25")); } void FilenameTest::testWindowTitle() { - em->setGrabMode(ImageGrabber::ActiveWindow); - QCOMPARE(em->formatFilename(QStringLiteral("%T")), QStringLiteral("Spectacle")); - QCOMPARE(em->formatFilename(QStringLiteral("Before%TAfter")), + mExportManager->setCaptureMode(Spectacle::CaptureMode::ActiveWindow); + QCOMPARE(mExportManager->formatFilename(QStringLiteral("%T")), QStringLiteral("Spectacle")); + QCOMPARE(mExportManager->formatFilename(QStringLiteral("Before%TAfter")), QStringLiteral("BeforeSpectacleAfter")); - em->setGrabMode(ImageGrabber::FullScreen); + mExportManager->setCaptureMode(Spectacle::CaptureMode::AllScreens); //Empty String produces Screenshot - QCOMPARE(em->formatFilename(QStringLiteral("%T")), QStringLiteral("Screenshot")); - QCOMPARE(em->formatFilename(QStringLiteral("Before%TAfter")), QStringLiteral("BeforeAfter")); - QCOMPARE(em->formatFilename(QStringLiteral("Before_%T_After")), QStringLiteral("Before_After")); + QCOMPARE(mExportManager->formatFilename(QStringLiteral("%T")), QStringLiteral("Screenshot")); + QCOMPARE(mExportManager->formatFilename(QStringLiteral("Before%TAfter")), QStringLiteral("BeforeAfter")); + QCOMPARE(mExportManager->formatFilename(QStringLiteral("Before_%T_After")), QStringLiteral("Before_After")); } void FilenameTest::testNumbering() { - QString baseName = QStringLiteral("spectacle_test_")+ QUuid::createUuid().toString(); - QCOMPARE(em->formatFilename(baseName+QStringLiteral("_%d")), baseName+QStringLiteral("_1")); - QCOMPARE(em->formatFilename(baseName+QStringLiteral("_%1d")), baseName+QStringLiteral("_1")); - QCOMPARE(em->formatFilename(baseName+QStringLiteral("_%2d")), baseName+QStringLiteral("_01")); - QCOMPARE(em->formatFilename(baseName+QStringLiteral("_%3d")), baseName+QStringLiteral("_001")); - QCOMPARE(em->formatFilename(baseName+QStringLiteral("_%4d")), baseName+QStringLiteral("_0001")); - QCOMPARE(em->formatFilename(baseName+QStringLiteral("_%d_%2d_%3d")), - baseName+QStringLiteral("_1_01_001")); - QFile file(QDir(em->defaultSaveLocation()).filePath(baseName + QStringLiteral("_1.png"))); - file.open(QIODevice::WriteOnly); - QCOMPARE(em->formatFilename(baseName+QStringLiteral("_%d")), baseName+QStringLiteral("_2")); - file.remove(); + QString lBaseName = QStringLiteral("spectacle_test_")+ QUuid::createUuid().toString(); + QCOMPARE(mExportManager->formatFilename(lBaseName + QStringLiteral("_%d")), lBaseName + QStringLiteral("_1")); + QCOMPARE(mExportManager->formatFilename(lBaseName + QStringLiteral("_%1d")), lBaseName + QStringLiteral("_1")); + QCOMPARE(mExportManager->formatFilename(lBaseName + QStringLiteral("_%2d")), lBaseName + QStringLiteral("_01")); + QCOMPARE(mExportManager->formatFilename(lBaseName + QStringLiteral("_%3d")), lBaseName + QStringLiteral("_001")); + QCOMPARE(mExportManager->formatFilename(lBaseName + QStringLiteral("_%4d")), lBaseName + QStringLiteral("_0001")); + QCOMPARE(mExportManager->formatFilename(lBaseName + QStringLiteral("_%d_%2d_%3d")), + lBaseName+QStringLiteral("_1_01_001")); + + QFile lFile(QDir(mExportManager->defaultSaveLocation()).filePath(lBaseName + QStringLiteral("_1.png"))); + lFile.open(QIODevice::WriteOnly); + QCOMPARE(mExportManager->formatFilename(lBaseName+QStringLiteral("_%d")), lBaseName+QStringLiteral("_2")); + lFile.remove(); } void FilenameTest::testCombined() { - em->setGrabMode(ImageGrabber::ActiveWindow); - QCOMPARE(em->formatFilename(QStringLiteral("App_%T_Date_%Y%M%D_Time_%H:%m:%S%F")), + mExportManager->setCaptureMode(Spectacle::CaptureMode::ActiveWindow); + QCOMPARE(mExportManager->formatFilename(QStringLiteral("App_%T_Date_%Y%M%D_Time_%H:%m:%S%F")), QStringLiteral("App_Spectacle_Date_20190322_Time_10:43:25%F")); - em->setGrabMode(ImageGrabber::FullScreen); - QCOMPARE(em->formatFilename(QStringLiteral("App_%T_Date_%Y%M%D_Time_%H:%m:%S%F")), + mExportManager->setCaptureMode(Spectacle::CaptureMode::AllScreens); + QCOMPARE(mExportManager->formatFilename(QStringLiteral("App_%T_Date_%Y%M%D_Time_%H:%m:%S%F")), QStringLiteral("App_Date_20190322_Time_10:43:25%F")); }