Revision control
Copy as Markdown
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QtCore/QTimer>
#include <QtCore/QDebug>
#include <QtCore/QFileInfo>
#include <QtCore/QHashFunctions>
#include <QtGui/QCursor>
#include <QtGui/QGuiApplication>
#include <QtGui/QImage>
#include <QtCore/QLoggingCategory>
#include <QtCore/QElapsedTimer>
#include <QtQuick/QQuickView>
#include <QtQuick/QQuickItem>
#include <QtQuickControls2/qquickstyle.h>
#include <QQmlApplicationEngine>
#include <QtQml/qqmlcomponent.h>
#include <QtQuickTemplates2/private/qquickapplicationwindow_p.h>
#include <QtQuick/private/qquickscreen_p.h>
#ifdef Q_OS_WIN
# include <fcntl.h>
# include <io.h>
#endif // Q_OS_WIN
// Timeout values:
// A valid screen grab requires the scene to not change
// for SCENE_STABLE_TIME ms
#define SCENE_STABLE_TIME 200
// Give up after SCENE_TIMEOUT ms
#define SCENE_TIMEOUT 6000
#define GRABBERDEBUG qCDebug(lcGrabber()) << (QByteArray::number(runtime.elapsed()) + "ms").constData()
Q_LOGGING_CATEGORY(lcGrabber, "qt.baseline.scenegrabber")
static const QSize DefaultGrabSize(320, 480);
class GrabbingView : public QQuickView
{
Q_OBJECT
public:
GrabbingView(const QString &outputFile, const bool useAppWindow)
: ofile(outputFile), justShow(outputFile.isEmpty()), preferAppWindow(useAppWindow)
{
if (justShow)
return;
grabTimer = new QTimer(this);
grabTimer->setSingleShot(true);
int stableTime = qEnvironmentVariableIntegerValue("LANCELOT_SCENE_STABLE_TIME").value_or(SCENE_STABLE_TIME);
grabTimer->setInterval(stableTime);
connect(grabTimer, &QTimer::timeout, this, &GrabbingView::grab);
if (!preferAppWindow)
QObject::connect(this, &QQuickWindow::afterRendering, this, &GrabbingView::startGrabbing, Qt::SingleShotConnection);
sceneTimeout = qEnvironmentVariableIntegerValue("LANCELOT_SCENE_TIMEOUT").value_or(SCENE_TIMEOUT);
QTimer::singleShot(sceneTimeout, this, &GrabbingView::timedOut);
runtime.start();
GRABBERDEBUG << "Starting, scene timeout:" << sceneTimeout << "stableTime:" << stableTime;
}
void setApplicationWindow(QWindow* window) {
GRABBERDEBUG << "Using ApplicationWindow as visual parent" << this;
appwindow = qobject_cast<QQuickApplicationWindow *>(window);
if (preferAppWindow && !justShow)
QObject::connect(appwindow, &QQuickWindow::afterRendering, this, &GrabbingView::startGrabbing);
}
QQuickApplicationWindow* appWindow() { return appwindow; }
private slots:
void startGrabbing()
{
GRABBERDEBUG << "afterRendering received";
grab();
}
void grab()
{
if (isGrabbing) {
GRABBERDEBUG << "Already grabbing, skipping";
return;
}
QScopedValueRollback grabGuard(isGrabbing, true);
if (QQuickItem *rootObject = this->rootObject()) {
QVariant v = rootObject->property("suspendGrabbing");
if (v.isValid() && v.canConvert<bool>() && v.toBool()) {
GRABBERDEBUG << "Skipping grab, item has suspended grabbing";
grabTimer->start();
return;
}
}
grabNo++;
GRABBERDEBUG << "Starting grab no." << grabNo;
QImage img;
img = appwindow ? appwindow->grabWindow() : grabWindow();
GRABBERDEBUG << "Finishing grab no." << grabNo;
if (!img.isNull() && img == lastGrab) {
sceneStabilized();
} else {
lastGrab = img;
grabTimer->start();
}
}
void sceneStabilized()
{
GRABBERDEBUG << "...sceneStabilized IN";
if (QGuiApplication::platformName() == QLatin1String("eglfs")) {
QSize grabSize = initialSize().isEmpty() ? DefaultGrabSize : initialSize();
lastGrab = lastGrab.copy(QRect(QPoint(0, 0), grabSize));
}
if (ofile == "-") { // Write to stdout
QFile of;
#ifdef Q_OS_WIN
// Make sure write to stdout doesn't do LF->CRLF
_setmode(_fileno(stdout), _O_BINARY);
#endif // Q_OS_WIN
if (!of.open(1, QIODevice::WriteOnly) || !lastGrab.save(&of, "ppm")) {
qWarning() << "Error: failed to write grabbed image to stdout.";
QGuiApplication::exit(2);
return;
}
} else {
if (!lastGrab.save(ofile)) {
qWarning() << "Error: failed to store grabbed image to" << ofile;
QGuiApplication::exit(2);
return;
}
}
GRABBERDEBUG << "...sceneStabilized OUT";
QGuiApplication::exit(0);
}
void timedOut()
{
GRABBERDEBUG << "Timedout, lastGrab:" << lastGrab << "grabtimer:" << grabTimer->isActive() << grabTimer->remainingTime();
qWarning() << "Error: timed out waiting for scene to stabilize." << grabNo << "grab(s) done. Last grab was" << (lastGrab.isNull() ? "invalid." : "valid.");
QGuiApplication::exit(3);
}
private:
QImage lastGrab;
QTimer *grabTimer = nullptr;
QElapsedTimer runtime;
QString ofile;
int sceneTimeout = 0;
int grabNo = 0;
bool isGrabbing = false;
bool justShow = false;
QQuickApplicationWindow *appwindow = nullptr;
bool preferAppWindow = false;
};
int main(int argc, char *argv[])
{
QHashSeed::setDeterministicGlobalSeed();
QGuiApplication a(argc, argv);
// Parse command line
QString ifile, ofile, style;
bool noText = false;
bool justShow = false;
QStringList args = a.arguments();
int i = 0;
bool argError = false;
bool useAppWindow = false;
while (++i < args.size()) {
QString arg = args.at(i);
if ((arg == "-o") && (i < args.size()-1)) {
ofile = args.at(++i);
}
else if (arg == "-notext") {
noText = true;
}
else if (arg == "--cache-distance-fields") {
;
}
else if (arg == "-viewonly") {
justShow = true;
}
else if (arg == "-style") {
if (i < args.size()-1)
style = args.at(++i);
else {
argError = true;
break;
}
}
else if (arg == "-useAppWindow") {
useAppWindow = true;
}
else if (ifile.isEmpty()) {
ifile = arg;
}
else {
argError = true;
break;
}
}
if (argError || ifile.isEmpty() || (ofile.isEmpty() && !justShow)) {
qWarning() << "Usage:" << args.at(0).toLatin1().constData()
<< "[-notext] [-style stylename] <qml-infile> {-o <outfile or - for ppm on stdout>|-viewonly}";
return 1;
}
QFileInfo ifi(ifile);
if (!ifi.exists() || !ifi.isReadable() || !ifi.isFile()) {
qWarning() << args.at(0).toLatin1().constData() << " error: unreadable input file" << ifile;
return 1;
}
// End parsing
// Enable debugging
const QString debugFile = qEnvironmentVariable("QMLSCENEGRABBER_DEBUGFILE");
if (!debugFile.isNull() && ifile.contains(debugFile))
QLoggingCategory::setFilterRules(lcGrabber().categoryName() + QLatin1String(".debug=true"));
if (!style.isEmpty())
QQuickStyle::setStyle(style);
GrabbingView v(ofile, useAppWindow);
// prefer a screen with a 1.0 DPR
QScreen *preferredScreen = QGuiApplication::primaryScreen();
if (!qFuzzyCompare(QGuiApplication::primaryScreen()->devicePixelRatio(), 1.0)) {
for (const auto screen : QGuiApplication::screens()) {
if (qFuzzyCompare(screen->devicePixelRatio(), 1.0)) {
preferredScreen = screen;
break;
}
}
}
Q_ASSERT(preferredScreen);
const QRect preferredScreenRect = preferredScreen->availableGeometry();
if (useAppWindow) {
QQmlEngine *engine = new QQmlEngine;
{
// test qml component creation
QQmlComponent component(engine, QUrl::fromLocalFile(ifile));
if (component.isError()) {
qWarning().nospace() << "Failed to load QML at " << ifile << ": " << component.errors();
return 1;
}
auto *itemObject = qobject_cast<QQuickItem*>(component.create());
if (component.isError()) {
qWarning().nospace() << "Failed to create object instance from QML at "
<< ifile << ": " << component.errors();
return 1;
}
// TODO: Hack to import native style forcefully for windows
QString appCompStr = "import QtQuick.Controls\n";
if (QQuickStyle::name() == "Windows")
appCompStr += "import QtQuick.NativeStyle\n";
appCompStr += "ApplicationWindow{}";
// Create application window
QQmlComponent appWndComp(engine);
appWndComp.setData(appCompStr.toLatin1(), QUrl::fromLocalFile(""));
auto *appWindow = qobject_cast<QQuickApplicationWindow *>(appWndComp.create());
if (!appWindow) {
qWarning() << "Error: failed to create application window.";
QGuiApplication::exit(2);
}
appWindow->resize(itemObject->size().toSize());
// Use application window as visual parent to the loaded component
v.setApplicationWindow(appWindow);
itemObject->setParentItem(v.appWindow()->contentItem());
itemObject->setParent(v.appWindow());
}
auto *wrappedScreen = new QQuickScreenInfo(engine, preferredScreen);
v.appWindow()->setScreen(wrappedScreen);
v.appWindow()->setPosition(preferredScreenRect.topLeft());
v.appWindow()->show();
} else {
v.setSource(QUrl::fromLocalFile(ifile));
if (noText) {
const QList<QQuickItem*> items = v.rootObject()->findChildren<QQuickItem*>();
for (QQuickItem *item : items) {
if (QByteArray(item->metaObject()->className()).contains("Text"))
item->setVisible(false);
}
}
if (v.initialSize().isEmpty())
v.resize(DefaultGrabSize);
v.setScreen(preferredScreen);
v.setPosition(preferredScreenRect.topLeft());
v.show();
}
QCursor::setPos(preferredScreenRect.topLeft());
int retVal = a.exec();
qCDebug(lcGrabber) << "...retVal=" << retVal;
return retVal;
}
#include "main.moc"