[graphics/spectacle] /: feat: add window shadows to the capture options

Noah Davis null at kde.org
Sun Oct 22 14:01:59 BST 2023


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();
 


More information about the kde-doc-english mailing list