[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
This commit is contained in:
parent
cfdc1acbd3
commit
fbab204968
4 changed files with 94 additions and 13 deletions
|
@ -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()) {
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue