[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