From fbab20496894967f5222a080e6e96bb4f0710519 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Gr=C3=A4=C3=9Flin?= Date: Fri, 18 Nov 2016 10:02:04 +0100 Subject: [PATCH] [effects] Add a screenshot dbus method which takes a file descriptor Summary: The idea behind this mode is to support applications like spectacle (see T4458). The calling application passes a file descriptor (created through e.g. a pipe) and the screenshot effect writes the captured image into that fd. The advantage over the existing variant which writes to a file in the /tmp directory is that this is peer-to-peer between the requesting application and KWin. No other application can get to that image. The change also includes setting SIGPIPE to ignore. It showed that when the reading side already cancelled the read prior to KWin writing out the image we get a SIGPIPE which results in application termination, which is not what we want in case of a Wayland compositor. The sigpipe can be ignored as Qt (and libpng) handles that error just fine at runtime. Reviewers: #kwin, #plasma_on_wayland Subscribers: plasma-devel, kwin Tags: #plasma_on_wayland, #kwin Differential Revision: https://phabricator.kde.org/D3412 --- effects/screenshot/screenshot.cpp | 86 ++++++++++++++++++++++++++----- effects/screenshot/screenshot.h | 19 ++++++- main_wayland.cpp | 1 + main_x11.cpp | 1 + 4 files changed, 94 insertions(+), 13 deletions(-) diff --git a/effects/screenshot/screenshot.cpp b/effects/screenshot/screenshot.cpp index 80091de824..9f7da1cd73 100644 --- a/effects/screenshot/screenshot.cpp +++ b/effects/screenshot/screenshot.cpp @@ -22,6 +22,8 @@ along with this program. If not, see . #include #include #include +#include +#include #include #include #include @@ -33,6 +35,8 @@ along with this program. If not, see . #include #include +#include + namespace KWin { @@ -187,6 +191,20 @@ void ScreenShotEffect::postPaintScreen() m_windowMode = WindowMode::NoCapture; } else if (m_windowMode == WindowMode::File) { sendReplyImage(img); + } else if (m_windowMode == WindowMode::FileDescriptor) { + QtConcurrent::run( + [] (int fd, const QImage &img) { + QFile file; + if (file.open(fd, QIODevice::WriteOnly, QFileDevice::AutoCloseHandle)) { + QDataStream ds(&file); + ds << img; + file.close(); + } else { + close(fd); + } + }, m_fd, img); + m_windowMode = WindowMode::NoCapture; + m_fd = -1; } #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (xImage) { @@ -306,7 +324,7 @@ QString ScreenShotEffect::interactive(int mask) setDelayedReply(true); effects->startInteractiveWindowSelection( [this] (EffectWindow *w) { - m_infoFrame.reset(); + hideInfoMessage(); if (!w) { m_replyConnection.send(m_replyMessage.createErrorReply(QDBusError::Failed, "Screenshot got cancelled")); m_windowMode = WindowMode::NoCapture; @@ -317,20 +335,64 @@ QString ScreenShotEffect::interactive(int mask) } }); - - if (m_infoFrame.isNull()) { - m_infoFrame.reset(effects->effectFrame(EffectFrameStyled, false)); - QFont font; - font.setBold(true); - m_infoFrame->setFont(font); - QRect area = effects->clientArea(ScreenArea, effects->activeScreen(), effects->currentDesktop()); - m_infoFrame->setPosition(QPoint(area.x() + area.width() / 2, area.y() + area.height() / 3)); - m_infoFrame->setText(i18n("Select window to screen shot with left click or enter.\nEscape or right click to cancel.")); - effects->addRepaintFull(); - } + showInfoMessage(); return QString(); } +void ScreenShotEffect::interactive(QDBusUnixFileDescriptor fd, int mask) +{ + if (!calledFromDBus()) { + return; + } + if (!m_scheduledGeometry.isNull() || m_windowMode != WindowMode::NoCapture) { + sendErrorReply(QDBusError::Failed, "A screenshot is already been taken"); + return; + } + m_fd = dup(fd.fileDescriptor()); + if (m_fd == -1) { + sendErrorReply(QDBusError::Failed, "No valid file descriptor"); + return; + } + m_type = (ScreenShotType) mask; + m_windowMode = WindowMode::FileDescriptor; + + effects->startInteractiveWindowSelection( + [this] (EffectWindow *w) { + hideInfoMessage(); + if (!w) { + close(m_fd); + m_fd = -1; + m_windowMode = WindowMode::NoCapture; + return; + } else { + m_scheduledScreenshot = w; + m_scheduledScreenshot->addRepaintFull(); + } + }); + + showInfoMessage(); +} + +void ScreenShotEffect::showInfoMessage() +{ + if (!m_infoFrame.isNull()) { + return; + } + m_infoFrame.reset(effects->effectFrame(EffectFrameStyled, false)); + QFont font; + font.setBold(true); + m_infoFrame->setFont(font); + QRect area = effects->clientArea(ScreenArea, effects->activeScreen(), effects->currentDesktop()); + m_infoFrame->setPosition(QPoint(area.x() + area.width() / 2, area.y() + area.height() / 3)); + m_infoFrame->setText(i18n("Select window to screen shot with left click or enter.\nEscape or right click to cancel.")); + effects->addRepaintFull(); +} + +void ScreenShotEffect::hideInfoMessage() +{ + m_infoFrame.reset(); +} + QString ScreenShotEffect::screenshotFullscreen(bool captureCursor) { if (!calledFromDBus()) { diff --git a/effects/screenshot/screenshot.h b/effects/screenshot/screenshot.h index 2323b9f9c6..8a54aa5639 100644 --- a/effects/screenshot/screenshot.h +++ b/effects/screenshot/screenshot.h @@ -25,6 +25,7 @@ along with this program. If not, see . #include #include #include +#include #include #include @@ -64,6 +65,18 @@ public Q_SLOTS: * @param mask The mask for what to include in the screenshot **/ Q_SCRIPTABLE QString interactive(int mask = 0); + /** + * Starts an interactive window screenshot session. The user can select a window to + * screenshot. + * + * Once the window is selected the screenshot is saved into the @p fd passed to the + * method. It is intended to be used with a pipe, so that the invoking side can just + * read from the pipe. The image gets written into the fd using a QDataStream. + * + * @param fd File descriptor into which the screenshot should be saved + * @param mask The mask for what to include in the screenshot + **/ + Q_SCRIPTABLE void interactive(QDBusUnixFileDescriptor fd, int mask = 0); Q_SCRIPTABLE void screenshotWindowUnderCursor(int mask = 0); /** * Saves a screenshot of all screen into a file and returns the path to the file. @@ -103,6 +116,8 @@ private: QImage blitScreenshot(const QRect &geometry); QString saveTempImage(const QImage &img); void sendReplyImage(const QImage &img); + void showInfoMessage(); + void hideInfoMessage(); EffectWindow *m_scheduledScreenshot; ScreenShotType m_type; QRect m_scheduledGeometry; @@ -115,9 +130,11 @@ private: enum class WindowMode { NoCapture, Xpixmap, - File + File, + FileDescriptor }; WindowMode m_windowMode = WindowMode::NoCapture; + int m_fd = -1; QScopedPointer m_infoFrame; }; diff --git a/main_wayland.cpp b/main_wayland.cpp index 364f4ca725..ebc2c14a64 100644 --- a/main_wayland.cpp +++ b/main_wayland.cpp @@ -451,6 +451,7 @@ int main(int argc, char * argv[]) signal(SIGHUP, SIG_IGN); signal(SIGABRT, KWin::unsetDumpable); signal(SIGSEGV, KWin::unsetDumpable); + signal(SIGPIPE, SIG_IGN); // ensure that no thread takes SIGUSR sigset_t userSignals; sigemptyset(&userSignals); diff --git a/main_x11.cpp b/main_x11.cpp index da3c0452e7..923462f84e 100644 --- a/main_x11.cpp +++ b/main_x11.cpp @@ -396,6 +396,7 @@ KWIN_EXPORT int kdemain(int argc, char * argv[]) signal(SIGINT, SIG_IGN); if (signal(SIGHUP, KWin::sighandler) == SIG_IGN) signal(SIGHUP, SIG_IGN); + signal(SIGPIPE, SIG_IGN); // Disable the glib event loop integration, since it seems to be responsible // for several bug reports about high CPU usage (bug #239963)