diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -110,11 +110,12 @@ # hand off to subdirectories add_subdirectory(src) +add_subdirectory(src/BackendPlugins) add_subdirectory(dbus) add_subdirectory(desktop) add_subdirectory(icons) add_subdirectory(doc) -add_subdirectory(tests) +# add_subdirectory(tests) if (${ECM_VERSION} STRGREATER "5.58.0") install(FILES spectacle.categories DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}) diff --git a/src/BackendInterfaces/ScreenshotInterface.h b/src/BackendInterfaces/ScreenshotInterface.h new file mode 100644 --- /dev/null +++ b/src/BackendInterfaces/ScreenshotInterface.h @@ -0,0 +1,79 @@ +/* This file is part of Spectacle, the KDE screenshot utility + * Copyright (C) 2019 Leon De Andrade + * + * 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 + */ + + +#ifndef SCREENSHOT_INTERFACE_H +#define SCREENSHOT_INTERFACE_H + +#include +#include +#include + +enum class CaptureMode { + AllScreens = 1, + CurrentScreen = 2, + ActiveWindow = 4, + WindowUnderCursor = 8, + TransientWithParent = 16, + RectangularRegion = 32 +}; +Q_DECLARE_FLAGS(CaptureModes, CaptureMode) +Q_DECLARE_OPERATORS_FOR_FLAGS(CaptureModes) + +enum class ShutterMode { + Immediate = 1, + OnClick = 2 +}; +Q_DECLARE_FLAGS(ShutterModes, ShutterMode) +Q_DECLARE_OPERATORS_FOR_FLAGS(ShutterModes) + +enum class Platform { + Xcb, + Wayland, + Unknown +}; + +class ScreenshotInterface : public QObject { + Q_OBJECT + +public: + ScreenshotInterface(QObject* parent) : QObject(parent) {} + virtual ~ScreenshotInterface() = default; + + //Meta Data + virtual QString name() const = 0; + virtual Platform platform() const = 0; + virtual CaptureModes supportedCaptureModes() const = 0; + virtual ShutterModes supportedShutterModes() const = 0; + + virtual bool requirementsComplied() const = 0; + + virtual void doGrab(ShutterMode theShutterMode, CaptureMode theCaptureMode, bool theIncludePointer, bool theIncludeDecorations) = 0; + +Q_SIGNALS: + void newScreenshotTaken(const QPixmap &thePixmap); + void newScreenshotFailed(); + void windowTitleChanged(const QString &theWindowTitle); +}; + +Q_DECLARE_INTERFACE(ScreenshotInterface, "org.kde.spectacle.ScreenshotInterface/1.0") + +#endif //SCREENSHOT_INTERFACE_H \ No newline at end of file diff --git a/src/BackendPlugins/CMakeLists.txt b/src/BackendPlugins/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/src/BackendPlugins/CMakeLists.txt @@ -0,0 +1,18 @@ +add_library(XcbKWinScreenshotPlugin SHARED XcbKWinScreenshotPlugin.cpp ../BackendInterfaces/ScreenshotInterface.h) +target_link_libraries(XcbKWinScreenshotPlugin + Qt5::Widgets Qt5::DBus Qt5::X11Extras + KF5::WindowSystem + XCB::XFIXES XCB::IMAGE XCB::CURSOR XCB::UTIL +) + +add_library(WaylandKWinScreenshotPlugin SHARED WaylandKWinScreenshotPlugin.cpp ../BackendInterfaces/ScreenshotInterface.h) +target_link_libraries(WaylandKWinScreenshotPlugin + Qt5::Widgets Qt5::DBus Qt5::Concurrent +) + +add_library(XcbScreenshotPlugin SHARED XcbScreenshotPlugin.cpp ../BackendInterfaces/ScreenshotInterface.h) +target_link_libraries(XcbScreenshotPlugin + Qt5::Widgets Qt5::X11Extras + KF5::WindowSystem + XCB::XFIXES XCB::IMAGE XCB::CURSOR XCB::UTIL +) \ No newline at end of file diff --git a/src/BackendPlugins/WaylandKWinScreenshotPlugin.h b/src/BackendPlugins/WaylandKWinScreenshotPlugin.h new file mode 100644 --- /dev/null +++ b/src/BackendPlugins/WaylandKWinScreenshotPlugin.h @@ -0,0 +1,57 @@ +/* This file is part of Spectacle, the KDE screenshot utility + * Copyright (C) 2019 Leon De Andrade + * + * 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 + */ + + +#ifndef WAYLAND_KWIN_SCREENSHOT_PLUGIN_H +#define WAYLAND_KWIN_SCREENSHOT_PLUGIN_H + +#include "../BackendInterfaces/ScreenshotInterface.h" + +class WaylandKWinScreenshotPlugin : public ScreenshotInterface { + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.kde.spectacle.WaylandKWinScreenshotPlugin" FILE "WaylandScreenshotPlugin.json") + Q_INTERFACES(ScreenshotInterface) + +public: + explicit WaylandKWinScreenshotPlugin(QObject *parent = nullptr); + virtual ~WaylandKWinScreenshotPlugin() = default; + + //Meta Data + virtual QString name() const override; + virtual Platform platform() const override; + virtual CaptureModes supportedCaptureModes() const override; + virtual ShutterModes supportedShutterModes() const override; + + virtual bool requirementsComplied() const override; + + virtual void doGrab(ShutterMode theShutterMode, CaptureMode theCaptureMode, bool theIncludePointer, bool theIncludeDecorations) override; + +private: + void startReadImage(int theReadPipe); + + template + void doGrabHelper(const QString &theGrabMethod, ArgType theArgument); + + template + void callDBus(const QString &theGrabMethod, ArgType theArgument, int theWriteFile); +}; + +#endif //WAYLAND_SCREENSHOT_PLUGIN_H \ No newline at end of file diff --git a/src/Platforms/PlatformKWinWayland.cpp b/src/BackendPlugins/WaylandKWinScreenshotPlugin.cpp rename from src/Platforms/PlatformKWinWayland.cpp rename to src/BackendPlugins/WaylandKWinScreenshotPlugin.cpp --- a/src/Platforms/PlatformKWinWayland.cpp +++ b/src/BackendPlugins/WaylandKWinScreenshotPlugin.cpp @@ -1,5 +1,5 @@ /* This file is part of Spectacle, the KDE screenshot utility - * Copyright (C) 2016 Martin Graesslin + * Copyright (C) 2019 Leon De Andrade * * 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 @@ -19,13 +19,16 @@ * SPDX-License-Identifier: LGPL-2.0-or-later */ -#include "PlatformKWinWayland.h" + +#include "WaylandKWinScreenshotPlugin.h" #include #include #include #include #include +#include +#include #include #include #include @@ -71,61 +74,65 @@ return lImage; } -/* -- General Plumbing ------------------------------------------------------------------------- */ -PlatformKWinWayland::PlatformKWinWayland(QObject *parent) : - Platform(parent) -{} +/* -- WaylandKWinScreenshotPlugin ------------------------------------------------------------------------- */ + +WaylandKWinScreenshotPlugin::WaylandKWinScreenshotPlugin(QObject *parent) : ScreenshotInterface {parent} {} + -QString PlatformKWinWayland::platformName() const +QString WaylandKWinScreenshotPlugin::name() const { - return QStringLiteral("KWinWayland"); + return QStringLiteral("Wayland Screenshot Backend"); } -Platform::GrabModes PlatformKWinWayland::supportedGrabModes() const +Platform WaylandKWinScreenshotPlugin::platform() const { + return Platform::Wayland; +} + +CaptureModes WaylandKWinScreenshotPlugin::supportedCaptureModes() const { - Platform::GrabModes lSupportedModes({ GrabMode::AllScreens, GrabMode::WindowUnderCursor }); - if (QApplication::screens().count() > 1) { - lSupportedModes |= Platform::GrabMode::CurrentScreen; - } - return lSupportedModes; + return CaptureModes({ CaptureMode::AllScreens, CaptureMode::CurrentScreen, CaptureMode::WindowUnderCursor, CaptureMode::RectangularRegion }); } -Platform::ShutterModes PlatformKWinWayland::supportedShutterModes() const +ShutterModes WaylandKWinScreenshotPlugin::supportedShutterModes() const { return { ShutterMode::OnClick }; } -void PlatformKWinWayland::doGrab(ShutterMode theShutterMode, GrabMode theGrabMode, bool theIncludePointer, bool theIncludeDecorations) +bool WaylandKWinScreenshotPlugin::requirementsComplied() const +{ + return true; +} + +void WaylandKWinScreenshotPlugin::doGrab(ShutterMode theShutterMode, CaptureMode theCaptureMode, 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: { + switch(theCaptureMode) { + case CaptureMode::AllScreens: + case CaptureMode::RectangularRegion: + doGrabHelper(QStringLiteral("screenshotFullscreen"), theIncludePointer); + break; + case CaptureMode::CurrentScreen: + doGrabHelper(QStringLiteral("screenshotScreen"), theIncludePointer); + break; + case CaptureMode::WindowUnderCursor: { int lOpMask = theIncludeDecorations ? 1 : 0; if (theIncludePointer) { lOpMask |= 1 << 1; } - return doGrabHelper(QStringLiteral("interactive"), lOpMask); + doGrabHelper(QStringLiteral("interactive"), lOpMask); + break; } - case GrabMode::InvalidChoice: - case GrabMode::ActiveWindow: - case GrabMode::TransientWithParent: + default: emit newScreenshotFailed(); - return; } } -/* -- Grab Helpers ----------------------------------------------------------------------------- */ - -void PlatformKWinWayland::startReadImage(int theReadPipe) +void WaylandKWinScreenshotPlugin::startReadImage(int theReadPipe) { auto lWatcher = new QFutureWatcher(this); QObject::connect(lWatcher, &QFutureWatcher::finished, this, @@ -139,14 +146,7 @@ } 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) +void WaylandKWinScreenshotPlugin::doGrabHelper(const QString &theGrabMethod, ArgType theArgument) { int lPipeFds[2]; if (pipe2(lPipeFds, O_CLOEXEC|O_NONBLOCK) != 0) { @@ -159,3 +159,12 @@ close(lPipeFds[1]); } + +template +void WaylandKWinScreenshotPlugin::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); +} + +#include "moc_WaylandKWinScreenshotPlugin.cpp" \ No newline at end of file diff --git a/src/BackendPlugins/WaylandScreenshotPlugin.json b/src/BackendPlugins/WaylandScreenshotPlugin.json new file mode 100644 --- /dev/null +++ b/src/BackendPlugins/WaylandScreenshotPlugin.json @@ -0,0 +1,25 @@ +{ + "Name" : "Wayland KWin Screenshot Backend", + "Version" : "1.0", + "Vendor" : "Leon De Andrade", + + "Type": "Screenshot Backend", + + "Recommended": 5, + + "Platforms" : [ + "Wayland" + ], + "Requirements" : [ + "KWin" + ], + + "GrabModes" : [ + "AllScreens", + "WindowUnderCursor", + "CurrentScreen" + ], + "ShutterModes" : [ + "OnClick" + ] +} \ No newline at end of file diff --git a/src/BackendPlugins/XcbKWinScreenshotPlugin.h b/src/BackendPlugins/XcbKWinScreenshotPlugin.h new file mode 100644 --- /dev/null +++ b/src/BackendPlugins/XcbKWinScreenshotPlugin.h @@ -0,0 +1,97 @@ +/* This file is part of Spectacle, the KDE screenshot utility + * Copyright (C) 2019 Leon De Andrade + * + * 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 + */ + +#ifndef XCB_KWIN_SCREENSHOT_PLUGIN_H +#define XCB_KWIN_SCREENSHOT_PLUGIN_H + +#include "../BackendInterfaces/ScreenshotInterface.h" + +#include +#include + +typedef uint32_t xcb_window_t; +typedef uint32_t xcb_drawable_t; + +class xcb_image_t; + +class XcbKWinScreenshotPlugin : public ScreenshotInterface { + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.kde.spectacle.XcbKWinScreenshotPlugin" FILE "XcbKWinScreenshotPlugin.json") + Q_INTERFACES(ScreenshotInterface) + +public: + explicit XcbKWinScreenshotPlugin(QObject *parent = nullptr); + virtual ~XcbKWinScreenshotPlugin(); + + //Meta Data + virtual QString name() const override; + virtual Platform platform() const override; + virtual CaptureModes supportedCaptureModes() const override; + virtual ShutterModes supportedShutterModes() const override; + + virtual bool requirementsComplied() const override; + + virtual void doGrab(ShutterMode shutterMode, CaptureMode captureMode, bool includePointer, bool includeDecorations) override; + +private Q_SLOTS: + void doGrabNow(const CaptureMode &captureMode, bool includePointer, bool includeDecorations); + void doGrabOnClick(const CaptureMode &captureMode, bool includePointer, bool includeDecorations); + + void handleKWinScreenshotReply(quint64 theDrawable); + +private: + QRect getDrawableGeometry(xcb_drawable_t drawable); + QPixmap getPixmapFromDrawable(xcb_drawable_t xcbDrawable, const QRect &rect); + QPixmap convertFromNative(xcb_image_t *xcbImage); + + void updateWindowTitle(xcb_window_t window); + + void grabAllScreens(bool includePointer); + void grabCurrentScreen(bool includePointer); + void grabActiveWindow(bool includePointer, bool includeDecorations); + void grabWindowUnderCursor(bool includePointer, bool includeDecorations); + + void callDBus(const QString &method, const QList &args); + + // On-click screenshot shutter support needs a native event filter in xcb + class OnClickEventFilter; + + //TO DO: Do we need this a member? + OnClickEventFilter *mNativeEventFilter; +}; + +class XcbKWinScreenshotPlugin::OnClickEventFilter : public QAbstractNativeEventFilter { + +public: + OnClickEventFilter(XcbKWinScreenshotPlugin *platformPtr); + + void setCaptureOptions(const CaptureMode &captureMode, bool includePointer, bool includeDecorations); + bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) override; + +private: + XcbKWinScreenshotPlugin *mPlatformPtr; + // TO DO: Just assuming default values seems like a bad idea (AllScreen might not even be available) + CaptureMode mCaptureMode { CaptureMode::AllScreens }; + bool mIncludePointer { true }; + bool mIncludeDecorations { true }; +}; + +#endif //XCB_KWIN_SCREENSHOT_PLUGIN_H \ No newline at end of file diff --git a/src/BackendPlugins/XcbKWinScreenshotPlugin.cpp b/src/BackendPlugins/XcbKWinScreenshotPlugin.cpp new file mode 100644 --- /dev/null +++ b/src/BackendPlugins/XcbKWinScreenshotPlugin.cpp @@ -0,0 +1,376 @@ +/* This file is part of Spectacle, the KDE screenshot utility + * Copyright (C) 2019 Leon De Andrade + * + * 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 "XcbKWinScreenshotPlugin.h" + +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +struct XcbImagePtrDeleter { + void operator()(xcb_image_t *theXcbImage) const + { + if (theXcbImage) { + xcb_image_destroy(theXcbImage); + } + } +}; +using XcbImagePtr = std::unique_ptr; + +XcbKWinScreenshotPlugin::OnClickEventFilter::OnClickEventFilter(XcbKWinScreenshotPlugin *platformPtr) + : mPlatformPtr(platformPtr) {} + +void XcbKWinScreenshotPlugin::OnClickEventFilter::setCaptureOptions(const CaptureMode &captureMode, bool includePointer, bool includeDecorations) +{ + mCaptureMode = captureMode; + mIncludePointer = includePointer; + mIncludeDecorations = includeDecorations; +} + +bool XcbKWinScreenshotPlugin::OnClickEventFilter::nativeEventFilter(const QByteArray &eventType, void *message, long *) +{ + if (eventType == "xcb_generic_event_t") { + auto lFirstEvent = static_cast(message); + + 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(message); + if (lSecondEvent->detail == 1) { + QTimer::singleShot(0, [this]() { + mPlatformPtr->doGrabNow(mCaptureMode, mIncludePointer, mIncludeDecorations); + }); + } else if (lSecondEvent->detail < 4) { + emit mPlatformPtr->newScreenshotFailed(); + } else { + QTimer::singleShot(0, [this]() { + mPlatformPtr->doGrabOnClick(mCaptureMode, mIncludePointer, mIncludeDecorations); + }); + } + } + return true; + } + default: + return false; + } + } + return false; +} + +XcbKWinScreenshotPlugin::XcbKWinScreenshotPlugin(QObject *parent) + : ScreenshotInterface(parent), + mNativeEventFilter(new OnClickEventFilter(this)) {} + +XcbKWinScreenshotPlugin::~XcbKWinScreenshotPlugin() +{ + delete mNativeEventFilter; +} + +CaptureModes XcbKWinScreenshotPlugin::supportedCaptureModes() const +{ + return CaptureModes({CaptureMode::ActiveWindow, CaptureMode::CurrentScreen, CaptureMode::WindowUnderCursor }); +} + +ShutterModes XcbKWinScreenshotPlugin::supportedShutterModes() const +{ + return { ShutterMode::Immediate | ShutterMode::OnClick }; +} + +QString XcbKWinScreenshotPlugin::name() const +{ + return QStringLiteral("Xcb KWin Screenshot Backend"); +} + +Platform XcbKWinScreenshotPlugin::platform() const +{ + return Platform::Xcb; +} + +// TO DO: Real condition +bool XcbKWinScreenshotPlugin::requirementsComplied() const +{ + 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; +} + +void XcbKWinScreenshotPlugin::doGrab(ShutterMode shutterMode, CaptureMode captureMode, bool includePointer, bool includeDecorations) +{ + switch (shutterMode) { + case ShutterMode::Immediate: + return doGrabNow(captureMode, includePointer, includeDecorations); + case ShutterMode::OnClick: + return doGrabOnClick(captureMode, includePointer, includeDecorations); + } +} + +void XcbKWinScreenshotPlugin::doGrabNow(const CaptureMode &captureMode, bool includePointer, bool includeDecorations) +{ + switch (captureMode) { + case CaptureMode::CurrentScreen: + grabCurrentScreen(includePointer); + break; + case CaptureMode::ActiveWindow: + grabActiveWindow(includePointer, includeDecorations); + break; + case CaptureMode::WindowUnderCursor: + grabWindowUnderCursor(includePointer, includeDecorations); + break; + default: + emit newScreenshotFailed(); + } +} + +void XcbKWinScreenshotPlugin::doGrabOnClick(const CaptureMode &captureMode, bool includePointer, bool includeDecorations) +{ + // 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(captureMode, includePointer, includeDecorations); + return; + } + + // fix things if our pointer grab causes a lockup and install our event filter + mNativeEventFilter->setCaptureOptions(captureMode, includePointer, includeDecorations); + 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); +} + +void XcbKWinScreenshotPlugin::handleKWinScreenshotReply(quint64 drawable) +{ + // obtain width and height and grab an image (x and y are always zero for pixmaps) + auto lDrawable = static_cast(drawable); + auto rect = getDrawableGeometry(lDrawable); + auto pixmap = getPixmapFromDrawable(lDrawable, rect); + + if (!pixmap.isNull()) { + emit newScreenshotTaken(pixmap); + return; + } + emit newScreenshotFailed(); +} + +QRect XcbKWinScreenshotPlugin::getDrawableGeometry(xcb_drawable_t drawable) +{ + auto xcbConn = QX11Info::connection(); + auto geoCookie = xcb_get_geometry_unchecked(xcbConn, drawable); + std::unique_ptr geoReply(xcb_get_geometry_reply(xcbConn, geoCookie, nullptr)); + if (!geoReply) { + return QRect(); + } + return QRect(geoReply->x, geoReply->y, geoReply->width, geoReply->height); +} + +QPixmap XcbKWinScreenshotPlugin::getPixmapFromDrawable(xcb_drawable_t xcbDrawable, const QRect &rect) +{ + auto xcbConn = QX11Info::connection(); + + // proceed to get an image based on the geometry (in device pixels) + XcbImagePtr xcbImage( + xcb_image_get( + xcbConn, + xcbDrawable, + rect.x(), + rect.y(), + rect.width(), + rect.height(), + ~0, + XCB_IMAGE_FORMAT_Z_PIXMAP + ) + ); + + // too bad, the capture failed. + if (!xcbImage) { + return QPixmap(); + } + + // now process the image + return convertFromNative(xcbImage.get()); +} + +QPixmap XcbKWinScreenshotPlugin::convertFromNative(xcb_image_t *xcbImage) +{ + auto imageFormat = QImage::Format_Invalid; + switch (xcbImage->depth) { + case 1: + imageFormat = QImage::Format_MonoLSB; + break; + case 16: + imageFormat = QImage::Format_RGB16; + break; + case 24: + imageFormat = QImage::Format_RGB32; + break; + case 30: + imageFormat = QImage::Format_BGR30; + break; + case 32: + imageFormat = 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 (imageFormat == QImage::Format_RGB32) { + auto data = reinterpret_cast(xcbImage->data); + for (size_t iIter = 0; iIter < xcbImage->width * xcbImage->height; iIter++) { + data[iIter] |= 0xff000000; + } + } + + QImage image(xcbImage->data, xcbImage->width, xcbImage->height, imageFormat); + 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()); + } + + // 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(image).copy(); +} + + +void XcbKWinScreenshotPlugin::updateWindowTitle(xcb_window_t window) +{ + auto title = KWindowSystem::readNameProperty(window, XA_WM_NAME); + emit windowTitleChanged(title); +} + +void XcbKWinScreenshotPlugin::grabAllScreens(bool includePointer) +{ + callDBus(QStringLiteral("screenshotFullscreen"), { includePointer }); +} + +void XcbKWinScreenshotPlugin::grabCurrentScreen(bool includePointer) +{ + QScreen *currentScreen = QGuiApplication::screenAt(QCursor::pos()); + QList screenList = QGuiApplication::screens(); + + if (!currentScreen) { + //TO DO: Error handling + } + + callDBus(QStringLiteral("screenshotScreen"), { screenList.indexOf(currentScreen), includePointer }); +} + +void XcbKWinScreenshotPlugin::grabActiveWindow(bool includePointer, bool includeDecorations) +{ + //TO DO: Why is this not handled by KWin directly + auto activeWindow = KWindowSystem::activeWindow(); + updateWindowTitle(activeWindow); + + int optionMask = includeDecorations ? 1 : 0; + if (includePointer) { + optionMask |= 1 << 1; + } + + callDBus(QStringLiteral("screenshotForWindow"), { static_cast(activeWindow), optionMask }); +} + +void XcbKWinScreenshotPlugin::grabWindowUnderCursor(bool includePointer, bool includeDecorations) +{ + int optionMask = includeDecorations ? 1 : 0; + if (includePointer) { + optionMask |= 1 << 1; + } + + callDBus(QStringLiteral("screenshotWindowUnderCursor"), { optionMask }); +} + +void XcbKWinScreenshotPlugin::callDBus(const QString &method, const QList &args) { + QDBusConnection::sessionBus().connect(QStringLiteral("org.kde.KWin"), QStringLiteral("/Screenshot"), QStringLiteral("org.kde.kwin.Screenshot"), QStringLiteral("screenshotCreated"), this, SLOT(handleKWinScreenshotReply(quint64))); + QDBusInterface interface(QStringLiteral("org.kde.KWin"), QStringLiteral("/Screenshot"), QStringLiteral("org.kde.kwin.Screenshot")); + interface.callWithArgumentList(QDBus::NoBlock, method, args); +} + +#include "moc_XcbKWinScreenshotPlugin.cpp" \ No newline at end of file diff --git a/src/BackendPlugins/XcbKWinScreenshotPlugin.json b/src/BackendPlugins/XcbKWinScreenshotPlugin.json new file mode 100644 --- /dev/null +++ b/src/BackendPlugins/XcbKWinScreenshotPlugin.json @@ -0,0 +1,27 @@ +{ + "Name" : "Xcb KWin Screenshot Backend", + "Version" : "1.0", + "Vendor" : "Leon De Andrade", + + "Type": "Screenshot Backend", + + "Recommended": 2, + + "Platforms" : [ + "Xcb" + ], + "Requirements" : [ + "KWin" + ], + + "GrabModes" : [ + "AllScreens", + "ActiveWindow", + "WindowUnderCursor", + "CurrentScreen" + ], + "ShutterModes" : [ + "Immediate", + "OnClick" + ] +} \ No newline at end of file diff --git a/src/Platforms/PlatformXcb.h b/src/BackendPlugins/XcbScreenshotPlugin.h rename from src/Platforms/PlatformXcb.h rename to src/BackendPlugins/XcbScreenshotPlugin.h --- a/src/Platforms/PlatformXcb.h +++ b/src/BackendPlugins/XcbScreenshotPlugin.h @@ -1,5 +1,5 @@ /* This file is part of Spectacle, the KDE screenshot utility - * Copyright (C) 2019 Boudhayan Gupta + * Copyright (C) 2019 Leon De Andrade * * 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 @@ -19,40 +19,47 @@ * SPDX-License-Identifier: LGPL-2.0-or-later */ -#pragma once -#include "Platform.h" +#ifndef XCB_SCREENSHOT_PLUGIN +#define XCB_SCREENSHOT_PLUGIN -#include -#include +#include "../BackendInterfaces/ScreenshotInterface.h" -#include +#include -class PlatformXcb final: public Platform -{ - Q_OBJECT +typedef uint32_t xcb_window_t; +typedef uint32_t xcb_drawable_t; - public: +class QPoint; +class QRect; +class QPixmap; +class xcb_image_t; - explicit PlatformXcb(QObject *parent = nullptr); - virtual ~PlatformXcb(); +class XcbScreenshotPlugin : public ScreenshotInterface { + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.kde.spectacle.XcbScreenshotPlugin" FILE "XcbScreenshotPlugin.json") + Q_INTERFACES(ScreenshotInterface) + +public: + explicit XcbScreenshotPlugin(QObject *parent = nullptr); + virtual ~XcbScreenshotPlugin(); + + //Meta Data + virtual QString name() const override; + virtual Platform platform() const override; + virtual CaptureModes supportedCaptureModes() const override; + virtual ShutterModes supportedShutterModes() const override; - QString platformName() const override final; - GrabModes supportedGrabModes() const override final; - ShutterModes supportedShutterModes() const override final; - - public Q_SLOTS: - - void doGrab(Platform::ShutterMode theShutterMode, Platform::GrabMode theGrabMode, bool theIncludePointer, bool theIncludeDecorations) override final; - - private Q_SLOTS: + virtual bool requirementsComplied() const override; + + virtual void doGrab(ShutterMode shutterMode, CaptureMode theCaptureMode, bool theIncludePointer, bool theIncludeDecorations) override; +private Q_SLOTS: void handleKWinScreenshotReply(quint64 theDrawable); - void doGrabNow(const Platform::GrabMode &theGrabMode, bool theIncludePointer, bool theIncludeDecorations); - void doGrabOnClick(const Platform::GrabMode &theGrabMode, bool theIncludePointer, bool theIncludeDecorations); - - private: + void doGrabNow(const CaptureMode &theCaptureMode, bool theIncludePointer, bool theIncludeDecorations); + void doGrabOnClick(const CaptureMode &theCaptureMode, bool theIncludePointer, bool theIncludeDecorations); +private: inline void updateWindowTitle(xcb_window_t theWindow); bool isKWinAvailable(); QPoint getCursorPosition(); @@ -72,8 +79,28 @@ 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 + + // On-click screenshot shutter support needs a native event filter in xcb class OnClickEventFilter; - OnClickEventFilter *mNativeEventFilter; + + //TO DO: Do we need this a member? + OnClickEventFilter *mNativeEventFilter; +}; + +class XcbScreenshotPlugin::OnClickEventFilter : public QAbstractNativeEventFilter { + +public: + OnClickEventFilter(XcbScreenshotPlugin *thePlatformPtr); + + void setCaptureOptions(const CaptureMode &theCaptureMode, bool theIncludePointer, bool theIncludeDecorations); + bool nativeEventFilter(const QByteArray &theEventType, void *theMessage, long *theResult) override; + +private: + XcbScreenshotPlugin *mPlatformPtr; + // TO DO: Just assuming default values seems like a bad idea (AllScreen might not even be available) + CaptureMode mCaptureMode { CaptureMode::AllScreens }; + bool mIncludePointer { true }; + bool mIncludeDecorations { true }; }; + +#endif //XCB_SCREENSHOT_PLUGIN \ No newline at end of file diff --git a/src/Platforms/PlatformXcb.cpp b/src/BackendPlugins/XcbScreenshotPlugin.cpp rename from src/Platforms/PlatformXcb.cpp rename to src/BackendPlugins/XcbScreenshotPlugin.cpp --- a/src/Platforms/PlatformXcb.cpp +++ b/src/BackendPlugins/XcbScreenshotPlugin.cpp @@ -1,5 +1,5 @@ /* This file is part of Spectacle, the KDE screenshot utility - * Copyright (C) 2019 Boudhayan Gupta + * Copyright (C) 2019 Leon De Andrade * * 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 @@ -19,38 +19,38 @@ * SPDX-License-Identifier: LGPL-2.0-or-later */ -#include "PlatformXcb.h" + +#include "XcbScreenshotPlugin.h" #include #include #include +#include +#include #include #include -#include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include -#include -#include -#include -#include +#include #include -#include -#include -#include -#include -#include +#include +#include #include #include #include -/* -- XCB Image Smart Pointer ------------------------------------------------------------------ */ - struct XcbImagePtrDeleter { void operator()(xcb_image_t *theXcbImage) const @@ -64,126 +64,106 @@ /* -- On Click Native Event Filter ------------------------------------------------------------- */ -class PlatformXcb::OnClickEventFilter: public QAbstractNativeEventFilter +XcbScreenshotPlugin::OnClickEventFilter::OnClickEventFilter(XcbScreenshotPlugin *thePlatformPtr) + : mPlatformPtr(thePlatformPtr) {} + +void XcbScreenshotPlugin::OnClickEventFilter::setCaptureOptions(const CaptureMode &theCaptureMode, bool theIncludePointer, bool theIncludeDecorations) { - 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: + mCaptureMode = theCaptureMode; + mIncludePointer = theIncludePointer; + mIncludeDecorations = theIncludeDecorations; +} - PlatformXcb *mPlatformPtr; - Platform::GrabMode mGrabMode { GrabMode::AllScreens }; - bool mIncludePointer { true }; - bool mIncludeDecorations { true }; -}; +bool XcbScreenshotPlugin::OnClickEventFilter::nativeEventFilter(const QByteArray &theEventType, void *theMessage, long*) +{ + 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(mCaptureMode, mIncludePointer, mIncludeDecorations); + }); + } else if (lSecondEvent->detail < 4) { + emit mPlatformPtr->newScreenshotFailed(); + } else { + QTimer::singleShot(0, [this]() { + mPlatformPtr->doGrabOnClick(mCaptureMode, mIncludePointer, mIncludeDecorations); + }); + } + } + return true; + } + default: + return false; + } + } + return false; +} /* -- General Plumbing ------------------------------------------------------------------------- */ -PlatformXcb::PlatformXcb(QObject *theParent) : - Platform(theParent), - mNativeEventFilter(new OnClickEventFilter(this)) -{} +XcbScreenshotPlugin::XcbScreenshotPlugin(QObject *theParent) + : ScreenshotInterface(theParent), mNativeEventFilter(new OnClickEventFilter(this)) {} -PlatformXcb::~PlatformXcb() +XcbScreenshotPlugin::~XcbScreenshotPlugin() { delete mNativeEventFilter; } -QString PlatformXcb::platformName() const +CaptureModes XcbScreenshotPlugin::supportedCaptureModes() const { - return QStringLiteral("Xcb"); + return CaptureModes({ CaptureMode::AllScreens, CaptureMode::CurrentScreen, CaptureMode::ActiveWindow, CaptureMode::WindowUnderCursor, CaptureMode::TransientWithParent, CaptureMode::RectangularRegion }); } -Platform::GrabModes PlatformXcb::supportedGrabModes() const +ShutterModes XcbScreenshotPlugin::supportedShutterModes() const { - Platform::GrabModes lSupportedModes({ GrabMode::AllScreens, GrabMode::ActiveWindow, GrabMode::WindowUnderCursor, GrabMode::TransientWithParent }); - if (QApplication::screens().count() > 1) { - lSupportedModes |= Platform::GrabMode::CurrentScreen; - } - return lSupportedModes; + return { ShutterMode::Immediate | ShutterMode::OnClick }; } -Platform::ShutterModes PlatformXcb::supportedShutterModes() const +QString XcbScreenshotPlugin::name() const { - return { ShutterMode::Immediate | ShutterMode::OnClick }; + return QStringLiteral("Xcb Screenshot Backend"); } -void PlatformXcb::doGrab(ShutterMode theShutterMode, GrabMode theGrabMode, bool theIncludePointer, bool theIncludeDecorations) +Platform XcbScreenshotPlugin::platform() const { + return Platform::Xcb; +} + +bool XcbScreenshotPlugin::requirementsComplied() const +{ + return true; +} + +void XcbScreenshotPlugin::doGrab(ShutterMode theShutterMode, CaptureMode theCaptureMode, bool theIncludePointer, bool theIncludeDecorations) { switch(theShutterMode) { case ShutterMode::Immediate: - return doGrabNow(theGrabMode, theIncludePointer, theIncludeDecorations); + return doGrabNow(theCaptureMode, theIncludePointer, theIncludeDecorations); case ShutterMode::OnClick: - return doGrabOnClick(theGrabMode, theIncludePointer, theIncludeDecorations); + return doGrabOnClick(theCaptureMode, theIncludePointer, theIncludeDecorations); } } /* -- Platform Utilities ----------------------------------------------------------------------- */ -void PlatformXcb::updateWindowTitle(xcb_window_t theWindow) +void XcbScreenshotPlugin::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() +QPoint XcbScreenshotPlugin::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 @@ -196,7 +176,7 @@ return QPoint(lPointerReply->root_x, lPointerReply->root_y); } -QRect PlatformXcb::getDrawableGeometry(xcb_drawable_t theDrawable) +QRect XcbScreenshotPlugin::getDrawableGeometry(xcb_drawable_t theDrawable) { auto lXcbConn = QX11Info::connection(); auto lGeoCookie = xcb_get_geometry_unchecked(lXcbConn, theDrawable); @@ -207,7 +187,7 @@ return QRect(lGeoReply->x, lGeoReply->y, lGeoReply->width, lGeoReply->height); } -xcb_window_t PlatformXcb::getWindowUnderCursor() +xcb_window_t XcbScreenshotPlugin::getWindowUnderCursor() { auto lXcbConn = QX11Info::connection(); auto lAppWin = QX11Info::appRootWindow(); @@ -254,7 +234,7 @@ return lPointerReply->child; } -xcb_window_t PlatformXcb::getTransientWindowParent(xcb_window_t theChildWindow, QRect &theWindowRectOut, bool theIncludeDectorations) +xcb_window_t XcbScreenshotPlugin::getTransientWindowParent(xcb_window_t theChildWindow, QRect &theWindowRectOut, bool theIncludeDectorations) { NET::Properties lNetProp = theIncludeDectorations ? NET::WMFrameExtents : NET::WMGeometry; KWindowInfo lWindowInfo(theChildWindow, lNetProp, NET::WM2TransientFor); @@ -270,7 +250,7 @@ /* -- Image Processing Utilities --------------------------------------------------------------- */ -QPixmap PlatformXcb::convertFromNative(xcb_image_t *theXcbImage) +QPixmap XcbScreenshotPlugin::convertFromNative(xcb_image_t *theXcbImage) { auto lImageFormat = QImage::Format_Invalid; switch (theXcbImage->depth) { @@ -318,7 +298,7 @@ return QPixmap::fromImage(lImage).copy(); } -QPixmap PlatformXcb::blendCursorImage(QPixmap &thePixmap, const QRect theRect) +QPixmap XcbScreenshotPlugin::blendCursorImage(QPixmap &thePixmap, const QRect theRect) { // If the cursor position lies outside the area, do not bother drawing a cursor. @@ -353,7 +333,7 @@ return thePixmap; } -QPixmap PlatformXcb::postProcessPixmap(QPixmap &thePixmap, QRect theRect, bool theBlendPointer) +QPixmap XcbScreenshotPlugin::postProcessPixmap(QPixmap &thePixmap, QRect theRect, bool theBlendPointer) { if (!(theBlendPointer)) { // note: this may be the null pixmap if an error occurred. @@ -364,7 +344,7 @@ /* -- Capture Helpers -------------------------------------------------------------------------- */ -QPixmap PlatformXcb::getPixmapFromDrawable(xcb_drawable_t theXcbDrawable, const QRect &theRect) +QPixmap XcbScreenshotPlugin::getPixmapFromDrawable(xcb_drawable_t theXcbDrawable, const QRect &theRect) { auto lXcbConn = QX11Info::connection(); @@ -388,11 +368,10 @@ } // now process the image - auto lPixmap = convertFromNative(lXcbImage.get()); - return lPixmap; + return convertFromNative(lXcbImage.get()); } -QPixmap PlatformXcb::getToplevelPixmap(QRect theRect, bool theBlendPointer) +QPixmap XcbScreenshotPlugin::getToplevelPixmap(QRect theRect, bool theBlendPointer) { auto lRootWindow = QX11Info::appRootWindow(); @@ -420,7 +399,7 @@ return postProcessPixmap(lPixmap, theRect, theBlendPointer); } -QPixmap PlatformXcb::getWindowPixmap(xcb_window_t theWindow, bool theBlendPointer) +QPixmap XcbScreenshotPlugin::getWindowPixmap(xcb_window_t theWindow, bool theBlendPointer) { auto lXcbConn = QX11Info::connection(); @@ -449,7 +428,7 @@ return postProcessPixmap(lPixmap, lWindowRect, theBlendPointer); } -void PlatformXcb::handleKWinScreenshotReply(quint64 theDrawable) +void XcbScreenshotPlugin::handleKWinScreenshotReply(quint64 theDrawable) { // obtain width and height and grab an image (x and y are always zero for pixmaps) auto lDrawable = static_cast(theDrawable); @@ -465,13 +444,13 @@ /* -- Grabber Methods -------------------------------------------------------------------------- */ -void PlatformXcb::grabAllScreens(bool theIncludePointer) +void XcbScreenshotPlugin::grabAllScreens(bool theIncludePointer) { auto lPixmap = getToplevelPixmap(QRect(), theIncludePointer); emit newScreenshotTaken(lPixmap); } -void PlatformXcb::grabCurrentScreen(bool theIncludePointer) +void XcbScreenshotPlugin::grabCurrentScreen(bool theIncludePointer) { auto lCursorPosition = QCursor::pos(); const auto lScreens = QGuiApplication::screens(); @@ -493,7 +472,7 @@ grabAllScreens(theIncludePointer); } -void PlatformXcb::grabApplicationWindow(xcb_window_t theWindow, bool theIncludePointer, bool theIncludeDecorations) +void XcbScreenshotPlugin::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 @@ -525,63 +504,24 @@ emit newScreenshotTaken(lPixmap); } -void PlatformXcb::grabActiveWindow(bool theIncludePointer, bool theIncludeDecorations) +void XcbScreenshotPlugin::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) +void XcbScreenshotPlugin::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) +void XcbScreenshotPlugin::grabTransientWithParent(bool theIncludePointer, bool theIncludeDecorations) { auto lWindow = getWindowUnderCursor(); updateWindowTitle(lWindow); @@ -667,30 +607,31 @@ emit newScreenshotTaken(lPixmap); } -void PlatformXcb::doGrabNow(const GrabMode &theGrabMode, bool theIncludePointer, bool theIncludeDecorations) +void XcbScreenshotPlugin::doGrabNow(const CaptureMode &theCaptureMode, bool theIncludePointer, bool theIncludeDecorations) { - switch(theGrabMode) { - case GrabMode::AllScreens: + switch(theCaptureMode) { + case CaptureMode::AllScreens: + case CaptureMode::RectangularRegion: grabAllScreens(theIncludePointer); break; - case GrabMode::CurrentScreen: + case CaptureMode::CurrentScreen: grabCurrentScreen(theIncludePointer); break; - case GrabMode::ActiveWindow: + case CaptureMode::ActiveWindow: grabActiveWindow(theIncludePointer, theIncludeDecorations); break; - case GrabMode::WindowUnderCursor: + case CaptureMode::WindowUnderCursor: grabWindowUnderCursor(theIncludePointer, theIncludeDecorations); break; - case GrabMode::TransientWithParent: + case CaptureMode::TransientWithParent: grabTransientWithParent(theIncludePointer, theIncludeDecorations); break; - case GrabMode::InvalidChoice: + default: emit newScreenshotFailed(); } } -void PlatformXcb::doGrabOnClick(const GrabMode &theGrabMode, bool theIncludePointer, bool theIncludeDecorations) +void XcbScreenshotPlugin::doGrabOnClick(const CaptureMode &theCaptureMode, bool theIncludePointer, bool theIncludeDecorations) { // get the cursor image xcb_cursor_t lXcbCursor = XCB_CURSOR_NONE; @@ -730,16 +671,18 @@ // if the grab failed, take the screenshot right away if (lGrabPointerReply->status != XCB_GRAB_STATUS_SUCCESS) { - doGrabNow(theGrabMode, theIncludePointer, theIncludeDecorations); + doGrabNow(theCaptureMode, theIncludePointer, theIncludeDecorations); return; } // fix things if our pointer grab causes a lockup and install our event filter - mNativeEventFilter->setCaptureOptions(theGrabMode, theIncludePointer, theIncludeDecorations); + mNativeEventFilter->setCaptureOptions(theCaptureMode, 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); } + +#include "moc_XcbScreenshotPlugin.cpp" diff --git a/src/BackendPlugins/XcbScreenshotPlugin.json b/src/BackendPlugins/XcbScreenshotPlugin.json new file mode 100644 --- /dev/null +++ b/src/BackendPlugins/XcbScreenshotPlugin.json @@ -0,0 +1,26 @@ +{ + "Name" : "Xcb Screenshot Backend", + "Version" : "1.0", + "Vendor" : "Leon De Andrade", + + "Type": "Screenshot Backend", + + "Recommended": 4, + + "Platforms" : [ + "Xcb" + ], + "Requirements" : [], + + "GrabModes" : [ + "AllScreens", + "ActiveWindow", + "WindowUnderCursor", + "CurrentScreen", + "TransientWithParent" + ], + "ShutterModes" : [ + "Immediate", + "OnClick" + ] +} \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,32 +3,14 @@ configure_file(Config.h.in ${CMAKE_CURRENT_BINARY_DIR}/Config.h) set(CMAKE_AUTORCC 1) -# target - -if(XCB_FOUND) - set( - SPECTACLE_SRCS_PLATFORM_XCB - Platforms/PlatformXcb.cpp - ) -endif() - -set( - SPECTACLE_SRCS_PLATFORM - Platforms/PlatformLoader.cpp - Platforms/Platform.cpp - Platforms/PlatformNull.cpp - Platforms/PlatformKWinWayland.cpp - ${SPECTACLE_SRCS_PLATFORM_XCB} -) - set( SPECTACLE_SRCS_DEFAULT Main.cpp ExportManager.cpp SpectacleCore.cpp SpectacleConfig.cpp SpectacleDBusAdapter.cpp - ${SPECTACLE_SRCS_PLATFORM} + BackendInterfaces/ScreenshotInterface.h Gui/KSMainWindow.cpp Gui/KSWidget.cpp Gui/KSImageWidget.cpp @@ -40,6 +22,7 @@ Gui/SettingsDialog/SaveOptionsPage.cpp Gui/SettingsDialog/GeneralOptionsPage.cpp Gui/SettingsDialog/ShortcutsOptionsPage.cpp + Gui/SettingsDialog/BackendOptionsPage.cpp QuickEditor/QuickEditor.cpp ) @@ -68,11 +51,10 @@ ) # link libraries - target_link_libraries( spectacle - Qt5::DBus Qt5::PrintSupport + Qt5::X11Extras KF5::CoreAddons KF5::DBusAddons KF5::WidgetsAddons @@ -84,19 +66,9 @@ KF5::NewStuff KF5::GlobalAccel KF5::XmlGui + XCB::UTIL ) -if(XCB_FOUND) - target_link_libraries( - spectacle - XCB::XFIXES - XCB::IMAGE - XCB::CURSOR - XCB::UTIL - Qt5::X11Extras - ) -endif() - if(KIPI_FOUND) target_link_libraries ( spectacle diff --git a/src/ExportManager.h b/src/ExportManager.h --- a/src/ExportManager.h +++ b/src/ExportManager.h @@ -21,7 +21,7 @@ #pragma once -#include +#include "BackendInterfaces/ScreenshotInterface.h" #include #include @@ -63,8 +63,8 @@ void updatePixmapTimestamp(); void setTimestamp(const QDateTime ×tamp); QString windowTitle() const; - Spectacle::CaptureMode captureMode() const; - void setCaptureMode(const Spectacle::CaptureMode &theCaptureMode); + CaptureMode captureMode() const; + void setCaptureMode(const CaptureMode &theCaptureMode); QString formatFilename(const QString &nameTemplate); static const QMap filenamePlaceholders; @@ -107,5 +107,6 @@ QTemporaryDir *mTempDir; QList mUsedTempFileNames; QString mWindowTitle; - Spectacle::CaptureMode mCaptureMode { Spectacle::CaptureMode::AllScreens }; + //TO DO: Assuming a default value here, seems like a bad idea (this mode might not even be available) + CaptureMode mCaptureMode { CaptureMode::AllScreens }; }; diff --git a/src/ExportManager.cpp b/src/ExportManager.cpp --- a/src/ExportManager.cpp +++ b/src/ExportManager.cpp @@ -82,12 +82,12 @@ return mWindowTitle; } -Spectacle::CaptureMode ExportManager::captureMode() const +CaptureMode ExportManager::captureMode() const { return mCaptureMode; } -void ExportManager::setCaptureMode(const Spectacle::CaptureMode &theCaptureMode) +void ExportManager::setCaptureMode(const CaptureMode &theCaptureMode) { mCaptureMode = theCaptureMode; } @@ -170,9 +170,9 @@ const QString baseDir = defaultSaveLocation(); QString title; - if (mCaptureMode == Spectacle::CaptureMode::ActiveWindow || - mCaptureMode == Spectacle::CaptureMode::TransientWithParent || - mCaptureMode == Spectacle::CaptureMode::WindowUnderCursor) { + if (mCaptureMode == CaptureMode::ActiveWindow || + mCaptureMode == CaptureMode::TransientWithParent || + mCaptureMode == 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 @@ -43,7 +43,6 @@ void setScreenshot(const QPixmap &pixmap); Q_SIGNALS: - void dragInitiated(); protected: diff --git a/src/Gui/KSMainWindow.h b/src/Gui/KSMainWindow.h --- a/src/Gui/KSMainWindow.h +++ b/src/Gui/KSMainWindow.h @@ -30,11 +30,10 @@ #include #include -#include "SpectacleCommon.h" +#include "BackendInterfaces/ScreenshotInterface.h" #include "SpectacleConfig.h" #include "KSWidget.h" #include "ExportMenu.h" -#include "Platforms/Platform.h" #include @@ -44,7 +43,7 @@ public: - explicit KSMainWindow(const Platform::GrabModes &theGrabModes, const Platform::ShutterModes &theShutterModes, QWidget *parent = nullptr); + explicit KSMainWindow(const CaptureModes &theCaptureModes, const ShutterModes &theShutterModes, QWidget *parent = nullptr); virtual ~KSMainWindow() = default; private: @@ -66,7 +65,7 @@ private Q_SLOTS: - void captureScreenshot(Spectacle::CaptureMode theCaptureMode, int theTimeout, bool theIncludePointer, bool theIncludeDecorations); + void captureScreenshot(CaptureMode theCaptureMode, int theTimeout, bool theIncludePointer, bool theIncludeDecorations); void showPrintDialog(); void openScreenshotsFolder(); void showPreferencesDialog(); @@ -81,12 +80,15 @@ public Q_SLOTS: + void setCaptureModes(const CaptureModes &theCaptureModes); + void setShutterModes(const ShutterModes &theShutterModes); + void setScreenshotAndShow(const QPixmap &pixmap); void imageSaved(const QUrl &location); Q_SIGNALS: - void newScreenshotRequest(Spectacle::CaptureMode theCaptureMode, int theTimeout, bool theIncludePointer, bool theIncludeDecorations); + void newScreenshotRequest(CaptureMode theCaptureMode, int theTimeout, bool theIncludePointer, bool theIncludeDecorations); void dragAndDropRequest(); protected: @@ -111,6 +113,6 @@ QMenu *mScreenRecorderToolsMenu; std::unique_ptr mScreenrecorderToolsMenuFactory; ExportMenu *mExportMenu; - Platform::ShutterModes mShutterModes; + ShutterModes mShutterModes; QTimer *mHideMessageWidgetTimer; }; diff --git a/src/Gui/KSMainWindow.cpp b/src/Gui/KSMainWindow.cpp --- a/src/Gui/KSMainWindow.cpp +++ b/src/Gui/KSMainWindow.cpp @@ -54,9 +54,9 @@ static const int DEFAULT_WINDOW_WIDTH = 840; static const int MAXIMUM_WINDOW_WIDTH = 1000; -KSMainWindow::KSMainWindow(const Platform::GrabModes &theGrabModes, const Platform::ShutterModes &theShutterModes, QWidget *parent) : +KSMainWindow::KSMainWindow(const CaptureModes &theCaptureModes, const ShutterModes &theShutterModes, QWidget *parent) : QDialog(parent), - mKSWidget(new KSWidget(theGrabModes, this)), + mKSWidget(new KSWidget(theCaptureModes, this)), mDivider(new QFrame(this)), mDialogButtonBox(new QDialogButtonBox(this)), mConfigureButton(new QToolButton(this)), @@ -208,9 +208,9 @@ connect(mExportMenu, &ExportMenu::imageShared, this, &KSMainWindow::showImageSharedFeedback); // lock down the onClick mode depending on available shutter modes - if (!mShutterModes.testFlag(Platform::ShutterMode::OnClick)) { + if (!mShutterModes.testFlag(ShutterMode::OnClick)) { mKSWidget->lockOnClickDisabled(); - } else if (!mShutterModes.testFlag(Platform::ShutterMode::Immediate)) { + } else if (!mShutterModes.testFlag(ShutterMode::Immediate)) { mKSWidget->lockOnClickEnabled(); } resize(QSize(DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT).expandedTo(minimumSize())); @@ -271,7 +271,7 @@ } // slots -void KSMainWindow::captureScreenshot(Spectacle::CaptureMode theCaptureMode, int theTimeout, bool theIncludePointer, bool theIncludeDecorations) +void KSMainWindow::captureScreenshot(CaptureMode theCaptureMode, int theTimeout, bool theIncludePointer, bool theIncludeDecorations) { if (theTimeout < 0) { // OnClick is checked (always the case on Wayland) hide(); @@ -324,6 +324,21 @@ delayAnimation->start(); } +void KSMainWindow::setCaptureModes(const CaptureModes &theCaptureModes) +{ + mKSWidget->initComboBox(theCaptureModes); +} + +void KSMainWindow::setShutterModes(const ShutterModes &theShutterModes) +{ + mShutterModes = theShutterModes; + if (!mShutterModes.testFlag(ShutterMode::OnClick)) { + mKSWidget->lockOnClickDisabled(); + } else if (!mShutterModes.testFlag(ShutterMode::Immediate)) { + mKSWidget->lockOnClickEnabled(); + } +} + void KSMainWindow::setScreenshotAndShow(const QPixmap &pixmap) { if (!pixmap.isNull()) { diff --git a/src/Gui/KSWidget.h b/src/Gui/KSWidget.h --- a/src/Gui/KSWidget.h +++ b/src/Gui/KSWidget.h @@ -24,8 +24,7 @@ #include #include -#include "SpectacleCommon.h" -#include "Platforms/Platform.h" +#include "BackendInterfaces/ScreenshotInterface.h" class QAction; class QGridLayout; @@ -46,7 +45,7 @@ public: - explicit KSWidget(const Platform::GrabModes &theGrabModes, QWidget *parent = nullptr); + explicit KSWidget(const CaptureModes &theCaptureModes, QWidget *parent = nullptr); virtual ~KSWidget() = default; @@ -60,11 +59,13 @@ Q_SIGNALS: void dragInitiated(); - void newScreenshotRequest(Spectacle::CaptureMode theCaptureMode, int theCaptureDelat, bool theIncludePointer, bool theIncludeDecorations); + void newScreenshotRequest(CaptureMode theCaptureMode, int theCaptureDelat, bool theIncludePointer, bool theIncludeDecorations); void screenshotCanceled(); public Q_SLOTS: + void initComboBox(const CaptureModes &theCaptureModes); + void setScreenshotPixmap(const QPixmap &thePixmap); void lockOnClickDisabled(); void lockOnClickEnabled(); diff --git a/src/Gui/KSWidget.cpp b/src/Gui/KSWidget.cpp --- a/src/Gui/KSWidget.cpp +++ b/src/Gui/KSWidget.cpp @@ -37,7 +37,7 @@ #include -KSWidget::KSWidget(const Platform::GrabModes &theGrabModes, QWidget *parent) : +KSWidget::KSWidget(const CaptureModes &theCaptureModes, QWidget *parent) : QWidget(parent) { // get a handle to the configuration manager @@ -51,23 +51,8 @@ // the capture mode options first mCaptureModeLabel = new QLabel(i18n("Capture Mode"), this); mCaptureArea = new QComboBox(this); - QString lFullScreenLabel = QApplication::screens().count() == 1 - ? i18n("Full Screen") - : i18n("Full Screen (All Monitors)"); - - 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); + initComboBox(theCaptureModes); connect(mCaptureArea, qOverload(&QComboBox::currentIndexChanged), this, &KSWidget::captureModeChanged); mDelayMsec = new SmartSpinBox(this); @@ -171,6 +156,31 @@ mDelayMsec->setValue (lConfigMgr->captureDelay()); } +void KSWidget::initComboBox(const CaptureModes &theCaptureModes) { + mCaptureArea->clear(); + + bool multipleScreenSetup = QApplication::screens().count() != 1; + + if (theCaptureModes.testFlag(CaptureMode::AllScreens)) { + if (multipleScreenSetup) + mCaptureArea->insertItem(1, i18n("Full Screen (All Monitors)"), static_cast(CaptureMode::AllScreens)); + else + mCaptureArea->insertItem(1, i18n("Full Screen"), static_cast(CaptureMode::AllScreens)); + } + if (theCaptureModes.testFlag(CaptureMode::CurrentScreen) && multipleScreenSetup) + mCaptureArea->insertItem(2, i18n("Current Screen"), static_cast(CaptureMode::CurrentScreen)); + if (theCaptureModes.testFlag(CaptureMode::ActiveWindow)) + mCaptureArea->insertItem(3, i18n("Active Window"), static_cast(CaptureMode::ActiveWindow)); + if (theCaptureModes.testFlag(CaptureMode::WindowUnderCursor)) + mCaptureArea->insertItem(4, i18n("Window Under Cursor"), static_cast(CaptureMode::WindowUnderCursor)); + if (theCaptureModes.testFlag(CaptureMode::TransientWithParent)) { + mTransientWithParentAvailable = true; + } + if (theCaptureModes.testFlag(CaptureMode::RectangularRegion)) { + mCaptureArea->insertItem(5, i18n("Rectangular Region"), static_cast(CaptureMode::RectangularRegion)); + } +} + int KSWidget::imagePaddingWidth() const { int lRightLayoutLeft = 0; @@ -213,11 +223,11 @@ void KSWidget::newScreenshotClicked() { int lDelay = mCaptureOnClick->isChecked() ? -1 : (mDelayMsec->value() * 1000); - auto lMode = static_cast(mCaptureArea->currentData().toInt()); + auto lMode = static_cast(mCaptureArea->currentData().toInt()); if (mTransientWithParentAvailable && - lMode == Spectacle::CaptureMode::WindowUnderCursor && + lMode == CaptureMode::WindowUnderCursor && !(mCaptureTransientOnly->isChecked())) { - lMode = Spectacle::CaptureMode::TransientWithParent; + lMode = CaptureMode::TransientWithParent; } setButtonState(State::Cancel); emit newScreenshotRequest(lMode, lDelay, mMousePointer->isChecked(), mWindowDecorations->isChecked()); @@ -234,31 +244,30 @@ void KSWidget::captureModeChanged(int theIndex) { + // TO DO: Is it a good idea to save the index? The index might change? SpectacleConfig::instance()->setCaptureMode(theIndex); - Spectacle::CaptureMode lCaptureMode = static_cast(mCaptureArea->itemData(theIndex).toInt()); + CaptureMode lCaptureMode = static_cast(mCaptureArea->itemData(theIndex).toInt()); switch(lCaptureMode) { - case Spectacle::CaptureMode::WindowUnderCursor: + case CaptureMode::WindowUnderCursor: mWindowDecorations->setEnabled(true); if (mTransientWithParentAvailable) { mCaptureTransientOnly->setEnabled(true); } else { mCaptureTransientOnly->setEnabled(false); } break; - case Spectacle::CaptureMode::ActiveWindow: + case CaptureMode::ActiveWindow: mWindowDecorations->setEnabled(true); mCaptureTransientOnly->setEnabled(false); break; - case Spectacle::CaptureMode::AllScreens: - case Spectacle::CaptureMode::CurrentScreen: - case Spectacle::CaptureMode::RectangularRegion: + case CaptureMode::AllScreens: + case CaptureMode::CurrentScreen: + case CaptureMode::RectangularRegion: mWindowDecorations->setEnabled(false); mCaptureTransientOnly->setEnabled(false); break; - 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/BackendOptionsPage.h b/src/Gui/SettingsDialog/BackendOptionsPage.h new file mode 100644 --- /dev/null +++ b/src/Gui/SettingsDialog/BackendOptionsPage.h @@ -0,0 +1,41 @@ +#ifndef BACKEND_PAGE_H +#define BACKEND_PAGE_H + +#include "SettingsPage.h" + +#include +#include + +#include +#include + +#include +#include + +class QString; + +class BackendOptionsPage : public SettingsPage { + Q_OBJECT +public: + explicit BackendOptionsPage(QWidget *parent = nullptr); + +public Q_SLOTS: + void saveChanges() override; + void resetChanges() override; + +private: + void initalizeModel(); + void setScreenshotViewLayout(); + + enum class PluginState { + InUse, + NotInUse, + Disabled + }; + + QVBoxLayout* layout; + QButtonGroup* activateButtonGroup; + std::vector> model; +}; + +#endif //BACKEND_PAGE_H \ No newline at end of file diff --git a/src/Gui/SettingsDialog/BackendOptionsPage.cpp b/src/Gui/SettingsDialog/BackendOptionsPage.cpp new file mode 100644 --- /dev/null +++ b/src/Gui/SettingsDialog/BackendOptionsPage.cpp @@ -0,0 +1,140 @@ +#include "BackendOptionsPage.h" + +#include "BackendInterfaces/ScreenshotInterface.h" +#include "SpectacleConfig.h" + +#include +#include + +#include +#include +#include +#include +#include + +BackendOptionsPage::BackendOptionsPage(QWidget *parent) +: SettingsPage {parent}, + layout {new QVBoxLayout {}}, + activateButtonGroup {new QButtonGroup {this}} +{ + KTitleWidget* screenshotBackendLabel = new KTitleWidget {}; + screenshotBackendLabel->setText(i18n("Screenshot Backend")); + screenshotBackendLabel->setLevel(2); + layout->addWidget(screenshotBackendLabel); + + initalizeModel(); + setScreenshotViewLayout(); + + layout->addStretch(); + + setLayout(layout); +} + +void BackendOptionsPage::initalizeModel() +{ + QJsonValue backendName; + QJsonArray backendPlatforms; + PluginState pluginState; + + auto spectacleConfig = SpectacleConfig::instance(); + + for(const auto& screenshotPlugin: spectacleConfig->screenshotPlugins()) { + backendName = screenshotPlugin.metaData[QStringLiteral("Name")]; + backendPlatforms = screenshotPlugin.metaData[QStringLiteral("Platforms")].toArray(); + + auto platformIt = std::find(backendPlatforms.begin(), backendPlatforms.end(), QJsonValue(spectacleConfig->platform())); + + if(platformIt == backendPlatforms.end()) { + pluginState = PluginState::Disabled; + } else { + if(screenshotPlugin.backend) { + pluginState = PluginState::InUse; + } else { + pluginState = PluginState::NotInUse; + } + } + + model.push_back(std::make_tuple(backendName, backendPlatforms, pluginState)); + } +} + +void BackendOptionsPage::setScreenshotViewLayout() +{ + QVBoxLayout* screenshotView = new QVBoxLayout {}; + screenshotView->setSpacing(10); + + for(size_t i = 0; i < model.size(); ++i) { + QWidget* rowWidget = new QWidget {}; + QHBoxLayout* rowWidgetLayout = new QHBoxLayout {}; + rowWidget->setLayout(rowWidgetLayout); + + QPalette pal = QApplication::palette(); + pal.setColor(QPalette::Background, pal.color(QPalette::Mid)); + rowWidget->setAutoFillBackground(true); + rowWidget->setPalette(pal); + + QGraphicsDropShadowEffect *effect = new QGraphicsDropShadowEffect; + effect->setXOffset(3); + effect->setYOffset(3); + effect->setColor(pal.color(QPalette::Dark)); + rowWidget->setGraphicsEffect(effect); + + QLabel* backendName = new QLabel { std::get<0>(model[i]).toString() }; + + QString backendList; + for(const QJsonValue& value: std::get<1>(model[i])) { + backendList += value.toString(); + backendList += QStringLiteral(","); + } + backendList.remove(backendList.size() - 1, 1); + QLabel *backendPlatform = new QLabel { backendList }; + + QPushButton* activateButton = new QPushButton {}; + activateButton->setCheckable(true); + activateButton->setFocusPolicy(Qt::NoFocus); + activateButton->setProperty("pluginName", std::get<0>(model[i])); + + activateButtonGroup->addButton(activateButton); + connect(activateButtonGroup, QOverload::of(&QButtonGroup::buttonToggled), [=](QAbstractButton *button, bool checked) { + if(checked) { + std::get<2>(model[i]) = PluginState::InUse; + button->setText(QStringLiteral("In Use")); + } else { + std::get<2>(model[i]) = PluginState::NotInUse; + button->setText(QStringLiteral("Activate")); + } + }); + + switch(std::get<2>(model[i])) { + case PluginState::InUse: activateButton->setText(QStringLiteral("In Use")); activateButton->setChecked(true); break; + case PluginState::NotInUse: activateButton->setText(QStringLiteral("Activate")); break; + case PluginState::Disabled: activateButton->setText(QStringLiteral("Activate")); activateButton->setEnabled(false); break; + } + + rowWidgetLayout->addWidget(backendName); + rowWidgetLayout->addStretch(); + rowWidgetLayout->addWidget(backendPlatform); + rowWidgetLayout->addStretch(); + rowWidgetLayout->addWidget(activateButton); + + screenshotView->addWidget(rowWidget); + } + + layout->addLayout(screenshotView); +} + +void BackendOptionsPage::saveChanges() +{ + auto spectacleConfig = SpectacleConfig::instance(); + + if(!activateButtonGroup->checkedButton()) { + return; + } + + spectacleConfig->findScreenshotBackend(activateButtonGroup->checkedButton()->property("pluginName").toString()); +} + +void BackendOptionsPage::resetChanges() +{ + //Not sure yet +} \ No newline at end of file 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,7 +19,7 @@ #include "SaveOptionsPage.h" -#include "SpectacleCommon.h" +#include "BackendInterfaces/ScreenshotInterface.h" #include "SpectacleConfig.h" #include "ExportManager.h" @@ -206,15 +206,15 @@ { auto lExportManager = ExportManager::instance(); lExportManager->setWindowTitle(QStringLiteral("Spectacle")); - Spectacle::CaptureMode lOldMode = lExportManager->captureMode(); + 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); + bool lSwitchGrabMode = !(lOldMode == CaptureMode::ActiveWindow || + lOldMode == CaptureMode::TransientWithParent || + lOldMode == CaptureMode::WindowUnderCursor); if (lSwitchGrabMode) { - lExportManager->setCaptureMode(Spectacle::CaptureMode::ActiveWindow); + lExportManager->setCaptureMode(CaptureMode::ActiveWindow); } const QString lFileName = lExportManager->formatFilename(mSaveNameFormat->text()); mPreviewLabel->setText(xi18nc("@info", "%1.%2", lFileName, mSaveImageFormat->currentText().toLower())); diff --git a/src/Gui/SettingsDialog/SettingsDialog.cpp b/src/Gui/SettingsDialog/SettingsDialog.cpp --- a/src/Gui/SettingsDialog/SettingsDialog.cpp +++ b/src/Gui/SettingsDialog/SettingsDialog.cpp @@ -21,6 +21,7 @@ #include "GeneralOptionsPage.h" #include "SaveOptionsPage.h" +#include "BackendOptionsPage.h" #include "ShortcutsOptionsPage.h" #include @@ -55,6 +56,12 @@ saveOptions->setIcon(QIcon::fromTheme(QStringLiteral("document-save"))); addPage(saveOptions); mPages.insert(saveOptions); + + KPageWidgetItem *backendOptions = new KPageWidgetItem(new BackendOptionsPage(this), i18n("Backend")); + backendOptions->setHeader(i18n("Backend")); + backendOptions->setIcon(QIcon::fromTheme(QStringLiteral("backend"))); + addPage(backendOptions); + mPages.insert(backendOptions); KPageWidgetItem *shortcutOptions = new KPageWidgetItem(new ShortcutsOptionsPage(this), i18n("Shortcuts")); shortcutOptions->setHeader(i18n("Shortcuts")); diff --git a/src/Main.cpp b/src/Main.cpp --- a/src/Main.cpp +++ b/src/Main.cpp @@ -18,9 +18,9 @@ */ #include "Config.h" -#include "SpectacleCommon.h" #include "SpectacleCore.h" #include "SpectacleDBusAdapter.h" +#include "BackendInterfaces/ScreenshotInterface.h" #include #include @@ -33,7 +33,6 @@ int main(int argc, char **argv) { // set up the application - QApplication lApp(argc, argv); lApp.setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true); @@ -79,17 +78,17 @@ // extract the capture mode - Spectacle::CaptureMode lCaptureMode = Spectacle::CaptureMode::AllScreens; + CaptureMode lCaptureMode = CaptureMode::AllScreens; if (lCmdLineParser.isSet(QStringLiteral("current"))) { - lCaptureMode = Spectacle::CaptureMode::CurrentScreen; + lCaptureMode = CaptureMode::CurrentScreen; } else if (lCmdLineParser.isSet(QStringLiteral("activewindow"))) { - lCaptureMode = Spectacle::CaptureMode::ActiveWindow; + lCaptureMode = CaptureMode::ActiveWindow; } else if (lCmdLineParser.isSet(QStringLiteral("region"))) { - lCaptureMode = Spectacle::CaptureMode::RectangularRegion; + lCaptureMode = CaptureMode::RectangularRegion; } else if (lCmdLineParser.isSet(QStringLiteral("windowundercursor"))) { - lCaptureMode = Spectacle::CaptureMode::TransientWithParent; + lCaptureMode = CaptureMode::TransientWithParent; } else if (lCmdLineParser.isSet(QStringLiteral("transientonly"))) { - lCaptureMode = Spectacle::CaptureMode::WindowUnderCursor; + lCaptureMode = CaptureMode::WindowUnderCursor; } // are we running in background or dbus mode? diff --git a/src/Platforms/Platform.h b/src/Platforms/Platform.h deleted file mode 100644 --- a/src/Platforms/Platform.h +++ /dev/null @@ -1,70 +0,0 @@ -/* 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(Platform::ShutterMode theShutterMode, Platform::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 deleted file mode 100644 --- a/src/Platforms/Platform.cpp +++ /dev/null @@ -1,26 +0,0 @@ -/* 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 deleted file mode 100644 --- a/src/Platforms/PlatformKWinWayland.h +++ /dev/null @@ -1,48 +0,0 @@ -/* 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(Platform::ShutterMode theShutterMode, Platform::GrabMode theGrabMode, bool theIncludePointer, bool theIncludeDecorations) override final; - - private: - - void startReadImage(int theReadPipe); - template void doGrabHelper(const QString &theGrabMethod, ArgType theArgument); - template void callDBus(const QString &theGrabMethod, ArgType theArgument, int theWriteFile); -}; diff --git a/src/Platforms/PlatformLoader.h b/src/Platforms/PlatformLoader.h deleted file mode 100644 --- a/src/Platforms/PlatformLoader.h +++ /dev/null @@ -1,28 +0,0 @@ -/* 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 deleted file mode 100644 --- a/src/Platforms/PlatformLoader.cpp +++ /dev/null @@ -1,52 +0,0 @@ -/* 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 deleted file mode 100644 --- a/src/Platforms/PlatformNull.h +++ /dev/null @@ -1,42 +0,0 @@ -/* 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(Platform::ShutterMode theShutterMode, Platform::GrabMode theGrabMode, bool theIncludePointer, bool theIncludeDecorations) override final; -}; diff --git a/src/Platforms/PlatformNull.cpp b/src/Platforms/PlatformNull.cpp deleted file mode 100644 --- a/src/Platforms/PlatformNull.cpp +++ /dev/null @@ -1,54 +0,0 @@ -/* 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/SpectacleCommon.h b/src/SpectacleCommon.h deleted file mode 100644 --- a/src/SpectacleCommon.h +++ /dev/null @@ -1,34 +0,0 @@ -/* 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/SpectacleConfig.h b/src/SpectacleConfig.h --- a/src/SpectacleConfig.h +++ b/src/SpectacleConfig.h @@ -20,6 +20,8 @@ #ifndef SPECTACLECONFIG_H #define SPECTACLECONFIG_H +#include "BackendInterfaces/ScreenshotInterface.h" + #include #include #include @@ -33,6 +35,12 @@ Save }; +struct ScreenshotPlugin { + QString fileName; + QJsonObject metaData; + ScreenshotInterface *backend; +}; + class SpectacleConfig : public QObject { Q_OBJECT @@ -43,6 +51,14 @@ static SpectacleConfig* instance(); + bool findScreenshotBackend(const QString &name); + + const QString& platform() const; + + ScreenshotInterface* screenshotBackend() const; + + const std::vector& screenshotPlugins() const; + QString defaultFilename() const; QString defaultTimestampTemplate() const; @@ -65,9 +81,10 @@ SpectacleConfig(SpectacleConfig const&) = delete; void operator= (SpectacleConfig const&) = delete; - // everything else + void findPlugins(); + void selectScreenshotBackend(); - public Q_SLOTS: +public Q_SLOTS: QUrl lastSaveAsFile() const; void setLastSaveAsFile(const QUrl &location); @@ -131,15 +148,35 @@ QString saveImageFormat() const; void setSaveImageFormat(const QString &saveFmt); + + //Backend Settings + QString screenshotBackendName() const; + void setScreenshotBackendName(const QString& name); PrintKeyActionRunning printKeyActionRunning() const; void setPrintKeyActionRunning (PrintKeyActionRunning action); + + Q_SIGNALS: + + void screenshotBackendNameChanged(const QString& name); + void screenshotBackendChanged(ScreenshotInterface* screenshotBackend); private: + + QString screenshotXcbBackendName() const; + void setScreenshotXcbBackendName(const QString& name); + + QString screenshotWaylandBackendName() const; + void setScreenshotWaylandBackendName(const QString& name); + + bool setScreenshotBackend(ScreenshotPlugin &newPlugin, ScreenshotPlugin *oldPlugin); KSharedConfigPtr mConfig; KConfigGroup mGeneralConfig; KConfigGroup mGuiConfig; + + QString mPlatform; + std::vector mScreenshotPlugins; }; #endif // SPECTACLECONFIG_H diff --git a/src/SpectacleConfig.cpp b/src/SpectacleConfig.cpp --- a/src/SpectacleConfig.cpp +++ b/src/SpectacleConfig.cpp @@ -17,15 +17,45 @@ * Boston, MA 02110-1301, USA. */ +#include "Config.h" #include "SpectacleConfig.h" #include #include - +#include +#include +#include + +#include +#include +#include + +namespace { + static QString determinePlatform() { + QString platform = QString(); + + // 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) { + platform = QStringLiteral("Wayland"); + } + + // Try checking if we're running under X11 now + #ifdef XCB_FOUND + if (KWindowSystem::isPlatformX11()) { + platform = QStringLiteral("Xcb");; + } + #endif + + return platform; + } +} SpectacleConfig::SpectacleConfig(QObject *parent) : - QObject(parent) + QObject(parent), + mPlatform(determinePlatform()) { mConfig = KSharedConfig::openConfig(QStringLiteral("spectaclerc")); mGeneralConfig = KConfigGroup(mConfig, "General"); @@ -66,17 +96,141 @@ action->setObjectName(QStringLiteral("RectangularRegionScreenShot")); shortCutActions->addAction(action->objectName(), action); } + + findPlugins(); + selectScreenshotBackend(); } SpectacleConfig::~SpectacleConfig() {} +void SpectacleConfig::findPlugins() +{ + QDir pluginsDir { qApp->applicationDirPath() }; + pluginsDir.cd(QStringLiteral("build/bin")); + + // Iterate over all plugins and save their filepath and their metadata in a pair + const auto entryList = pluginsDir.entryList(QDir::Files); + for (const QString &fileName : entryList) { + QPluginLoader loader(pluginsDir.absoluteFilePath(fileName)); + QJsonObject metaData = QJsonObject(loader.metaData())[QStringLiteral("MetaData")].toObject(); + + if (metaData[QStringLiteral("Type")].toString() == QStringLiteral("Screenshot Backend")) { + mScreenshotPlugins.push_back({fileName, metaData, nullptr}); + } + } + + // Sort plugins after recommendation + std::sort(mScreenshotPlugins.begin(), mScreenshotPlugins.end(), [](const ScreenshotPlugin &plugin1, const ScreenshotPlugin &plugin2) { + return plugin1.metaData[QStringLiteral("Recommended")].toInt() > plugin2.metaData[QStringLiteral("Recommended")].toInt(); + }); +} + +void SpectacleConfig::selectScreenshotBackend() +{ + // Find default plugin + if (findScreenshotBackend(screenshotBackendName())) { + return; + } + + // Find other suitable plugin + for(auto& plugin : mScreenshotPlugins) { + if(!plugin.metaData[QStringLiteral("Platforms")].toArray().contains(mPlatform)) { + continue; + } + + if(setScreenshotBackend(plugin, nullptr)) { + break; + } + } +} + +bool SpectacleConfig::findScreenshotBackend(const QString& name) +{ + //Find old plugin + auto oldPluginIterator = std::find_if(mScreenshotPlugins.begin(), mScreenshotPlugins.end(), [&name](const ScreenshotPlugin &plugin) { + return plugin.backend; + }); + + // Find new plugin + auto newPluginIterator = std::find_if(mScreenshotPlugins.begin(), mScreenshotPlugins.end(), [&name](const ScreenshotPlugin &plugin) { + return plugin.metaData[QStringLiteral("Name")].toString() == name; + }); + + if(newPluginIterator == mScreenshotPlugins.end()) { + return false; + } + + if(oldPluginIterator != mScreenshotPlugins.end()) { + if(setScreenshotBackend(*newPluginIterator, &(*oldPluginIterator))) { + return true; + } + } else { + if(setScreenshotBackend(*newPluginIterator, nullptr)) { + return true; + } + } + return false; +} + +//TO DO: std::optional instead of pointer in C++17 +bool SpectacleConfig::setScreenshotBackend(ScreenshotPlugin& newPlugin, ScreenshotPlugin* oldPlugin) +{ + //Check if there is a change + if(oldPlugin && newPlugin.fileName == (*oldPlugin).fileName) { + return true; + } + + // Test if plugin can be used (runtime checks) + QPluginLoader newLoader { newPlugin.fileName }; + newPlugin.backend = qobject_cast(newLoader.instance()); + + if(!newPlugin.backend->requirementsComplied()) { + newPlugin.backend = nullptr; + newLoader.unload(); + return false; + } + + // If there was an old plugin, unload it + if(oldPlugin) { + QPluginLoader oldLoader { oldPlugin->fileName }; + oldLoader.unload(); + oldPlugin->backend = nullptr; + } + + // Update active screenshot Backend and filename + setScreenshotBackendName(newPlugin.metaData[QStringLiteral("Name")].toString()); + emit screenshotBackendChanged(newPlugin.backend); + return true; +} + SpectacleConfig* SpectacleConfig::instance() { static SpectacleConfig instance; return &instance; } +const QString& SpectacleConfig::platform() const +{ + return mPlatform; +} + +ScreenshotInterface* SpectacleConfig::screenshotBackend() const +{ + auto pluginIterator = std::find_if(mScreenshotPlugins.begin(), mScreenshotPlugins.end(), [](const ScreenshotPlugin &plugin) { + return plugin.backend; + }); + + if(pluginIterator != mScreenshotPlugins.end()) { + return (*pluginIterator).backend; + } + return nullptr; +} + +const std::vector& SpectacleConfig::screenshotPlugins() const { + return mScreenshotPlugins; +} + QString SpectacleConfig::defaultFilename() const { return QStringLiteral("Screenshot"); @@ -385,6 +539,45 @@ mGeneralConfig.sync(); } +QString SpectacleConfig::screenshotBackendName() const +{ + if(mPlatform == QStringLiteral("Xcb")) { + return screenshotXcbBackendName(); + } else if(mPlatform == QStringLiteral("Wayland")) { + return screenshotWaylandBackendName(); + } + return QString(); +} + +void SpectacleConfig::setScreenshotBackendName(const QString& name) +{ + if(mPlatform == QStringLiteral("Xcb")) { + setScreenshotXcbBackendName(name); + } else if(mPlatform == QStringLiteral("Wayland")) { + setScreenshotWaylandBackendName(name); + } +} + +QString SpectacleConfig::screenshotXcbBackendName() const { + return mGeneralConfig.readEntry(QStringLiteral("screenshotXcbBackendName"), QString()); +} + +void SpectacleConfig::setScreenshotXcbBackendName(const QString& name) { + mGeneralConfig.writeEntry(QStringLiteral("screenshotXcbBackendName"), name); + mGeneralConfig.sync(); + emit screenshotBackendNameChanged(name); +} + +QString SpectacleConfig::screenshotWaylandBackendName() const { + return mGeneralConfig.readEntry(QStringLiteral("screenshotWaylandBackendName"), QString()); +} + +void SpectacleConfig::setScreenshotWaylandBackendName(const QString& name) { + mGeneralConfig.writeEntry(QStringLiteral("screenshotWaylandBackendName"), name); + mGeneralConfig.sync(); + emit screenshotBackendNameChanged(name); +} + SpectacleConfig::PrintKeyActionRunning SpectacleConfig::printKeyActionRunning() const { mConfig->reparseConfiguration(); diff --git a/src/SpectacleCore.h b/src/SpectacleCore.h --- a/src/SpectacleCore.h +++ b/src/SpectacleCore.h @@ -23,10 +23,10 @@ #include +#include "BackendInterfaces/ScreenshotInterface.h" #include "ExportManager.h" #include "Gui/KSMainWindow.h" #include "QuickEditor/QuickEditor.h" -#include "Platforms/PlatformLoader.h" #include @@ -46,7 +46,7 @@ }; explicit SpectacleCore(StartMode theStartMode, - Spectacle::CaptureMode theCaptureMode, + CaptureMode theCaptureMode, QString &theSaveFileName, qint64 theDelayMsec, bool theNotifyOnGrab, @@ -66,7 +66,7 @@ public Q_SLOTS: - void takeNewScreenshot(Spectacle::CaptureMode theCaptureMode, int theTimeout, bool theIncludePointer, bool theIncludeDecorations); + void takeNewScreenshot(CaptureMode theCaptureMode, int theTimeout, bool theIncludePointer, bool theIncludeDecorations); void showErrorMessage(const QString &theErrString); void screenshotUpdated(const QPixmap &thePixmap); void screenshotFailed(); @@ -76,17 +76,17 @@ void doCopyPath(const QUrl &savedAt); private: - - + + void updateScreenshotBackendConnections(); + void initGui(bool theIncludePointer, bool theIncludeDecorations); - Platform::GrabMode toPlatformGrabMode(Spectacle::CaptureMode theCaptureMode); void setUpShortcuts(); StartMode mStartMode; bool mNotify; QString mFileNameString; QUrl mFileNameUrl; - PlatformPtr mPlatform; + ScreenshotInterface* mScreenshotPlugin; MainWindowPtr mMainWindow; EditorPtr mQuickEditor; bool mIsGuiInited; diff --git a/src/SpectacleCore.cpp b/src/SpectacleCore.cpp --- a/src/SpectacleCore.cpp +++ b/src/SpectacleCore.cpp @@ -39,16 +39,16 @@ #include SpectacleCore::SpectacleCore(StartMode theStartMode, - Spectacle::CaptureMode theCaptureMode, + CaptureMode theCaptureMode, QString &theSaveFileName, qint64 theDelayMsec, bool theNotifyOnGrab, bool theCopyToClipboard, QObject *parent) : QObject(parent), mStartMode(theStartMode), mNotify(theNotifyOnGrab), - mPlatform(loadPlatform()), + mScreenshotPlugin(nullptr), mMainWindow(nullptr), mIsGuiInited(false), mCopyToClipboard(theCopyToClipboard) @@ -62,20 +62,30 @@ } setFilename(theSaveFileName); } - + + auto lSpectacleConfig = SpectacleConfig::instance(); + + //Get screenshot backend + mScreenshotPlugin = lSpectacleConfig->screenshotBackend(); + if(mScreenshotPlugin) { + updateScreenshotBackendConnections(); + } else { + //TO DO: No backend found -> possibility to install a new backend + emit errorMessage(QStringLiteral("No backend found")); + return; + } + + //connection if screenshot backend has changed + connect(lSpectacleConfig, &SpectacleConfig::screenshotBackendChanged, this, [this](ScreenshotInterface* screenshotBackend) { + mScreenshotPlugin = screenshotBackend; + updateScreenshotBackendConnections(); + }); + // 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); - auto lImmediateAvailable = mPlatform->supportedShutterModes().testFlag(Platform::ShutterMode::Immediate); - auto lOnClickAvailable = mPlatform->supportedShutterModes().testFlag(Platform::ShutterMode::OnClick); - if ((!lOnClickAvailable) && (theDelayMsec < 0)) { - theDelayMsec = 0; - } // reset last region if it should not be remembered across restarts - auto lSpectacleConfig = SpectacleConfig::instance(); if(!lSpectacleConfig->alwaysRememberRegion()) { lSpectacleConfig->setCropRegion(QRect()); } @@ -86,19 +96,25 @@ 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); switch (theStartMode) { case StartMode::DBus: break; case StartMode::Background: { + //TO Do: Print error message, if there is no screenshot backend + auto lImmediateAvailable = mScreenshotPlugin->supportedShutterModes().testFlag(ShutterMode::Immediate); + + auto lOnClickAvailable = mScreenshotPlugin->supportedShutterModes().testFlag(ShutterMode::OnClick); + if ((!lOnClickAvailable) && (theDelayMsec < 0)) { + theDelayMsec = 0; + } + auto lMsec = (KWindowSystem::compositingActive() ? 200 : 50) + theDelayMsec; - auto lShutterMode = lImmediateAvailable ? Platform::ShutterMode::Immediate : Platform::ShutterMode::OnClick; + auto lShutterMode = lImmediateAvailable ? ShutterMode::Immediate : ShutterMode::OnClick; auto lIncludePointer = lGuiConfig.readEntry("includePointer", true); auto lIncludeDecorations = lGuiConfig.readEntry("includeDecorations", true); - const Platform::GrabMode lCaptureMode = toPlatformGrabMode(theCaptureMode); - QTimer::singleShot(lMsec, this, [ this, lCaptureMode, lShutterMode, lIncludePointer, lIncludeDecorations ]() { - mPlatform->doGrab(lShutterMode, lCaptureMode, lIncludePointer, lIncludeDecorations); + QTimer::singleShot(lMsec, this, [ this, theCaptureMode, lShutterMode, lIncludePointer, lIncludeDecorations ]() { + mScreenshotPlugin->doGrab(lShutterMode, theCaptureMode, lIncludePointer, lIncludeDecorations); }); } break; @@ -109,6 +125,20 @@ setUpShortcuts(); } +void SpectacleCore::updateScreenshotBackendConnections() { + connect(mScreenshotPlugin, &ScreenshotInterface::newScreenshotTaken, this, &SpectacleCore::screenshotUpdated); + connect(mScreenshotPlugin, &ScreenshotInterface::newScreenshotFailed, this, &SpectacleCore::screenshotFailed); + //TO DO: What is this window title thing about? + auto lExportManager = ExportManager::instance(); + connect(mScreenshotPlugin, &ScreenshotInterface::windowTitleChanged, lExportManager, &ExportManager::setWindowTitle); + + // Update capture modes and shutter modes + if(mMainWindow) { + mMainWindow->setCaptureModes(mScreenshotPlugin->supportedCaptureModes()); + mMainWindow->setShutterModes(mScreenshotPlugin->supportedShutterModes()); + } +} + void SpectacleCore::setUpShortcuts() { SpectacleConfig* config = SpectacleConfig::instance(); @@ -158,10 +188,11 @@ using Actions = SpectacleConfig::PrintKeyActionRunning; switch (SpectacleConfig::instance()->printKeyActionRunning()) { 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, [this, lShutterMode, lGrabMode, lIncludePointer, lIncludeDecorations]() { - mPlatform->doGrab(lShutterMode, lGrabMode, lIncludePointer, lIncludeDecorations); + //Not sure? + auto lShutterMode = mScreenshotPlugin->supportedShutterModes().testFlag(ShutterMode::Immediate) ? ShutterMode::Immediate : ShutterMode::OnClick; + //Not sure? + QTimer::singleShot(KWindowSystem::compositingActive() ? 200 : 50, this, [this, lShutterMode, lIncludePointer, lIncludeDecorations]() { + mScreenshotPlugin->doGrab(lShutterMode, ExportManager::instance()->captureMode(), lIncludePointer, lIncludeDecorations); }); break; } @@ -180,16 +211,16 @@ } } -void SpectacleCore::takeNewScreenshot(Spectacle::CaptureMode theCaptureMode, +void SpectacleCore::takeNewScreenshot(CaptureMode theCaptureMode, int theTimeout, bool theIncludePointer, bool theIncludeDecorations) { ExportManager::instance()->setCaptureMode(theCaptureMode); - auto lGrabMode = toPlatformGrabMode(theCaptureMode); if (theTimeout < 0) { - mPlatform->doGrab(Platform::ShutterMode::OnClick, lGrabMode, theIncludePointer, theIncludeDecorations); + //not sure + mScreenshotPlugin->doGrab(ShutterMode::OnClick, theCaptureMode, theIncludePointer, theIncludeDecorations); return; } @@ -200,8 +231,9 @@ // milliseconds is a good amount of wait time. auto lMsec = KWindowSystem::compositingActive() ? 200 : 50; - QTimer::singleShot(theTimeout + lMsec, this, [this, lGrabMode, theIncludePointer, theIncludeDecorations]() { - mPlatform->doGrab(Platform::ShutterMode::Immediate, lGrabMode, theIncludePointer, theIncludeDecorations); + QTimer::singleShot(theTimeout + lMsec, this, [this, theCaptureMode, theIncludePointer, theIncludeDecorations]() { + //not sure? + mScreenshotPlugin->doGrab(ShutterMode::Immediate, theCaptureMode, theIncludePointer, theIncludeDecorations); }); } @@ -221,7 +253,7 @@ // 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 (lExportManager->captureMode() == CaptureMode::RectangularRegion) { if(!mQuickEditor) { mQuickEditor = std::make_unique(thePixmap); connect(mQuickEditor.get(), &QuickEditor::grabDone, this, &SpectacleCore::screenshotUpdated); @@ -267,7 +299,7 @@ void SpectacleCore::screenshotFailed() { - if (ExportManager::instance()->captureMode() == Spectacle::CaptureMode::RectangularRegion && mQuickEditor) { + if (ExportManager::instance()->captureMode() == CaptureMode::RectangularRegion && mQuickEditor) { mQuickEditor->hide(); mQuickEditor.reset(nullptr); } @@ -291,24 +323,22 @@ KNotification *lNotify = new KNotification(QStringLiteral("newScreenshotSaved")); switch(ExportManager::instance()->captureMode()) { - case Spectacle::CaptureMode::AllScreens: + case CaptureMode::AllScreens: lNotify->setTitle(i18nc("The entire screen area was captured, heading", "Full Screen Captured")); break; - case Spectacle::CaptureMode::CurrentScreen: + case CaptureMode::CurrentScreen: lNotify->setTitle(i18nc("The current screen was captured, heading", "Current Screen Captured")); break; - case Spectacle::CaptureMode::ActiveWindow: + case CaptureMode::ActiveWindow: lNotify->setTitle(i18nc("The active window was captured, heading", "Active Window Captured")); break; - case Spectacle::CaptureMode::WindowUnderCursor: - case Spectacle::CaptureMode::TransientWithParent: + case CaptureMode::WindowUnderCursor: + case CaptureMode::TransientWithParent: lNotify->setTitle(i18nc("The window under the mouse was captured, heading", "Window Under Cursor Captured")); break; - case Spectacle::CaptureMode::RectangularRegion: + case CaptureMode::RectangularRegion: lNotify->setTitle(i18nc("A rectangular region was captured, heading", "Rectangular Region Captured")); break; - case Spectacle::CaptureMode::InvalidChoice: - break; } // a speaking message is prettier than a URL, special case for copy to clipboard and the default pictures location @@ -361,43 +391,20 @@ lDragHandler->exec(Qt::CopyAction); } -// Private - -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 (!mIsGuiInited) { - mMainWindow = std::make_unique(mPlatform->supportedGrabModes(), mPlatform->supportedShutterModes()); + mMainWindow = std::make_unique(mScreenshotPlugin->supportedCaptureModes(), mScreenshotPlugin->supportedShutterModes()); connect(mMainWindow.get(), &KSMainWindow::newScreenshotRequest, this, &SpectacleCore::takeNewScreenshot); connect(mMainWindow.get(), &KSMainWindow::dragAndDropRequest, this, &SpectacleCore::doStartDragAndDrop); mIsGuiInited = true; - - auto lShutterMode = mPlatform->supportedShutterModes().testFlag(Platform::ShutterMode::Immediate) ? Platform::ShutterMode::Immediate : Platform::ShutterMode::OnClick; - auto lGrabMode = toPlatformGrabMode(ExportManager::instance()->captureMode()); - - QTimer::singleShot(0, this, [this, lShutterMode, lGrabMode, theIncludePointer, theIncludeDecorations]() { - mPlatform->doGrab(lShutterMode, lGrabMode, theIncludePointer, theIncludeDecorations); + + auto lShutterMode = mScreenshotPlugin->supportedShutterModes().testFlag(ShutterMode::Immediate) ? ShutterMode::Immediate : ShutterMode::OnClick; + + QTimer::singleShot(0, this, [this, lShutterMode, theIncludePointer, theIncludeDecorations]() { + mScreenshotPlugin->doGrab(lShutterMode, ExportManager::instance()->captureMode(), theIncludePointer, theIncludeDecorations); }); } } diff --git a/src/SpectacleDBusAdapter.cpp b/src/SpectacleDBusAdapter.cpp --- a/src/SpectacleDBusAdapter.cpp +++ b/src/SpectacleDBusAdapter.cpp @@ -20,7 +20,6 @@ */ #include "SpectacleDBusAdapter.h" -#include "SpectacleCommon.h" SpectacleDBusAdapter::SpectacleDBusAdapter(SpectacleCore *parent) : QDBusAbstractAdaptor(parent) @@ -40,25 +39,25 @@ Q_NOREPLY void SpectacleDBusAdapter::FullScreen(bool includeMousePointer) { - parent()->takeNewScreenshot(Spectacle::CaptureMode::AllScreens, 0, includeMousePointer, true); + parent()->takeNewScreenshot(CaptureMode::AllScreens, 0, includeMousePointer, true); } Q_NOREPLY void SpectacleDBusAdapter::CurrentScreen(bool includeMousePointer) { - parent()->takeNewScreenshot(Spectacle::CaptureMode::CurrentScreen, 0, includeMousePointer, true); + parent()->takeNewScreenshot(CaptureMode::CurrentScreen, 0, includeMousePointer, true); } Q_NOREPLY void SpectacleDBusAdapter::ActiveWindow(bool includeWindowDecorations, bool includeMousePointer) { - parent()->takeNewScreenshot(Spectacle::CaptureMode::ActiveWindow, 0, includeMousePointer, includeWindowDecorations); + parent()->takeNewScreenshot(CaptureMode::ActiveWindow, 0, includeMousePointer, includeWindowDecorations); } Q_NOREPLY void SpectacleDBusAdapter::WindowUnderCursor(bool includeWindowDecorations, bool includeMousePointer) { - parent()->takeNewScreenshot(Spectacle::CaptureMode::WindowUnderCursor, 0, includeMousePointer, includeWindowDecorations); + parent()->takeNewScreenshot(CaptureMode::WindowUnderCursor, 0, includeMousePointer, includeWindowDecorations); } Q_NOREPLY void SpectacleDBusAdapter::RectangularRegion(bool includeMousePointer) { - parent()->takeNewScreenshot(Spectacle::CaptureMode::RectangularRegion, 0, includeMousePointer, false); + parent()->takeNewScreenshot(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/Platforms/Platform.cpp + ../src/ExportManager.cpp ../src/SpectacleConfig.cpp TEST_NAME "filename_test" LINK_LIBRARIES Qt5::Test Qt5::PrintSupport KF5::I18n KF5::ConfigCore KF5::GlobalAccel KF5::KIOCore KF5::WindowSystem KF5::XmlGui diff --git a/tests/FilenameTest.cpp b/tests/FilenameTest.cpp --- a/tests/FilenameTest.cpp +++ b/tests/FilenameTest.cpp @@ -3,8 +3,8 @@ #include #include -#include "SpectacleCommon.h" #include "ExportManager.h" +#include "BackendInterfaces/ScreenshotInterface.h" class FilenameTest: public QObject { @@ -54,11 +54,11 @@ void FilenameTest::testWindowTitle() { - mExportManager->setCaptureMode(Spectacle::CaptureMode::ActiveWindow); + mExportManager->setCaptureMode(CaptureMode::ActiveWindow); QCOMPARE(mExportManager->formatFilename(QStringLiteral("%T")), QStringLiteral("Spectacle")); QCOMPARE(mExportManager->formatFilename(QStringLiteral("Before%TAfter")), QStringLiteral("BeforeSpectacleAfter")); - mExportManager->setCaptureMode(Spectacle::CaptureMode::AllScreens); + mExportManager->setCaptureMode(CaptureMode::AllScreens); //Empty String produces Screenshot QCOMPARE(mExportManager->formatFilename(QStringLiteral("%T")), QStringLiteral("Screenshot")); QCOMPARE(mExportManager->formatFilename(QStringLiteral("Before%TAfter")), QStringLiteral("BeforeAfter")); @@ -84,10 +84,10 @@ void FilenameTest::testCombined() { - mExportManager->setCaptureMode(Spectacle::CaptureMode::ActiveWindow); + mExportManager->setCaptureMode(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")); - mExportManager->setCaptureMode(Spectacle::CaptureMode::AllScreens); + mExportManager->setCaptureMode(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")); }