Git commit 0b3419e95198831e167538696285b7927c9b150d by Noah Davis, on behalf of Kristen McWilliam. Committed on 22/10/2023 at 15:01. Pushed by ndavis into branch 'master'.
feat: add window shadows to the capture options Right now screenshots of windows always have drop shadows. This change makes the shadows optional. Resolves a 7-year-old bug report in conjunction with an incoming change to kwin. BUG: 372408 M +12 -0 dbus/org.kde.Spectacle.xml M +3 -0 doc/index.docbook M +5 -22 src/CommandLineOptions.h M +10 -0 src/Gui/CaptureSettingsColumn.qml M +13 -0 src/Gui/OptionsMenu.cpp M +1 -0 src/Gui/OptionsMenu.h M +4 -0 src/Gui/SettingsDialog/spectacle.kcfg M +2 -1 src/Platforms/ImagePlatform.h M +7 -1 src/Platforms/ImagePlatformKWin.cpp M +3 -1 src/Platforms/ImagePlatformKWin.h M +60 -36 src/Platforms/ImagePlatformXcb.cpp M +21 -6 src/Platforms/ImagePlatformXcb.h M +2 -1 src/Platforms/PlatformNull.cpp M +5 -1 src/Platforms/PlatformNull.h M +13 -11 src/SpectacleCore.cpp M +4 -3 src/SpectacleCore.h M +6 -4 src/SpectacleDBusAdapter.cpp M +2 -2 src/SpectacleDBusAdapter.h https://invent.kde.org/graphics/spectacle/-/commit/0b3419e95198831e167538696285b7927c9b150d diff --git a/dbus/org.kde.Spectacle.xml b/dbus/org.kde.Spectacle.xml index f37755071..6a5ab1cf1 100644 --- a/dbus/org.kde.Spectacle.xml +++ b/dbus/org.kde.Spectacle.xml @@ -61,6 +61,12 @@ <doc:para>Available parameters: -1 - uses the value set in the option 'include mouse pointer', 0 - doesn't include the mouse pointer, 1 - includes the mouse pointer</doc:para> </doc:doc> </arg> + <arg name="includeWindowShadow" direction="in" type="i"> + <doc:doc> + <doc:summary>Whether to include the window shadow. Depends on the user set option 'include window shadow' or the parameter sent via dbus.</doc:summary> + <doc:para>Available parameters: -1 - uses the value set in the option 'include window shadow', 0 - doesn't include window shadow, 1 - includes window shadow</doc:para> + </doc:doc> + </arg> <doc:doc> <doc:description> <doc:para>Takes a screenshot of the window that currently has window focus.</doc:para> @@ -82,6 +88,12 @@ <doc:para>Available parameters: -1 - uses the value set in the option 'include mouse pointer', 0 - doesn't include the mouse pointer, 1 - includes the mouse pointer</doc:para> </doc:doc> </arg> + <arg name="includeWindowShadow" direction="in" type="i"> + <doc:doc> + <doc:summary>Whether to include the window shadow. Depends on the user set option 'include window shadow' or the parameter sent via dbus.</doc:summary> + <doc:para>Available parameters: -1 - uses the value set in the option 'include window shadow', 0 - doesn't include window shadow, 1 - includes window shadow</doc:para> + </doc:doc> + </arg> <doc:doc> <doc:description> <doc:para>Takes a screenshot of the window that is currently under the mouse cursor.</doc:para> diff --git a/doc/index.docbook b/doc/index.docbook index 1ecd6b258..3c28649d3 100644 --- a/doc/index.docbook +++ b/doc/index.docbook @@ -179,6 +179,9 @@ <listitem> <para>The <guilabel>Include window titlebar and borders</guilabel> option is only enabled when either the <guilabel>Active Window</guilabel> mode or the <guilabel>Window Under Cursor</guilabel> mode is selected in the <guilabel>Area</guilabel> combo-box. Checking this option includes the window borders and decoration in the screenshot, while unchecking it gives an image of only the window contents.</para> </listitem> + <listitem> + <para>The <guilabel>Include window shadow</guilabel> option is only enabled when either the <guilabel>Active Window</guilabel> mode or the <guilabel>Window Under Cursor</guilabel> mode is selected in the <guilabel>Area</guilabel> combo-box. Checking this option includes the window shadow in the screenshot, while unchecking it gives an image of the window without the shadow.</para> + </listitem> <listitem> <para>The <guilabel>Capture the current pop-up only</guilabel> option is only enabled when the <guilabel>Window Under Cursor</guilabel> mode is selected in the <guilabel>Area</guilabel> combo-box. Checking this option captures only the popup menu under the cursor, without its parent window.</para> </listitem> diff --git a/src/CommandLineOptions.h b/src/CommandLineOptions.h index 8f11b92a2..eda0d36be 100644 --- a/src/CommandLineOptions.h +++ b/src/CommandLineOptions.h @@ -95,34 +95,16 @@ struct CommandLineOptions { {u"e"_s, u"no-decoration"_s}, i18n("In background mode, exclude decorations in the screenshot") }; + const QCommandLineOption noShadow = {{u"S"_s, u"no-shadow"_s}, i18n("In background mode, exclude shadows in the screenshot")}; const QCommandLineOption editExisting = { {u"E"_s, u"edit-existing"_s}, i18n("Open and edit existing screenshot file"), u"existingFileName"_s }; - const QList<QCommandLineOption> allOptions = { - fullscreen, - current, - activeWindow, - windowUnderCursor, - transientOnly, - region, - launchOnly, - gui, - background, - dbus, - noNotify, - output, - delay, - copyImage, - copyPath, - onClick, - newInstance, - pointer, - noDecoration, - editExisting - }; + const QList<QCommandLineOption> allOptions = {fullscreen, current, activeWindow, windowUnderCursor, transientOnly, region, launchOnly, + gui, background, dbus, noNotify, output, delay, copyImage, + copyPath, onClick, newInstance, pointer, noDecoration, noShadow, editExisting}; // Keep order in sync with allOptions enum Option { @@ -145,6 +127,7 @@ struct CommandLineOptions { NewInstance, Pointer, NoDecoration, + NoShadow, EditExisting, TotalOptions }; diff --git a/src/Gui/CaptureSettingsColumn.qml b/src/Gui/CaptureSettingsColumn.qml index ad0187ea8..2a157193f 100644 --- a/src/Gui/CaptureSettingsColumn.qml +++ b/src/Gui/CaptureSettingsColumn.qml @@ -29,6 +29,16 @@ ColumnLayout { checked: Settings.includeDecorations onToggled: Settings.includeDecorations = checked } + QQC.CheckBox { + Layout.fillWidth: true + text: i18n("Include window shadow") + QQC.ToolTip.text: i18n("Show the window shadow when taking a screenshot of a window.") + QQC.ToolTip.delay: Kirigami.Units.toolTipDelay + QQC.ToolTip.visible: hovered + enabled: Settings.includeDecorations + checked: Settings.includeShadow + onToggled: Settings.includeShadow = checked + } QQC.CheckBox { Layout.fillWidth: true text: i18n("Capture the current pop-up only") diff --git a/src/Gui/OptionsMenu.cpp b/src/Gui/OptionsMenu.cpp index 454638330..e9ded18f2 100644 --- a/src/Gui/OptionsMenu.cpp +++ b/src/Gui/OptionsMenu.cpp @@ -26,6 +26,7 @@ OptionsMenu::OptionsMenu(QWidget *parent) , captureSettingsSection(new QAction(this)) , includeMousePointerAction(new QAction(this)) , includeWindowDecorationsAction(new QAction(this)) + , includeWindowShadowAction(new QAction(this)) , onlyCapturePopupAction(new QAction(this)) , quitAfterSaveAction(new QAction(this)) , captureOnClickAction(new QAction(this)) @@ -92,6 +93,18 @@ OptionsMenu::OptionsMenu(QWidget *parent) }); addAction(includeWindowDecorationsAction.get()); + includeWindowShadowAction->setText(i18n("Include window shadow")); + includeWindowShadowAction->setToolTip(i18n("Show the window shadow")); + includeWindowShadowAction->setCheckable(true); + includeWindowShadowAction->setChecked(Settings::includeShadow()); + connect(includeWindowShadowAction.get(), &QAction::toggled, this, [](bool checked) { + Settings::setIncludeShadow(checked); + }); + connect(Settings::self(), &Settings::includeShadowChanged, this, [this]() { + includeWindowShadowAction->setChecked(Settings::includeShadow()); + }); + addAction(includeWindowShadowAction.get()); + onlyCapturePopupAction->setText(i18n("Capture the current pop-up only")); onlyCapturePopupAction->setToolTip( i18n("Capture only the current pop-up window (like a menu, tooltip etc).\n" diff --git a/src/Gui/OptionsMenu.h b/src/Gui/OptionsMenu.h index 395de7b76..3eb7f0ab6 100644 --- a/src/Gui/OptionsMenu.h +++ b/src/Gui/OptionsMenu.h @@ -46,6 +46,7 @@ private: const std::unique_ptr<QAction> captureSettingsSection; const std::unique_ptr<QAction> includeMousePointerAction; const std::unique_ptr<QAction> includeWindowDecorationsAction; + const std::unique_ptr<QAction> includeWindowShadowAction; const std::unique_ptr<QAction> onlyCapturePopupAction; const std::unique_ptr<QAction> quitAfterSaveAction; const std::unique_ptr<QAction> captureOnClickAction; diff --git a/src/Gui/SettingsDialog/spectacle.kcfg b/src/Gui/SettingsDialog/spectacle.kcfg index fc6373ef6..c828ea38f 100644 --- a/src/Gui/SettingsDialog/spectacle.kcfg +++ b/src/Gui/SettingsDialog/spectacle.kcfg @@ -91,6 +91,10 @@ <label>Whether the window decorations are included in the screenshot</label> <default>true</default> </entry> + <entry name="includeShadow" type="Bool"> + <label>Whether the window shadow is included in the screenshot</label> + <default>true</default> + </entry> <entry name="transientOnly" type="Bool"> <label>Only capture the current pop up menu</label> <default>false</default> diff --git a/src/Platforms/ImagePlatform.h b/src/Platforms/ImagePlatform.h index c20930855..9be2f6764 100644 --- a/src/Platforms/ImagePlatform.h +++ b/src/Platforms/ImagePlatform.h @@ -43,7 +43,8 @@ public: virtual ShutterModes supportedShutterModes() const = 0; public Q_SLOTS: - virtual void doGrab(ImagePlatform::ShutterMode shutterMode, ImagePlatform::GrabMode grabMode, bool includePointer, bool includeDecorations) = 0; + virtual void + doGrab(ImagePlatform::ShutterMode shutterMode, ImagePlatform::GrabMode grabMode, bool includePointer, bool includeDecorations, bool includeShadow) = 0; Q_SIGNALS: void supportedGrabModesChanged(); diff --git a/src/Platforms/ImagePlatformKWin.cpp b/src/Platforms/ImagePlatformKWin.cpp index 5179ef2d7..07ce45df4 100644 --- a/src/Platforms/ImagePlatformKWin.cpp +++ b/src/Platforms/ImagePlatformKWin.cpp @@ -42,6 +42,10 @@ static QVariantMap screenShotFlagsToVardict(ImagePlatformKWin::ScreenShotFlags f if (flags & ImagePlatformKWin::ScreenShotFlag::IncludeDecoration) { options.insert(u"include-decoration"_s, true); } + + bool includeShadow = flags & ImagePlatformKWin::ScreenShotFlag::IncludeShadow; + options.insert(u"include-shadow"_s, includeShadow); + if (flags & ImagePlatformKWin::ScreenShotFlag::NativeSize) { options.insert(u"native-resolution"_s, true); } @@ -286,10 +290,12 @@ ImagePlatform::ShutterModes ImagePlatformKWin::supportedShutterModes() const return ShutterMode::Immediate; } -void ImagePlatformKWin::doGrab(ShutterMode, GrabMode grabMode, bool includePointer, bool includeDecorations) +void ImagePlatformKWin::doGrab(ShutterMode, GrabMode grabMode, bool includePointer, bool includeDecorations, bool includeShadow) { ScreenShotFlags flags = ScreenShotFlag::NativeSize; + flags.setFlag(ScreenShotFlag::IncludeShadow, includeShadow); + if (includeDecorations) { flags |= ScreenShotFlag::IncludeDecoration; } diff --git a/src/Platforms/ImagePlatformKWin.h b/src/Platforms/ImagePlatformKWin.h index f6fa9d927..580171e47 100644 --- a/src/Platforms/ImagePlatformKWin.h +++ b/src/Platforms/ImagePlatformKWin.h @@ -32,6 +32,7 @@ public: IncludeCursor = 0x1, IncludeDecoration = 0x2, NativeSize = 0x4, + IncludeShadow = 0x8, }; Q_DECLARE_FLAGS(ScreenShotFlags, ScreenShotFlag) @@ -44,7 +45,8 @@ public: ShutterModes supportedShutterModes() const override; public Q_SLOTS: - void doGrab(ImagePlatform::ShutterMode shutterMode, ImagePlatform::GrabMode grabMode, bool includePointer, bool includeDecorations) override; + void + doGrab(ImagePlatform::ShutterMode shutterMode, ImagePlatform::GrabMode grabMode, bool includePointer, bool includeDecorations, bool includeShadow) override; private Q_SLOTS: void updateSupportedGrabModes(); diff --git a/src/Platforms/ImagePlatformXcb.cpp b/src/Platforms/ImagePlatformXcb.cpp index cacbd866c..faa8a9997 100644 --- a/src/Platforms/ImagePlatformXcb.cpp +++ b/src/Platforms/ImagePlatformXcb.cpp @@ -69,11 +69,12 @@ public: { } - void setCaptureOptions(ImagePlatform::GrabMode grabMode, bool includePointer, bool includeDecorations) + void setCaptureOptions(ImagePlatform::GrabMode grabMode, bool includePointer, bool includeDecorations, bool includeShadow) { m_grabMode = grabMode; m_includePointer = includePointer; m_includeDecorations = includeDecorations; + m_includeShadow = includeShadow; } bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr * /*result*/) override @@ -92,7 +93,7 @@ public: auto secondEvent = static_cast<xcb_button_release_event_t *>(message); if (secondEvent->detail == 1) { QTimer::singleShot(0, nullptr, [this]() { - m_platformPtr->doGrabNow(m_grabMode, m_includePointer, m_includeDecorations); + m_platformPtr->doGrabNow(m_grabMode, m_includePointer, m_includeDecorations, m_includeShadow); }); } else if (secondEvent->detail == 2 || secondEvent->detail == 3) { // 2: middle click, 3: right click; both cancel @@ -101,7 +102,7 @@ public: Q_EMIT m_platformPtr->newScreenshotFailed(); } else { QTimer::singleShot(0, nullptr, [this]() { - m_platformPtr->doGrabOnClick(m_grabMode, m_includePointer, m_includeDecorations); + m_platformPtr->doGrabOnClick(m_grabMode, m_includePointer, m_includeDecorations, m_includeShadow); }); } } @@ -119,6 +120,7 @@ private: ImagePlatform::GrabMode m_grabMode{GrabMode::AllScreens}; bool m_includePointer{true}; bool m_includeDecorations{true}; + bool m_includeShadow{true}; }; /* -- General Plumbing ------------------------------------------------------------------------- */ @@ -164,15 +166,15 @@ ImagePlatform::ShutterModes ImagePlatformXcb::supportedShutterModes() const return {ShutterMode::Immediate | ShutterMode::OnClick}; } -void ImagePlatformXcb::doGrab(ShutterMode shutterMode, GrabMode grabMode, bool includePointer, bool includeDecorations) +void ImagePlatformXcb::doGrab(ShutterMode shutterMode, GrabMode grabMode, bool includePointer, bool includeDecorations, bool includeShadow) { switch (shutterMode) { case ShutterMode::Immediate: { - doGrabNow(grabMode, includePointer, includeDecorations); + doGrabNow(grabMode, includePointer, includeDecorations, includeShadow); return; } case ShutterMode::OnClick: { - doGrabOnClick(grabMode, includePointer, includeDecorations); + doGrabOnClick(grabMode, includePointer, includeDecorations, includeShadow); return; } } @@ -301,6 +303,37 @@ QList<QRect> ImagePlatformXcb::getScreenRects() /* -- Image Processing Utilities --------------------------------------------------------------- */ +QImage ImagePlatformXcb::addDropShadow(QImage &image) +{ + // Create a new image that is 20px wider and 20px taller than the original image + QImage shadowImage(image.size() + QSize(40, 40), QImage::Format_ARGB32); + shadowImage.fill(Qt::transparent); + + // Create a painter for the shadow image + QPainter shadowPainter(&shadowImage); + + // Create a pixmap item for the original image + auto pixmapItem = new QGraphicsPixmapItem; + pixmapItem->setPixmap(QPixmap::fromImage(image)); + + // Create a drop shadow effect for the pixmap item + auto shadowEffect = new QGraphicsDropShadowEffect; + shadowEffect->setOffset(0); + shadowEffect->setBlurRadius(20); + pixmapItem->setGraphicsEffect(shadowEffect); + + // Create a graphics scene and add the pixmap item to it + QGraphicsScene graphicsScene; + graphicsScene.addItem(pixmapItem); + + // Render the graphics scene to the shadow image + graphicsScene.render(&shadowPainter, QRectF(), QRectF(-20, -20, image.width() + 40, image.height() + 40)); + shadowPainter.end(); + + // Return the shadow image + return shadowImage; +} + QImage ImagePlatformXcb::convertFromNative(xcb_image_t *xcbImage) { auto imageFormat = QImage::Format_Invalid; @@ -551,7 +584,7 @@ void ImagePlatformXcb::grabApplicationWindow(xcb_window_t window, bool includePo Q_EMIT newScreenshotTaken(image); } -void ImagePlatformXcb::grabActiveWindow(bool includePointer, bool includeDecorations) +void ImagePlatformXcb::grabActiveWindow(bool includePointer, bool includeDecorations, bool includeShadow) { auto activeWindow = KX11Extras::activeWindow(); updateWindowTitle(activeWindow); @@ -571,7 +604,10 @@ void ImagePlatformXcb::grabActiveWindow(bool includePointer, bool includeDecorat if (includePointer) { opMask |= 1 << 1; } - iface.call(u"screenshotForWindow"_s, static_cast<quint64>(activeWindow), opMask); + if (includeShadow) { + opMask |= 1 << 2; + } + iface.call(QStringLiteral("screenshotForWindow"), static_cast<quint64>(activeWindow), opMask); return; } @@ -580,7 +616,7 @@ void ImagePlatformXcb::grabActiveWindow(bool includePointer, bool includeDecorat grabApplicationWindow(activeWindow, includePointer, includeDecorations); } -void ImagePlatformXcb::grabWindowUnderCursor(bool includePointer, bool includeDecorations) +void ImagePlatformXcb::grabWindowUnderCursor(bool includePointer, bool includeDecorations, bool includeShadow) { auto window = getWindowUnderCursor(); updateWindowTitle(window); @@ -600,6 +636,9 @@ void ImagePlatformXcb::grabWindowUnderCursor(bool includePointer, bool includeDe if (includePointer) { opMask |= 1 << 1; } + if (includeShadow) { + opMask |= 1 << 2; + } interface.call(u"screenshotWindowUnderCursor"_s, opMask); return; @@ -609,7 +648,7 @@ void ImagePlatformXcb::grabWindowUnderCursor(bool includePointer, bool includeDe grabApplicationWindow(window, includePointer, includeDecorations); } -void ImagePlatformXcb::grabTransientWithParent(bool includePointer, bool includeDecorations) +void ImagePlatformXcb::grabTransientWithParent(bool includePointer, bool includeDecorations, bool includeShadow) { auto window = getWindowUnderCursor(); updateWindowTitle(window); @@ -668,26 +707,11 @@ void ImagePlatformXcb::grabTransientWithParent(bool includePointer, bool include painter.end(); image = clippedImage.copy(clipRegion.boundingRect()); - // why stop here, when we can render a 20px drop shadow all around it - auto shadowEffect = new QGraphicsDropShadowEffect; - shadowEffect->setOffset(0); - shadowEffect->setBlurRadius(20); - - auto pixmapItem = new QGraphicsPixmapItem; - pixmapItem->setPixmap(QPixmap::fromImage(image)); - pixmapItem->setGraphicsEffect(shadowEffect); - - QImage shadowImage(image.size() + QSize(40, 40), QImage::Format_ARGB32); - shadowImage.fill(Qt::transparent); - QPainter shadowPainter(&shadowImage); - - QGraphicsScene graphicsScene; - graphicsScene.addItem(pixmapItem); - graphicsScene.render(&shadowPainter, QRectF(), QRectF(-20, -20, image.width() + 40, image.height() + 40)); - shadowPainter.end(); + // add a drop shadow if requested + if (includeShadow) { + image = addDropShadow(image); + } - // we can finish up now - image = shadowImage; if (includePointer) { auto topLeft = clipRegion.boundingRect().topLeft() - QPoint(20, 20); image = blendCursorImage(image, QRect(topLeft, QSize(image.width(), image.height()))); @@ -696,7 +720,7 @@ void ImagePlatformXcb::grabTransientWithParent(bool includePointer, bool include Q_EMIT newScreenshotTaken(image); } -void ImagePlatformXcb::doGrabNow(GrabMode grabMode, bool includePointer, bool includeDecorations) +void ImagePlatformXcb::doGrabNow(GrabMode grabMode, bool includePointer, bool includeDecorations, bool includeShadow) { if (grabMode & ~(ActiveWindow | WindowUnderCursor | TransientWithParent)) { // Notify that window title is empty since we are not picking a window. @@ -715,20 +739,20 @@ void ImagePlatformXcb::doGrabNow(GrabMode grabMode, bool includePointer, bool in grabCurrentScreen(includePointer); break; case GrabMode::ActiveWindow: - grabActiveWindow(includePointer, includeDecorations); + grabActiveWindow(includePointer, includeDecorations, includeShadow); break; case GrabMode::WindowUnderCursor: - grabWindowUnderCursor(includePointer, includeDecorations); + grabWindowUnderCursor(includePointer, includeDecorations, includeShadow); break; case GrabMode::TransientWithParent: - grabTransientWithParent(includePointer, includeDecorations); + grabTransientWithParent(includePointer, includeDecorations, includeShadow); break; case GrabMode::NoGrabModes: Q_EMIT newScreenshotFailed(); } } -void ImagePlatformXcb::doGrabOnClick(GrabMode grabMode, bool includePointer, bool includeDecorations) +void ImagePlatformXcb::doGrabOnClick(GrabMode grabMode, bool includePointer, bool includeDecorations, bool includeShadow) { // get the cursor image xcb_cursor_t xcbCursor = XCB_CURSOR_NONE; @@ -762,12 +786,12 @@ void ImagePlatformXcb::doGrabOnClick(GrabMode grabMode, bool includePointer, boo // if the grab failed, take the screenshot right away if (grabPointerReply->status != XCB_GRAB_STATUS_SUCCESS) { - doGrabNow(grabMode, includePointer, includeDecorations); + doGrabNow(grabMode, includePointer, includeDecorations, includeShadow); return; } // fix things if our pointer grab causes a lockup and install our event filter - m_nativeEventFilter->setCaptureOptions(grabMode, includePointer, includeDecorations); + m_nativeEventFilter->setCaptureOptions(grabMode, includePointer, includeDecorations, includeShadow); xcb_allow_events(QX11Info::connection(), XCB_ALLOW_SYNC_POINTER, XCB_TIME_CURRENT_TIME); qApp->installNativeEventFilter(m_nativeEventFilter.get()); diff --git a/src/Platforms/ImagePlatformXcb.h b/src/Platforms/ImagePlatformXcb.h index 3dc93a4ce..4a3a4fa27 100644 --- a/src/Platforms/ImagePlatformXcb.h +++ b/src/Platforms/ImagePlatformXcb.h @@ -25,21 +25,36 @@ public: ShutterModes supportedShutterModes() const override final; public Q_SLOTS: - void doGrab(ImagePlatform::ShutterMode shutterMode, ImagePlatform::GrabMode grabMode, bool includePointer, bool includeDecorations) override final; + void doGrab(ImagePlatform::ShutterMode shutterMode, + ImagePlatform::GrabMode grabMode, + bool includePointer, + bool includeDecorations, + bool includeShadow) override final; private Q_SLOTS: void updateSupportedGrabModes(); void handleKWinScreenshotReply(quint64 drawable); - void doGrabNow(ImagePlatform::GrabMode grabMode, bool includePointer, bool includeDecorations); - void doGrabOnClick(ImagePlatform::GrabMode grabMode, bool includePointer, bool includeDecorations); + void doGrabNow(ImagePlatform::GrabMode grabMode, bool includePointer, bool includeDecorations, bool includeShadow); + void doGrabOnClick(ImagePlatform::GrabMode grabMode, bool includePointer, bool includeDecorations, bool includeShadow); private: inline void updateWindowTitle(xcb_window_t window); bool isKWinAvailable(); + QPoint getCursorPosition(); QRect getDrawableGeometry(xcb_drawable_t drawable); xcb_window_t getWindowUnderCursor(); xcb_window_t getTransientWindowParent(xcb_window_t childWindow, QRect &windowRectOut, bool includeDecorations); + + /* ----------------------- Image Processing Utilities ----------------------- */ + + /** + * @brief Adds a drop shadow to the given image. + * @param image The image to add a drop shadow to. + * @return The image with a drop shadow. + */ + QImage addDropShadow(QImage &image); + QList<QRect> getScreenRects(); QImage convertFromNative(xcb_image_t *xcbImage); QImage blendCursorImage(QImage &image, const QRect rect); @@ -51,9 +66,9 @@ private: void grabAllScreens(bool includePointer, bool crop = false); void grabCurrentScreen(bool includePointer); void grabApplicationWindow(xcb_window_t window, bool includePointer, bool includeDecorations); - void grabActiveWindow(bool includePointer, bool includeDecorations); - void grabWindowUnderCursor(bool includePointer, bool includeDecorations); - void grabTransientWithParent(bool includePointer, bool includeDecorations); + void grabActiveWindow(bool includePointer, bool includeDecorations, bool includeShadow); + void grabWindowUnderCursor(bool includePointer, bool includeDecorations, bool includeShadow); + void grabTransientWithParent(bool includePointer, bool includeDecorations, bool includeShadow); // on-click screenshot shutter support needs a native event filter in xcb class OnClickEventFilter; diff --git a/src/Platforms/PlatformNull.cpp b/src/Platforms/PlatformNull.cpp index 9fd54a80a..0e420bd5e 100644 --- a/src/Platforms/PlatformNull.cpp +++ b/src/Platforms/PlatformNull.cpp @@ -27,12 +27,13 @@ ImagePlatform::ShutterModes ImagePlatformNull::supportedShutterModes() const return {ShutterMode::Immediate | ShutterMode::OnClick}; } -void ImagePlatformNull::doGrab(ShutterMode shutterMode, GrabMode grabMode, bool includePointer, bool includeDecorations) +void ImagePlatformNull::doGrab(ShutterMode shutterMode, GrabMode grabMode, bool includePointer, bool includeDecorations, bool includeShadow) { Q_UNUSED(shutterMode) Q_UNUSED(grabMode) Q_UNUSED(includePointer) Q_UNUSED(includeDecorations) + Q_UNUSED(includeShadow) Q_EMIT newScreenshotTaken(); } diff --git a/src/Platforms/PlatformNull.h b/src/Platforms/PlatformNull.h index 75cdacf1f..39b32add0 100644 --- a/src/Platforms/PlatformNull.h +++ b/src/Platforms/PlatformNull.h @@ -21,7 +21,11 @@ public: public Q_SLOTS: - void doGrab(ImagePlatform::ShutterMode shutterMode, ImagePlatform::GrabMode grabMode, bool includePointer, bool includeDecorations) override final; + void doGrab(ImagePlatform::ShutterMode shutterMode, + ImagePlatform::GrabMode grabMode, + bool includePointer, + bool includeDecorations, + bool includeShadow) override final; }; class VideoPlatformNull final : public VideoPlatform diff --git a/src/SpectacleCore.cpp b/src/SpectacleCore.cpp index d8ba0104f..4f35c45b6 100644 --- a/src/SpectacleCore.cpp +++ b/src/SpectacleCore.cpp @@ -103,8 +103,7 @@ SpectacleCore::SpectacleCore(QObject *parent) } }; auto onFinished = [this]() { - m_imagePlatform->doGrab(ImagePlatform::ShutterMode::Immediate, m_lastGrabMode, - m_lastIncludePointer, m_lastIncludeDecorations); + m_imagePlatform->doGrab(ImagePlatform::ShutterMode::Immediate, m_lastGrabMode, m_lastIncludePointer, m_lastIncludeDecorations, m_lastIncludeShadow); }; QObject::connect(delayAnimation, &QVariantAnimation::stateChanged, this, onStateChanged, Qt::QueuedConnection); @@ -404,16 +403,19 @@ void SpectacleCore::activate(const QStringList &arguments, const QString &workin bool onClick; bool includeDecorations; bool includePointer; + bool includeShadow; if (m_startMode == StartMode::Background) { transientOnly = m_cliOptions[Option::TransientOnly]; onClick = m_cliOptions[Option::OnClick]; includeDecorations = !m_cliOptions[Option::NoDecoration]; includePointer = m_cliOptions[Option::Pointer]; + includeShadow = !m_cliOptions[Option::NoShadow]; } else { transientOnly = Settings::transientOnly() || m_cliOptions[Option::TransientOnly]; onClick = Settings::captureOnClick() || m_cliOptions[Option::OnClick]; includeDecorations = Settings::includeDecorations() && !m_cliOptions[Option::NoDecoration]; + includeShadow = Settings::includeShadow() && !m_cliOptions[Option::NoShadow]; includePointer = Settings::includePointer() || m_cliOptions[Option::Pointer]; } @@ -484,7 +486,7 @@ void SpectacleCore::activate(const QStringList &arguments, const QString &workin case StartMode::DBus: break; case StartMode::Background: - takeNewScreenshot(grabMode, delayMsec, includePointer, includeDecorations); + takeNewScreenshot(grabMode, delayMsec, includePointer, includeDecorations, includeShadow); break; case StartMode::Gui: if (isGuiNull()) { @@ -494,14 +496,14 @@ void SpectacleCore::activate(const QStringList &arguments, const QString &workin initViewerWindow(ViewerWindow::Dialog); ViewerWindow::instance()->setVisible(true); } else { - takeNewScreenshot(grabMode, delayMsec, includePointer, includeDecorations); + takeNewScreenshot(grabMode, delayMsec, includePointer, includeDecorations, includeShadow); } } else { using Actions = Settings::EnumPrintKeyActionRunning; switch (Settings::printKeyActionRunning()) { case Actions::TakeNewScreenshot: { // takeNewScreenshot switches to on click if immediate is not supported. - takeNewScreenshot(grabMode, 0, includePointer, includeDecorations); + takeNewScreenshot(grabMode, 0, includePointer, includeDecorations, includeShadow); break; } case Actions::FocusWindow: { @@ -537,7 +539,7 @@ void SpectacleCore::activate(const QStringList &arguments, const QString &workin } } -void SpectacleCore::takeNewScreenshot(ImagePlatform::GrabMode grabMode, int timeout, bool includePointer, bool includeDecorations) +void SpectacleCore::takeNewScreenshot(ImagePlatform::GrabMode grabMode, int timeout, bool includePointer, bool includeDecorations, bool includeWindowShadow) { if (m_cliOptions[CommandLineOptions::EditExisting]) { // Clear when a new screenshot is taken to avoid overwriting @@ -554,12 +556,13 @@ void SpectacleCore::takeNewScreenshot(ImagePlatform::GrabMode grabMode, int time m_lastGrabMode = grabMode; m_lastIncludePointer = includePointer; m_lastIncludeDecorations = includeDecorations; + m_lastIncludeShadow = includeWindowShadow; if ((timeout < 0 || !m_imagePlatform->supportedShutterModes().testFlag(ImagePlatform::Immediate)) && m_imagePlatform->supportedShutterModes().testFlag(ImagePlatform::OnClick) ) { SpectacleWindow::setVisibilityForAll(QWindow::Hidden); - m_imagePlatform->doGrab(ImagePlatform::ShutterMode::OnClick, m_lastGrabMode, m_lastIncludePointer, m_lastIncludeDecorations); + m_imagePlatform->doGrab(ImagePlatform::ShutterMode::OnClick, m_lastGrabMode, m_lastIncludePointer, m_lastIncludeDecorations, m_lastIncludeShadow); return; } @@ -581,7 +584,7 @@ void SpectacleCore::takeNewScreenshot(ImagePlatform::GrabMode grabMode, int time if (noDelay) { SpectacleWindow::setVisibilityForAll(QWindow::Hidden); QTimer::singleShot(timeout, this, [this]() { - m_imagePlatform->doGrab(ImagePlatform::ShutterMode::Immediate, m_lastGrabMode, m_lastIncludePointer, m_lastIncludeDecorations); + m_imagePlatform->doGrab(ImagePlatform::ShutterMode::Immediate, m_lastGrabMode, m_lastIncludePointer, m_lastIncludeDecorations, m_lastIncludeShadow); }); return; } @@ -592,11 +595,10 @@ void SpectacleCore::takeNewScreenshot(ImagePlatform::GrabMode grabMode, int time SpectacleWindow::setVisibilityForAll(QWindow::Minimized); } -void SpectacleCore::takeNewScreenshot(int captureMode, int timeout, bool includePointer, bool includeDecorations) +void SpectacleCore::takeNewScreenshot(int captureMode, int timeout, bool includePointer, bool includeDecorations, bool includeShadow) { using CaptureMode = CaptureModeModel::CaptureMode; - takeNewScreenshot(toGrabMode(CaptureMode(captureMode), Settings::transientOnly()), - timeout, includePointer, includeDecorations); + takeNewScreenshot(toGrabMode(CaptureMode(captureMode), Settings::transientOnly()), timeout, includePointer, includeDecorations, includeShadow); } void SpectacleCore::cancelScreenshot() diff --git a/src/SpectacleCore.h b/src/SpectacleCore.h index d7cda7e1b..35434be71 100644 --- a/src/SpectacleCore.h +++ b/src/SpectacleCore.h @@ -92,7 +92,8 @@ public Q_SLOTS: void takeNewScreenshot(int captureMode = Settings::captureMode(), int timeout = Settings::captureOnClick() ? -1 : Settings::captureDelay() * 1000, bool includePointer = Settings::includePointer(), - bool includeDecorations = Settings::includeDecorations()); + bool includeDecorations = Settings::includeDecorations(), + bool includeShadow = Settings::includeShadow()); void cancelScreenshot(); void showErrorMessage(const QString &message); void onScreenshotFailed(); @@ -112,8 +113,7 @@ Q_SIGNALS: void recordedTimeChanged(); private: - void takeNewScreenshot(ImagePlatform::GrabMode grabMode, int timeout, - bool includePointer, bool includeDecorations); + void takeNewScreenshot(ImagePlatform::GrabMode grabMode, int timeout, bool includePointer, bool includeDecorations, bool includeShadow); void setExportImage(const QImage &image); void showViewerIfGuiMode(); ImagePlatform::GrabMode toGrabMode(CaptureModeModel::CaptureMode captureMode, bool transientOnly) const; @@ -161,6 +161,7 @@ private: ImagePlatform::GrabMode m_lastGrabMode = ImagePlatform::GrabMode::NoGrabModes; bool m_lastIncludePointer = false; // cli default value bool m_lastIncludeDecorations = true; // cli default value + bool m_lastIncludeShadow = true; // cli default value bool m_videoMode = false; QUrl m_currentVideo; }; diff --git a/src/SpectacleDBusAdapter.cpp b/src/SpectacleDBusAdapter.cpp index 82fe4d99a..ef20ee3bf 100644 --- a/src/SpectacleDBusAdapter.cpp +++ b/src/SpectacleDBusAdapter.cpp @@ -28,20 +28,22 @@ void SpectacleDBusAdapter::CurrentScreen(int includeMousePointer) parent()->takeNewScreenshot(CaptureModeModel::CurrentScreen, 0, (includeMousePointer == -1) ? Settings::includePointer() : includeMousePointer, true); } -void SpectacleDBusAdapter::ActiveWindow(int includeWindowDecorations, int includeMousePointer) +void SpectacleDBusAdapter::ActiveWindow(int includeWindowDecorations, int includeMousePointer, int includeWindowShadow) { parent()->takeNewScreenshot(CaptureModeModel::ActiveWindow, 0, (includeMousePointer == -1) ? Settings::includePointer() : includeMousePointer, - includeWindowDecorations == -1 ? Settings::includeDecorations() : includeWindowDecorations); + includeWindowDecorations == -1 ? Settings::includeDecorations() : includeWindowDecorations, + includeWindowShadow == -1 ? Settings::includeShadow() : includeWindowShadow); } -void SpectacleDBusAdapter::WindowUnderCursor(int includeWindowDecorations, int includeMousePointer) +void SpectacleDBusAdapter::WindowUnderCursor(int includeWindowDecorations, int includeMousePointer, int includeWindowShadow) { parent()->takeNewScreenshot(CaptureModeModel::WindowUnderCursor, 0, (includeMousePointer == -1) ? Settings::includePointer() : includeMousePointer, - includeWindowDecorations == -1 ? Settings::includeDecorations() : includeWindowDecorations); + includeWindowDecorations == -1 ? Settings::includeDecorations() : includeWindowDecorations, + includeWindowShadow == -1 ? Settings::includeShadow() : includeWindowShadow); } void SpectacleDBusAdapter::RectangularRegion(int includeMousePointer) diff --git a/src/SpectacleDBusAdapter.h b/src/SpectacleDBusAdapter.h index 1673046b4..ad1443f5c 100644 --- a/src/SpectacleDBusAdapter.h +++ b/src/SpectacleDBusAdapter.h @@ -22,8 +22,8 @@ public Q_SLOTS: Q_NOREPLY void FullScreen(int includeMousePointer); Q_NOREPLY void CurrentScreen(int includeMousePointer); - Q_NOREPLY void ActiveWindow(int includeWindowDecorations, int includeMousePointer); - Q_NOREPLY void WindowUnderCursor(int includeWindowDecorations, int includeMousePointer); + Q_NOREPLY void ActiveWindow(int includeWindowDecorations, int includeMousePointer, int includeWindowShadow); + Q_NOREPLY void WindowUnderCursor(int includeWindowDecorations, int includeMousePointer, int includeWindowShadow); Q_NOREPLY void RectangularRegion(int includeMousePointer); Q_NOREPLY void OpenWithoutScreenshot();
