Skip to content

Commit

Permalink
[lipstick] Write screenshots to disk asynchronously. Contributes to J…
Browse files Browse the repository at this point in the history
…B#42583

It can take a significant amount of time to save the image on devices
with larger screens. Do the save in a thread and return an object
which can notify when it's done.  The HomeApplication API which returns
a boolean indicating success continues to block, wouldn't want a returned
object to be mistaken for success.
  • Loading branch information
adenexter committed Oct 18, 2018
1 parent fc36577 commit dc8e4b9
Show file tree
Hide file tree
Showing 6 changed files with 198 additions and 21 deletions.
1 change: 1 addition & 0 deletions plugin/lipstickplugin.cpp
Expand Up @@ -71,6 +71,7 @@ void LipstickPlugin::registerTypes(const char *uri)
qmlRegisterType<WindowPixmapItem>("org.nemomobile.lipstick", 0, 1, "WindowPixmapItem");
qmlRegisterType<WindowProperty>("org.nemomobile.lipstick", 0, 1, "WindowProperty");
qmlRegisterSingletonType<LipstickApi>("org.nemomobile.lipstick", 0, 1, "Lipstick", lipstickApi_callback);
qmlRegisterUncreatableType<ScreenshotResult>("org.nemomobile.lipstick", 0, 1, "ScreenshotResult", "This type is initialized by LipstickApi");

qmlRegisterType<LipstickCompositorWindow>();
qmlRegisterType<QObjectListModel>();
Expand Down
8 changes: 7 additions & 1 deletion src/homeapplication.cpp
Expand Up @@ -407,5 +407,11 @@ void HomeApplication::connectFrameSwappedSignal(bool mainWindowVisible)

bool HomeApplication::takeScreenshot(const QString &path)
{
return ScreenshotService::saveScreenshot(path);
if (ScreenshotResult *result = ScreenshotService::saveScreenshot(path)) {
result->waitForFinished();

return result->status() == ScreenshotResult::Finished;
} else {
return false;
}
}
4 changes: 2 additions & 2 deletions src/lipstickapi.cpp
Expand Up @@ -37,7 +37,7 @@ QObject *LipstickApi::compositor() const
return LipstickCompositor::instance();
}

void LipstickApi::takeScreenshot(const QString &path)
ScreenshotResult *LipstickApi::takeScreenshot(const QString &path)
{
HomeApplication::instance()->takeScreenshot(path);
return ScreenshotService::saveScreenshot(path);
}
4 changes: 2 additions & 2 deletions src/lipstickapi.h
Expand Up @@ -17,7 +17,7 @@
#define LIPSTICKAPI_H

#include <QObject>
#include "lipstickglobal.h"
#include "screenshotservice.h"

class LIPSTICK_EXPORT LipstickApi : public QObject
{
Expand All @@ -31,7 +31,7 @@ class LIPSTICK_EXPORT LipstickApi : public QObject
bool active() const;
QObject *compositor() const;

Q_INVOKABLE void takeScreenshot(const QString &path = QString());
Q_INVOKABLE ScreenshotResult *takeScreenshot(const QString &path = QString());

signals:
void activeChanged();
Expand Down
161 changes: 146 additions & 15 deletions src/screenshotservice.cpp
Expand Up @@ -20,18 +20,153 @@
#include <QImage>
#include <QScreen>
#include <QTransform>
#include <QThreadPool>
#include <private/qquickwindow_p.h>

bool ScreenshotService::saveScreenshot(const QString &path)
#include <unistd.h>
#include <sys/eventfd.h>

ScreenshotResult::ScreenshotResult(QObject *parent)
: QObject(parent)
, m_notifier(0, QSocketNotifier::Read)
, m_notifierId(0)
, m_status(Error)
{
if (path.isEmpty()) {
qWarning() << "Screenshot path is empty.";
return false;
}

ScreenshotResult::ScreenshotResult(int notifierId, const QUrl &path, QObject *parent)
: QObject(parent)
, m_notifier(notifierId, QSocketNotifier::Read)
, m_path(path)
, m_notifierId(notifierId)
{
connect(&m_notifier, &QSocketNotifier::activated, this, [this] {
if (m_status == Writing) {
quint64 status = Writing;
ssize_t unused = ::read(m_notifier.socket(), &status, sizeof(status));
Q_UNUSED(unused);

m_status = Status(status);
emit statusChanged();

if (m_status == Finished) {
emit finished();
} else if (m_status == Error) {
emit error();
}

deleteLater();
}
});
}

ScreenshotResult::~ScreenshotResult()
{
if (m_notifierId != 0) {
::close(m_notifierId);
}
}

ScreenshotResult::Status ScreenshotResult::status() const
{
return m_status;
}

QUrl ScreenshotResult::path() const
{
return m_path;
}

void ScreenshotResult::waitForFinished()
{
fd_set fdread;
FD_ZERO(&fdread);
FD_SET(m_notifierId, &fdread);

for (;;) {
const int ret = select(m_notifierId + 1, &fdread, nullptr, nullptr, nullptr);
if (ret >= 0) {
quint64 status = Writing;
ssize_t unused = ::read(m_notifierId, &status, sizeof(status));
Q_UNUSED(unused);

m_status = Status(status);
break;
} else if (errno != EINTR) {
m_status = Error;
break;
}
}

deleteLater();
}

class ScreenshotWriter : public QRunnable
{
public:
ScreenshotWriter(int notifierId, const QImage &image, const QString &path, int rotation);
~ScreenshotWriter();

void run() override;

private:
QImage m_image;
const QString m_path;
const int m_notifierId;
const int m_rotation;
};

ScreenshotWriter::ScreenshotWriter(int notifierId, const QImage &image, const QString &path, int rotation)
: m_image(image)
, m_path(path)
, m_notifierId(::dup(notifierId))
, m_rotation(rotation)
{
setAutoDelete(true);
}

ScreenshotWriter::~ScreenshotWriter()
{
::close(m_notifierId);
}

void ScreenshotWriter::run()
{
if (m_rotation != 0) {
QTransform xform;
xform.rotate(m_rotation);
m_image = m_image.transformed(xform, Qt::SmoothTransformation);
}

const quint64 status = m_image.save(m_path)
? ScreenshotResult::Finished
: ScreenshotResult::Error;
ssize_t unused = ::write(m_notifierId, &status, sizeof(status));
Q_UNUSED(unused);
}

ScreenshotResult *ScreenshotService::saveScreenshot(const QString &path)
{
LipstickCompositor *compositor = LipstickCompositor::instance();
if (!compositor) {
return false;
return nullptr;
}

const int notifierId = ::eventfd(0, 0);
if (notifierId == -1) {
return nullptr;
}

ScreenshotResult * const result = new ScreenshotResult(notifierId, path, compositor);

if (path.isEmpty()) {
qWarning() << "Screenshot path is empty.";

const quint64 status = ScreenshotResult::Error;
ssize_t unused = ::write(notifierId, &status, sizeof(status));
Q_UNUSED(unused);

return result;
}

QQuickWindowPrivate *wd = QQuickWindowPrivate::get(compositor);
Expand All @@ -41,18 +176,14 @@ bool ScreenshotService::saveScreenshot(const QString &path)

QImage grab(compositor->grabWindow());

int rotation(QGuiApplication::primaryScreen()->angleBetween(Qt::PrimaryOrientation, compositor->topmostWindowOrientation()));
if (rotation != 0) {
QTransform xform;
xform.rotate(rotation);
grab = grab.transformed(xform, Qt::SmoothTransformation);
}

const bool saved = grab.save(path);

if (renderStage) {
renderStage->setBypassHwc(false);
}

return saved;
const int rotation(QGuiApplication::primaryScreen()->angleBetween(
Qt::PrimaryOrientation, compositor->topmostWindowOrientation()));

QThreadPool::globalInstance()->start(new ScreenshotWriter(notifierId, grab, path, rotation));

return result;
}
41 changes: 40 additions & 1 deletion src/screenshotservice.h
Expand Up @@ -16,11 +16,50 @@
#define SCREENSHOTSERVICE_H

#include <QObject>
#include <QSocketNotifier>
#include <QUrl>

#include "lipstickglobal.h"

class LIPSTICK_EXPORT ScreenshotResult : public QObject
{
Q_OBJECT
Q_PROPERTY(Status status READ status NOTIFY statusChanged)
Q_PROPERTY(QUrl path READ path CONSTANT)
public:
enum Status {
Writing,
Finished,
Error
};
Q_ENUM(Status)

explicit ScreenshotResult(QObject *parent = nullptr); // For the benefit of qmlRegisterUncreatableType().
ScreenshotResult(int notifierId, const QUrl &path, QObject *parent = nullptr);
~ScreenshotResult();

Status status() const;
QUrl path() const;

void waitForFinished();

signals:
void finished();
void statusChanged();

void error();

private:
QSocketNotifier m_notifier;
const QUrl m_path;
const int m_notifierId;
Status m_status = Writing;
};

class ScreenshotService
{
public:
static bool saveScreenshot(const QString &path);
static ScreenshotResult *saveScreenshot(const QString &path);
};

#endif // SCREENSHOTSERVICE_H

0 comments on commit dc8e4b9

Please sign in to comment.