[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
icc-effect-5.14.5
Martin Gräßlin 2016-11-18 10:02:04 +01:00
parent cfdc1acbd3
commit fbab204968
4 changed files with 94 additions and 13 deletions

View File

@ -22,6 +22,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <kwinglplatform.h>
#include <kwinglutils.h>
#include <kwinxrenderutils.h>
#include <QtConcurrentRun>
#include <QDataStream>
#include <QtCore/QTemporaryFile>
#include <QtCore/QDir>
#include <QtDBus/QDBusConnection>
@ -33,6 +35,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <KLocalizedString>
#include <KNotification>
#include <unistd.h>
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()) {

View File

@ -25,6 +25,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <QDBusContext>
#include <QDBusConnection>
#include <QDBusMessage>
#include <QDBusUnixFileDescriptor>
#include <QObject>
#include <QImage>
@ -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<EffectFrame> m_infoFrame;
};

View File

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

View File

@ -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)