Offscreen: add configuration update API

Add support for updating the platform configuration at runtime,
which  enables use cases such as changing the screen configuration
from an auto test.

Provide functions for getting and setting the complete
configuration as a QJsonObject:

    QJsonObject configuration()
    void setConfiguration(QJsonObject)

User code can then either set a completely new configuration,
or make a smaller update by using a get-modify-set sequence:

    // Set the logical DPI for screen 0 to 192:
    auto config = configuration();
    config[“screens”][0][“logicalDpi] = 192
    setConfiguration(config);

This approach means we can expose a minimal but complete
API, at the cost of doing more work in the offscreen plugin
in order to figure out what changed.

Note that this change does not export thew API from the
platform plugin.

Change-Id: If776c36d5ae6d72dca715cc8e89e42768ed32c60
Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
This commit is contained in:
Morten Johan Sørvig 2021-03-17 13:14:44 +01:00
parent 1117e732d9
commit 95d5710d7b
5 changed files with 203 additions and 42 deletions

View File

@ -55,7 +55,11 @@ QPlatformWindow *QOffscreenScreen::windowContainingCursor = nullptr;
QList<QPlatformScreen *> QOffscreenScreen::virtualSiblings() const QList<QPlatformScreen *> QOffscreenScreen::virtualSiblings() const
{ {
return m_integration->screens(); QList<QPlatformScreen *> platformScreens;
for (auto screen : m_integration->screens()) {
platformScreens.append(screen);
}
return platformScreens;
} }
class QOffscreenCursor : public QPlatformCursor class QOffscreenCursor : public QPlatformCursor

View File

@ -93,7 +93,7 @@ public:
} }
}; };
QOffscreenIntegration::QOffscreenIntegration() QOffscreenIntegration::QOffscreenIntegration(const QStringList& paramList)
{ {
#if defined(Q_OS_UNIX) #if defined(Q_OS_UNIX)
#if defined(Q_OS_MAC) #if defined(Q_OS_MAC)
@ -109,6 +109,9 @@ QOffscreenIntegration::QOffscreenIntegration()
m_drag.reset(new QOffscreenDrag); m_drag.reset(new QOffscreenDrag);
#endif #endif
m_services.reset(new QPlatformServices); m_services.reset(new QPlatformServices);
QJsonObject config = resolveConfigFileConfiguration(paramList).value_or(defaultConfiguration());
setConfiguration(config);
} }
QOffscreenIntegration::~QOffscreenIntegration() QOffscreenIntegration::~QOffscreenIntegration()
@ -118,8 +121,12 @@ QOffscreenIntegration::~QOffscreenIntegration()
} }
/* /*
The offscren platform plugin is configurable with a JSON configuration The offscren platform plugin is configurable with a JSON configuration.
file. Write the config to disk and pass the file path as a platform argument: The confiuration can be provided either from a file on disk on startup,
or at by calling setConfiguration().
To provide a configuration on startuip, write the config to disk and pass
the file path as a platform argument:
./myapp -platform offscreen:configfile=/path/to/config.json ./myapp -platform offscreen:configfile=/path/to/config.json
@ -130,9 +137,9 @@ QOffscreenIntegration::~QOffscreenIntegration()
"screens": [<screens>], "screens": [<screens>],
} }
Screen: "screens" is an array of:
{ {
"name" : string, "name": string,
"x": int, "x": int,
"y": int, "y": int,
"width": int, "width": int,
@ -142,9 +149,29 @@ QOffscreenIntegration::~QOffscreenIntegration()
"dpr": double, "dpr": double,
} }
*/ */
void QOffscreenIntegration::configure(const QStringList& paramList)
QJsonObject QOffscreenIntegration::defaultConfiguration() const
{
const auto defaultScreen = QJsonObject {
{"name", ""},
{"x", 0},
{"y", 0},
{"width", 800},
{"height", 800},
{"logicalDpi", 96},
{"logicalBaseDpi", 96},
{"dpr", 1.0},
};
const auto defaultConfiguration = QJsonObject {
{"synchronousWindowSystemEvents", false},
{"windowFrameMargins", true},
{"screens", QJsonArray { defaultScreen } },
};
return defaultConfiguration;
}
std::optional<QJsonObject> QOffscreenIntegration::resolveConfigFileConfiguration(const QStringList& paramList) const
{ {
// Use config file configuring platform plugin, if one was specified
bool hasConfigFile = false; bool hasConfigFile = false;
QString configFilePath; QString configFilePath;
for (const QString &param : paramList) { for (const QString &param : paramList) {
@ -152,17 +179,11 @@ void QOffscreenIntegration::configure(const QStringList& paramList)
QString configPrefix(QLatin1String("configfile=")); QString configPrefix(QLatin1String("configfile="));
if (param.startsWith(configPrefix)) { if (param.startsWith(configPrefix)) {
hasConfigFile = true; hasConfigFile = true;
configFilePath= param.mid(configPrefix.length()); configFilePath = param.mid(configPrefix.length());
} }
} }
if (!hasConfigFile)
// Create the default screen if there was no config file return std::nullopt;
if (!hasConfigFile) {
QOffscreenScreen *offscreenScreen = new QOffscreenScreen(this);
m_screens.append(offscreenScreen);
QWindowSystemInterface::handleScreenAdded(offscreenScreen);
return;
}
// Read config file // Read config file
if (configFilePath.isEmpty()) if (configFilePath.isEmpty())
@ -179,28 +200,145 @@ void QOffscreenIntegration::configure(const QStringList& paramList)
if (config.isNull()) if (config.isNull())
qFatal("Platform config file parse error: %s", qPrintable(error.errorString())); qFatal("Platform config file parse error: %s", qPrintable(error.errorString()));
// Apply configuration (create screens) return config.object();
bool synchronousWindowSystemEvents = config["synchronousWindowSystemEvents"].toBool(false); }
void QOffscreenIntegration::setConfiguration(const QJsonObject &configuration)
{
// Apply the new configuration, diffing against the current m_configuration
const bool synchronousWindowSystemEvents = configuration["synchronousWindowSystemEvents"].toBool(
m_configuration["synchronousWindowSystemEvents"].toBool(false));
QWindowSystemInterface::setSynchronousWindowSystemEvents(synchronousWindowSystemEvents); QWindowSystemInterface::setSynchronousWindowSystemEvents(synchronousWindowSystemEvents);
m_windowFrameMarginsEnabled = config["windowFrameMargins"].toBool(true);
QJsonArray screens = config["screens"].toArray(); m_windowFrameMarginsEnabled = configuration["windowFrameMargins"].toBool(
for (QJsonValue screenValue : screens) { m_configuration["windowFrameMargins"].toBool(true));
QJsonObject screen = screenValue.toObject();
if (screen.isEmpty()) { // Diff screens array, using the screen name as the screen identity.
qWarning("QOffscreenIntegration::initializeWithPlatformArguments: empty screen object"); QJsonArray currentScreens = m_configuration["screens"].toArray();
QJsonArray newScreens = configuration["screens"].toArray();
auto getScreenNames = [](const QJsonArray &screens) -> QList<QString> {
QList<QString> names;
for (QJsonValue screen : screens) {
names.append(screen["name"].toString());
};
std::sort(names.begin(), names.end());
return names;
};
auto currentNames = getScreenNames(currentScreens);
auto newNames = getScreenNames(newScreens);
QList<QString> present;
std::set_intersection(currentNames.begin(), currentNames.end(), newNames.begin(), newNames.end(),
std::inserter(present, present.begin()));
QList<QString> added;
std::set_difference(newNames.begin(), newNames.end(), currentNames.begin(), currentNames.end(),
std::inserter(added, added.begin()));
QList<QString> removed;
std::set_difference(currentNames.begin(), currentNames.end(), newNames.begin(), newNames.end(),
std::inserter(removed, removed.begin()));
auto platformScreenByName = [](const QString &name, QList<QOffscreenScreen *> screens) -> QOffscreenScreen * {
for (QOffscreenScreen *screen : screens) {
if (screen->m_name == name)
return screen;
}
Q_UNREACHABLE();
};
auto screenConfigByName = [](const QString &name, QJsonArray screenConfigs) -> QJsonValue {
for (QJsonValue screenConfig : screenConfigs) {
if (screenConfig["name"].toString() == name)
return screenConfig;
}
Q_UNREACHABLE();
};
auto geometryFromConfig = [](const QJsonObject &config) -> QRect {
return QRect(config["x"].toInt(0), config["y"].toInt(0), config["width"].toInt(640), config["height"].toInt(480));
};
// Remove removed screens
for (const QString &remove : removed) {
QOffscreenScreen *screen = platformScreenByName(remove, m_screens);
m_screens.removeAll(screen);
QWindowSystemInterface::handleScreenRemoved(screen);
}
// Add new screens
for (const QString &add : added) {
QJsonValue configValue = screenConfigByName(add, newScreens);
QJsonObject config = configValue.toObject();
if (config.isEmpty()) {
qWarning("empty screen object");
continue; continue;
} }
QOffscreenScreen *offscreenScreen = new QOffscreenScreen(this); QOffscreenScreen *offscreenScreen = new QOffscreenScreen(this);
offscreenScreen->m_name = screen["name"].toString(); offscreenScreen->m_name = config["name"].toString();
offscreenScreen->m_geometry = QRect(screen["x"].toInt(0), screen["y"].toInt(0), offscreenScreen->m_geometry = geometryFromConfig(config);
screen["width"].toInt(640), screen["height"].toInt(480)); offscreenScreen->m_logicalDpi = config["logicalDpi"].toInt(96);
offscreenScreen->m_logicalDpi = screen["logicalDpi"].toInt(96); offscreenScreen->m_logicalBaseDpi = config["logicalBaseDpi"].toInt(96);
offscreenScreen->m_logicalBaseDpi = screen["logicalBaseDpi"].toInt(96); offscreenScreen->m_dpr = config["dpr"].toDouble(1.0);
offscreenScreen->m_dpr = screen["dpr"].toDouble(1.0);
m_screens.append(offscreenScreen); m_screens.append(offscreenScreen);
QWindowSystemInterface::handleScreenAdded(offscreenScreen); QWindowSystemInterface::handleScreenAdded(offscreenScreen);
} }
// Update present screens
for (const QString &pres : present) {
QOffscreenScreen *screen = platformScreenByName(pres, m_screens);
Q_ASSERT(screen);
QJsonObject currentConfig = screenConfigByName(pres, currentScreens).toObject();
QJsonObject newConfig = screenConfigByName(pres, newScreens).toObject();
// Name can't change, because it'd be a different screen
Q_ASSERT(currentConfig["name"] == newConfig["name"]);
// Geometry
QRect currentGeomtry = geometryFromConfig(currentConfig);
QRect newGeomtry = geometryFromConfig(newConfig);
if (currentGeomtry != newGeomtry) {
screen->m_geometry = newGeomtry;
QWindowSystemInterface::handleScreenGeometryChange(screen->screen(), newGeomtry, newGeomtry);
}
// logical DPI
int currentLogicalDpi = currentConfig["logicalDpi"].toInt(96);
int newLogicalDpi = newConfig["logicalDpi"].toInt(96);
if (currentLogicalDpi != newLogicalDpi) {
screen->m_logicalDpi = newLogicalDpi;
QWindowSystemInterface::handleScreenLogicalDotsPerInchChange(screen->screen(), newLogicalDpi, newLogicalDpi);
}
// The base DPI is more of a platform constant, and should not change, and
// there is no handleChange function for it. Print a warning.
int currentLogicalBaseDpi = currentConfig["logicalBaseDpi"].toInt(96);
int newLogicalBaseDpi = newConfig["logicalBaseDpi"].toInt(96);
if (currentLogicalBaseDpi != newLogicalBaseDpi) {
screen->m_logicalBaseDpi = newLogicalBaseDpi;
qWarning("You ain't supposed to change logicalBaseDpi - its a platform constant. Qt may not react to the change");
}
// DPR. There is also no handleChange function in Qt at this point, instead
// the new DPR value will be used during the next repaint. We could repaint
// all windows here, but don't. Print a warning.
double currentDpr = currentConfig["dpr"].toDouble(1);
double newDpr = newConfig["dpr"].toDouble(1);
if (currentDpr != newDpr) {
screen->m_dpr = newDpr;
qWarning("DPR change notifications is not implemented - Qt may not react to the change");
}
}
// Now the new configuration is the current configuration
m_configuration = configuration;
}
QJsonObject QOffscreenIntegration::configuration() const
{
return m_configuration;
} }
void QOffscreenIntegration::initialize() void QOffscreenIntegration::initialize()
@ -323,17 +461,15 @@ QOffscreenIntegration *QOffscreenIntegration::createOffscreenIntegration(const Q
#if QT_CONFIG(xlib) && QT_CONFIG(opengl) && !QT_CONFIG(opengles2) #if QT_CONFIG(xlib) && QT_CONFIG(opengl) && !QT_CONFIG(opengles2)
QByteArray glx = qgetenv("QT_QPA_OFFSCREEN_NO_GLX"); QByteArray glx = qgetenv("QT_QPA_OFFSCREEN_NO_GLX");
if (glx.isEmpty()) if (glx.isEmpty())
offscreenIntegration = new QOffscreenX11Integration; offscreenIntegration = new QOffscreenX11Integration(paramList);
#endif #endif
if (!offscreenIntegration) if (!offscreenIntegration)
offscreenIntegration = new QOffscreenIntegration; offscreenIntegration = new QOffscreenIntegration(paramList);
offscreenIntegration->configure(paramList);
return offscreenIntegration; return offscreenIntegration;
} }
QList<QPlatformScreen *> QOffscreenIntegration::screens() const QList<QOffscreenScreen *> QOffscreenIntegration::screens() const
{ {
return m_screens; return m_screens;
} }

View File

@ -44,18 +44,24 @@
#include <qpa/qplatformnativeinterface.h> #include <qpa/qplatformnativeinterface.h>
#include <qscopedpointer.h> #include <qscopedpointer.h>
#include <qjsonobject.h>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
class QOffscreenBackendData; class QOffscreenBackendData;
class QOffscreenScreen;
class QOffscreenIntegration : public QPlatformIntegration class QOffscreenIntegration : public QPlatformIntegration
{ {
public: public:
QOffscreenIntegration(); QOffscreenIntegration(const QStringList& paramList);
~QOffscreenIntegration(); ~QOffscreenIntegration();
void configure(const QStringList& paramList); QJsonObject defaultConfiguration() const;
std::optional<QJsonObject> resolveConfigFileConfiguration(const QStringList& paramList) const;
void setConfiguration(const QJsonObject &configuration);
QJsonObject configuration() const;
void initialize() override; void initialize() override;
bool hasCapability(QPlatformIntegration::Capability cap) const override; bool hasCapability(QPlatformIntegration::Capability cap) const override;
@ -78,7 +84,7 @@ public:
static QOffscreenIntegration *createOffscreenIntegration(const QStringList& paramList); static QOffscreenIntegration *createOffscreenIntegration(const QStringList& paramList);
QList<QPlatformScreen *> screens() const; QList<QOffscreenScreen *> screens() const;
protected: protected:
QScopedPointer<QPlatformFontDatabase> m_fontDatabase; QScopedPointer<QPlatformFontDatabase> m_fontDatabase;
#if QT_CONFIG(draganddrop) #if QT_CONFIG(draganddrop)
@ -87,8 +93,9 @@ protected:
QScopedPointer<QPlatformInputContext> m_inputContext; QScopedPointer<QPlatformInputContext> m_inputContext;
QScopedPointer<QPlatformServices> m_services; QScopedPointer<QPlatformServices> m_services;
mutable QScopedPointer<QPlatformNativeInterface> m_nativeInterface; mutable QScopedPointer<QPlatformNativeInterface> m_nativeInterface;
QList<QPlatformScreen *> m_screens; QList<QOffscreenScreen *> m_screens;
bool m_windowFrameMarginsEnabled = true; bool m_windowFrameMarginsEnabled = true;
QJsonObject m_configuration;
}; };
QT_END_NAMESPACE QT_END_NAMESPACE

View File

@ -76,6 +76,12 @@ private:
QOffscreenX11Connection *m_connection; QOffscreenX11Connection *m_connection;
}; };
QOffscreenX11Integration::QOffscreenX11Integration(const QStringList& paramList)
: QOffscreenIntegration(paramList)
{
}
QOffscreenX11Integration::~QOffscreenX11Integration() = default; QOffscreenX11Integration::~QOffscreenX11Integration() = default;
bool QOffscreenX11Integration::hasCapability(QPlatformIntegration::Capability cap) const bool QOffscreenX11Integration::hasCapability(QPlatformIntegration::Capability cap) const
@ -106,10 +112,16 @@ QPlatformOpenGLContext *QOffscreenX11Integration::createPlatformOpenGLContext(QO
QOffscreenX11PlatformNativeInterface *QOffscreenX11Integration::nativeInterface() const QOffscreenX11PlatformNativeInterface *QOffscreenX11Integration::nativeInterface() const
{ {
if (!m_nativeInterface) if (!m_nativeInterface)
m_nativeInterface.reset(new QOffscreenX11PlatformNativeInterface); m_nativeInterface.reset(new QOffscreenX11PlatformNativeInterface(const_cast<QOffscreenX11Integration *>(this)));
return static_cast<QOffscreenX11PlatformNativeInterface *>(m_nativeInterface.data()); return static_cast<QOffscreenX11PlatformNativeInterface *>(m_nativeInterface.data());
} }
QOffscreenX11PlatformNativeInterface::QOffscreenX11PlatformNativeInterface(QOffscreenIntegration *integration)
:QOffscreenPlatformNativeInterface(integration)
{
}
QOffscreenX11PlatformNativeInterface::~QOffscreenX11PlatformNativeInterface() = default; QOffscreenX11PlatformNativeInterface::~QOffscreenX11PlatformNativeInterface() = default;
void *QOffscreenX11PlatformNativeInterface::nativeResourceForScreen(const QByteArray &resource, QScreen *screen) void *QOffscreenX11PlatformNativeInterface::nativeResourceForScreen(const QByteArray &resource, QScreen *screen)

View File

@ -56,6 +56,7 @@ class QOffscreenX11Info;
class QOffscreenX11PlatformNativeInterface : public QOffscreenPlatformNativeInterface class QOffscreenX11PlatformNativeInterface : public QOffscreenPlatformNativeInterface
{ {
public: public:
QOffscreenX11PlatformNativeInterface(QOffscreenIntegration *integration);
~QOffscreenX11PlatformNativeInterface(); ~QOffscreenX11PlatformNativeInterface();
void *nativeResourceForScreen(const QByteArray &resource, QScreen *screen) override; void *nativeResourceForScreen(const QByteArray &resource, QScreen *screen) override;
@ -69,6 +70,7 @@ public:
class QOffscreenX11Integration : public QOffscreenIntegration class QOffscreenX11Integration : public QOffscreenIntegration
{ {
public: public:
QOffscreenX11Integration(const QStringList& paramList);
~QOffscreenX11Integration(); ~QOffscreenX11Integration();
bool hasCapability(QPlatformIntegration::Capability cap) const override; bool hasCapability(QPlatformIntegration::Capability cap) const override;