Commit 77703895 authored by Vlad Zahorodnii's avatar Vlad Zahorodnii Committed by Nate Graham
Browse files

Platforms: Introduce PlatformKWinWayland2

The new interface is more extensible and it fixes the latency issue on
wayland. The old org.kde.kwin.Screenshot interface is deprecated.

BUG: 437652
FIXED-IN: 21.04.2
(cherry picked from commit be8ee0f7)
parent 2ea7c8fa
......@@ -161,7 +161,7 @@ Actions=FullScreenScreenShot;CurrentMonitorScreenShot;ActiveWindowScreenShot;Rec
X-DBUS-StartupType=Unique
X-DBUS-ServiceName=org.kde.Spectacle
X-KDE-Shortcuts=Print
X-KDE-DBUS-Restricted-Interfaces=org.kde.kwin.Screenshot
X-KDE-DBUS-Restricted-Interfaces=org.kde.kwin.Screenshot,org.kde.KWin.ScreenShot2
[Desktop Action FullScreenScreenShot]
Name=Capture Entire Desktop
......
......@@ -31,6 +31,7 @@ add_executable(spectacle
Platforms/Platform.cpp
Platforms/PlatformNull.cpp
Platforms/PlatformKWinWayland.cpp
Platforms/PlatformKWinWayland2.cpp
)
if(XCB_FOUND)
......
/*
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "PlatformKWinWayland2.h"
#include <QDBusConnection>
#include <QDBusConnectionInterface>
#include <QDBusPendingCall>
#include <QDBusPendingCallWatcher>
#include <QDBusUnixFileDescriptor>
#include <QFuture>
#include <QFutureWatcher>
#include <QGuiApplication>
#include <QPixmap>
#include <QScreen>
#include <QtConcurrent>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
static QVariantMap screenShotFlagsToVardict(PlatformKWinWayland2::ScreenShotFlags flags)
{
QVariantMap options;
if (flags & PlatformKWinWayland2::ScreenShotFlag::IncludeCursor) {
options.insert(QStringLiteral("include-cursor"), true);
}
if (flags & PlatformKWinWayland2::ScreenShotFlag::IncludeDecoration) {
options.insert(QStringLiteral("include-decoration"), true);
}
if (flags & PlatformKWinWayland2::ScreenShotFlag::NativeSize) {
options.insert(QStringLiteral("native-resolution"), true);
}
return options;
}
static const QString s_screenShotService = QStringLiteral("org.kde.KWin.ScreenShot2");
static const QString s_screenShotObjectPath = QStringLiteral("/org/kde/KWin/ScreenShot2");
static const QString s_screenShotInterface = QStringLiteral("org.kde.KWin.ScreenShot2");
template <typename ...ArgType>
ScreenShotSource2::ScreenShotSource2(const QString &methodName, ArgType... arguments)
{
// Do not set the O_NONBLOCK flag. Code that reads data from the pipe assumes
// that read() will block if there is no any data yet.
int pipeFds[2];
if (pipe2(pipeFds, O_CLOEXEC) == -1) {
QTimer::singleShot(0, this, &ScreenShotSource2::errorOccurred);
qWarning() << "pipe2() failed:" << strerror(errno);
return;
}
QDBusMessage message = QDBusMessage::createMethodCall(s_screenShotService,
s_screenShotObjectPath,
s_screenShotInterface,
methodName);
QVariantList dbusArguments{arguments...};
dbusArguments.append(QVariant::fromValue(QDBusUnixFileDescriptor(pipeFds[1])));
message.setArguments(dbusArguments);
QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(message);
close(pipeFds[1]);
m_pipeFileDescriptor = pipeFds[0];
auto watcher = new QDBusPendingCallWatcher(pendingCall, this);
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, watcher]() {
watcher->deleteLater();
const QDBusPendingReply<QVariantMap> reply = *watcher;
if (reply.isError()) {
qWarning() << "Screenshot request failed:" << reply.error().message();
Q_EMIT errorOccurred();
} else {
handleMetaDataReceived(reply);
}
});
}
ScreenShotSource2::~ScreenShotSource2()
{
if (m_pipeFileDescriptor != -1) {
close(m_pipeFileDescriptor);
}
}
QImage ScreenShotSource2::result() const
{
return m_result;
}
static QImage allocateImage(const QVariantMap &metadata)
{
bool ok;
const uint width = metadata.value(QStringLiteral("width")).toUInt(&ok);
if (!ok) {
return QImage();
}
const uint height = metadata.value(QStringLiteral("height")).toUInt(&ok);
if (!ok) {
return QImage();
}
const uint format = metadata.value(QStringLiteral("format")).toUInt(&ok);
if (!ok) {
return QImage();
}
return QImage(width, height, QImage::Format(format));
}
static QImage readImage(int fileDescriptor, const QVariantMap &metadata)
{
QFile file;
if (!file.open(fileDescriptor, QFileDevice::ReadOnly, QFileDevice::AutoCloseHandle)) {
close(fileDescriptor);
return QImage();
}
QImage result = allocateImage(metadata);
if (result.isNull()) {
return QImage();
}
QDataStream stream(&file);
stream.readRawData(reinterpret_cast<char *>(result.bits()), result.sizeInBytes());
return result;
}
void ScreenShotSource2::handleMetaDataReceived(const QVariantMap &metadata)
{
const QString type = metadata.value(QStringLiteral("type")).toString();
if (type != QLatin1String("raw")) {
qWarning() << "Unsupported metadata type:" << type;
return;
}
auto watcher = new QFutureWatcher<QImage>(this);
connect(watcher, &QFutureWatcher<QImage>::finished, this, [this, watcher]() {
watcher->deleteLater();
m_result = watcher->result();
if (m_result.isNull()) {
Q_EMIT errorOccurred();
} else {
Q_EMIT finished(m_result);
}
});
watcher->setFuture(QtConcurrent::run(readImage, m_pipeFileDescriptor, metadata));
// The ownership of the pipe file descriptor has been moved to the worker thread.
m_pipeFileDescriptor = -1;
}
ScreenShotSourceArea2::ScreenShotSourceArea2(const QRect &area,
PlatformKWinWayland2::ScreenShotFlags flags)
: ScreenShotSource2(QStringLiteral("CaptureArea"),
qint32(area.x()), qint32(area.y()),
quint32(area.width()), quint32(area.height()),
screenShotFlagsToVardict(flags))
{
}
ScreenShotSourceInteractive2::ScreenShotSourceInteractive2(PlatformKWinWayland2::InteractiveKind kind,
PlatformKWinWayland2::ScreenShotFlags flags)
: ScreenShotSource2(QStringLiteral("CaptureInteractive"),
quint32(kind), screenShotFlagsToVardict(flags))
{
}
ScreenShotSourceScreen2::ScreenShotSourceScreen2(const QScreen *screen,
PlatformKWinWayland2::ScreenShotFlags flags)
: ScreenShotSource2(QStringLiteral("CaptureScreen"),
screen->name(), screenShotFlagsToVardict(flags))
{
}
ScreenShotSourceMeta2::ScreenShotSourceMeta2(const QVector<ScreenShotSource2 *> &sources)
: m_sources(sources)
{
for (ScreenShotSource2 *source : sources) {
source->setParent(this);
connect(source, &ScreenShotSource2::finished,
this, &ScreenShotSourceMeta2::handleSourceFinished);
connect(source, &ScreenShotSource2::errorOccurred,
this, &ScreenShotSourceMeta2::handleSourceErrorOccurred);
}
}
void ScreenShotSourceMeta2::handleSourceFinished()
{
const bool isFinished = std::all_of(m_sources.constBegin(), m_sources.constEnd(), [](const ScreenShotSource2 *source) {
return !source->result().isNull();
});
if (!isFinished) {
return;
}
QVector<QImage> results;
results.reserve(m_sources.count());
for (const ScreenShotSource2 *source : qAsConst(m_sources)) {
results.append(source->result());
}
Q_EMIT finished(results);
}
void ScreenShotSourceMeta2::handleSourceErrorOccurred()
{
Q_EMIT errorOccurred();
}
std::unique_ptr<PlatformKWinWayland2> PlatformKWinWayland2::create()
{
QDBusConnectionInterface *interface = QDBusConnection::sessionBus().interface();
if (interface->isServiceRegistered(s_screenShotService)) {
return std::unique_ptr<PlatformKWinWayland2>(new PlatformKWinWayland2());
}
return nullptr;
}
PlatformKWinWayland2::PlatformKWinWayland2(QObject *parent)
: Platform(parent)
{
}
QString PlatformKWinWayland2::platformName() const
{
return QStringLiteral("PlatformKWinWayland2");
}
Platform::GrabModes PlatformKWinWayland2::supportedGrabModes() const
{
Platform::GrabModes supportedModes = GrabMode::AllScreens |
GrabMode::WindowUnderCursor |
GrabMode::PerScreenImageNative |
GrabMode::AllScreensScaled;
const QList<QScreen *> screens = QGuiApplication::screens();
if (screens.count() > 1) {
supportedModes |= GrabMode::CurrentScreen;
}
return supportedModes;
}
Platform::ShutterModes PlatformKWinWayland2::supportedShutterModes() const
{
return ShutterMode::Immediate;
}
static QRect workArea()
{
const QList<QScreen *> screens = QGuiApplication::screens();
auto accumulateFunc = [](const QRect &accumulator, const QScreen *screen) {
return accumulator.united(screen->geometry());
};
return std::accumulate(screens.begin(), screens.end(), QRect(), accumulateFunc);
}
void PlatformKWinWayland2::doGrab(ShutterMode, GrabMode theGrabMode,
bool theIncludePointer, bool theIncludeDecorations)
{
ScreenShotFlags flags = ScreenShotFlag::NativeSize;
if (theIncludeDecorations) {
flags |= ScreenShotFlag::IncludeDecoration;
}
if (theIncludePointer) {
flags |= ScreenShotFlag::IncludeCursor;
}
switch (theGrabMode) {
case GrabMode::AllScreens:
takeScreenShotArea(workArea(), flags & ~ScreenShotFlags(ScreenShotFlag::NativeSize));
break;
case GrabMode::CurrentScreen:
takeScreenShotInteractive(InteractiveKind::Screen, flags);
break;
case GrabMode::WindowUnderCursor:
takeScreenShotInteractive(InteractiveKind::Window, flags);
break;
case GrabMode::AllScreensScaled:
takeScreenShotArea(workArea(), flags);
break;
case GrabMode::PerScreenImageNative:
takeScreenShotScreens(QGuiApplication::screens(), flags);
break;
case GrabMode::InvalidChoice:
case GrabMode::TransientWithParent:
case GrabMode::ActiveWindow:
Q_EMIT newScreenshotFailed();
break;
}
}
void PlatformKWinWayland2::trackSource(ScreenShotSource2 *source)
{
connect(source, &ScreenShotSourceArea2::finished, this, [this, source](const QImage &image) {
source->deleteLater();
Q_EMIT newScreenshotTaken(QPixmap::fromImage(image));
});
connect(source, &ScreenShotSourceArea2::errorOccurred, this, [this, source]() {
source->deleteLater();
Q_EMIT newScreenshotFailed();
});
}
void PlatformKWinWayland2::trackSource(ScreenShotSourceMeta2 *source)
{
connect(source, &ScreenShotSourceMeta2::finished, this, [this, source](const QVector<QImage> &images) {
source->deleteLater();
Q_EMIT newScreensScreenshotTaken(images);
});
connect(source, &ScreenShotSourceMeta2::errorOccurred, this, [this, source]() {
source->deleteLater();
Q_EMIT newScreenshotFailed();
});
}
void PlatformKWinWayland2::takeScreenShotArea(const QRect &area, ScreenShotFlags flags)
{
trackSource(new ScreenShotSourceArea2(area, flags));
}
void PlatformKWinWayland2::takeScreenShotInteractive(InteractiveKind kind,
ScreenShotFlags flags)
{
trackSource(new ScreenShotSourceInteractive2(kind, flags));
}
void PlatformKWinWayland2::takeScreenShotScreens(const QList<QScreen *> &screens,
ScreenShotFlags flags)
{
QVector<ScreenShotSource2 *> sources;
sources.reserve(screens.count());
for (QScreen *screen : screens) {
sources.append(new ScreenShotSourceScreen2(screen, flags));
}
trackSource(new ScreenShotSourceMeta2(sources));
}
/*
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#pragma once
#include "Platform.h"
#include <QImage>
#include <QScreen>
#include <memory>
class ScreenShotSource2;
class ScreenShotSourceMeta2;
/**
* The PlatformKWinWayland2 class uses the org.kde.KWin.ScreenShot2 dbus interface
* for taking screenshots of screens and windows.
*/
class PlatformKWinWayland2 final : public Platform
{
Q_OBJECT
public:
static std::unique_ptr<PlatformKWinWayland2> create();
enum class ScreenShotFlag : uint {
IncludeCursor = 0x1,
IncludeDecoration = 0x2,
NativeSize = 0x4,
};
Q_DECLARE_FLAGS(ScreenShotFlags, ScreenShotFlag)
enum class InteractiveKind : uint {
Window = 0,
Screen = 1,
};
QString platformName() const override;
GrabModes supportedGrabModes() const override;
ShutterModes supportedShutterModes() const override;
public Q_SLOTS:
void doGrab(Platform::ShutterMode theShutterMode, Platform::GrabMode theGrabMode,
bool theIncludePointer, bool theIncludeDecorations) override;
private:
explicit PlatformKWinWayland2(QObject *parent = nullptr);
void takeScreenShotInteractive(InteractiveKind kind, ScreenShotFlags flags);
void takeScreenShotArea(const QRect &area, ScreenShotFlags flags);
void takeScreenShotScreens(const QList<QScreen *> &screens, ScreenShotFlags flags);
void trackSource(ScreenShotSource2 *source);
void trackSource(ScreenShotSourceMeta2 *source);
};
/**
* The ScreenShotSource2 class is the base class for screenshot sources that use the
* org.kde.KWin.ScreenShot2 dbus interface.
*/
class ScreenShotSource2 : public QObject
{
Q_OBJECT
public:
template <typename ...ArgType>
explicit ScreenShotSource2(const QString &methodName, ArgType... arguments);
~ScreenShotSource2() override;
QImage result() const;
Q_SIGNALS:
void finished(const QImage &image);
void errorOccurred();
private Q_SLOTS:
void handleMetaDataReceived(const QVariantMap &metadata);
private:
QImage m_result;
int m_pipeFileDescriptor = -1;
};
/**
* The ScreenShotSourceArea2 class provides a convenient way to take a screenshot of the
* specified area using the org.kde.KWin.ScreenShot2 dbus interface.
*/
class ScreenShotSourceArea2 final : public ScreenShotSource2
{
Q_OBJECT
public:
ScreenShotSourceArea2(const QRect &area, PlatformKWinWayland2::ScreenShotFlags flags);
};
/**
* The ScreenShotSourceInteractive2 class provides a convenient way to take a screenshot
* of a screen or a window as selected by the user. This uses the org.kde.KWin.ScreenShot2
* dbus interface.
*/
class ScreenShotSourceInteractive2 final : public ScreenShotSource2
{
Q_OBJECT
public:
ScreenShotSourceInteractive2(PlatformKWinWayland2::InteractiveKind kind,
PlatformKWinWayland2::ScreenShotFlags flags);
};
/**
* The ScreenShotSourceScreen2 class provides a convenient way to take a screenshot of
* the specified screen using the org.kde.KWin.ScreenShot2 dbus interface.
*/
class ScreenShotSourceScreen2 final : public ScreenShotSource2
{
Q_OBJECT
public:
ScreenShotSourceScreen2(const QScreen *screen, PlatformKWinWayland2::ScreenShotFlags flags);
};
/**
* The ScreenShotSourceMeta2 class represents a screenshot source that is made of several
* other sources.
*/
class ScreenShotSourceMeta2 final : public QObject
{
Q_OBJECT
public:
explicit ScreenShotSourceMeta2(const QVector<ScreenShotSource2 *> &sources);
Q_SIGNALS:
void finished(const QVector<QImage> &images);
void errorOccurred();
private Q_SLOTS:
void handleSourceFinished();
void handleSourceErrorOccurred();
private:
QVector<ScreenShotSource2 *> m_sources;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(PlatformKWinWayland2::ScreenShotFlags)
......@@ -9,6 +9,7 @@
#include "PlatformNull.h"
#include "PlatformKWinWayland.h"
#include "PlatformKWinWayland2.h"
#ifdef XCB_FOUND
#include "PlatformXcb.h"
......@@ -22,6 +23,10 @@ PlatformPtr loadPlatform()
// but the X11 grabber won't work in that case. So force the Wayland grabber
// in Wayland sessions.
if (KWindowSystem::isPlatformWayland() || qstrcmp(qgetenv("XDG_SESSION_TYPE").data(), "wayland") == 0) {
std::unique_ptr<Platform> platform = PlatformKWinWayland2::create();
if (platform) {
return platform;
}
return std::make_unique<PlatformKWinWayland>();
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment