From 95d5710d7b608cd4e22b20d570d54dbadc8105cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Morten=20Johan=20S=C3=B8rvig?= Date: Wed, 17 Mar 2021 13:14:44 +0100 Subject: [PATCH] Offscreen: add configuration update API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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ø --- .../platforms/offscreen/qoffscreencommon.cpp | 6 +- .../offscreen/qoffscreenintegration.cpp | 208 +++++++++++++++--- .../offscreen/qoffscreenintegration.h | 15 +- .../offscreen/qoffscreenintegration_x11.cpp | 14 +- .../offscreen/qoffscreenintegration_x11.h | 2 + 5 files changed, 203 insertions(+), 42 deletions(-) diff --git a/src/plugins/platforms/offscreen/qoffscreencommon.cpp b/src/plugins/platforms/offscreen/qoffscreencommon.cpp index 33b98cfa1b..29e2cc417d 100644 --- a/src/plugins/platforms/offscreen/qoffscreencommon.cpp +++ b/src/plugins/platforms/offscreen/qoffscreencommon.cpp @@ -55,7 +55,11 @@ QPlatformWindow *QOffscreenScreen::windowContainingCursor = nullptr; QList QOffscreenScreen::virtualSiblings() const { - return m_integration->screens(); + QList platformScreens; + for (auto screen : m_integration->screens()) { + platformScreens.append(screen); + } + return platformScreens; } class QOffscreenCursor : public QPlatformCursor diff --git a/src/plugins/platforms/offscreen/qoffscreenintegration.cpp b/src/plugins/platforms/offscreen/qoffscreenintegration.cpp index 310e71ae13..a8504316d8 100644 --- a/src/plugins/platforms/offscreen/qoffscreenintegration.cpp +++ b/src/plugins/platforms/offscreen/qoffscreenintegration.cpp @@ -93,7 +93,7 @@ public: } }; -QOffscreenIntegration::QOffscreenIntegration() +QOffscreenIntegration::QOffscreenIntegration(const QStringList& paramList) { #if defined(Q_OS_UNIX) #if defined(Q_OS_MAC) @@ -109,6 +109,9 @@ QOffscreenIntegration::QOffscreenIntegration() m_drag.reset(new QOffscreenDrag); #endif m_services.reset(new QPlatformServices); + + QJsonObject config = resolveConfigFileConfiguration(paramList).value_or(defaultConfiguration()); + setConfiguration(config); } QOffscreenIntegration::~QOffscreenIntegration() @@ -118,8 +121,12 @@ QOffscreenIntegration::~QOffscreenIntegration() } /* - 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 offscren platform plugin is configurable with a JSON configuration. + 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 @@ -130,9 +137,9 @@ QOffscreenIntegration::~QOffscreenIntegration() "screens": [], } - Screen: + "screens" is an array of: { - "name" : string, + "name": string, "x": int, "y": int, "width": int, @@ -142,9 +149,29 @@ QOffscreenIntegration::~QOffscreenIntegration() "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 QOffscreenIntegration::resolveConfigFileConfiguration(const QStringList& paramList) const { - // Use config file configuring platform plugin, if one was specified bool hasConfigFile = false; QString configFilePath; for (const QString ¶m : paramList) { @@ -152,17 +179,11 @@ void QOffscreenIntegration::configure(const QStringList& paramList) QString configPrefix(QLatin1String("configfile=")); if (param.startsWith(configPrefix)) { hasConfigFile = true; - configFilePath= param.mid(configPrefix.length()); + configFilePath = param.mid(configPrefix.length()); } } - - // Create the default screen if there was no config file - if (!hasConfigFile) { - QOffscreenScreen *offscreenScreen = new QOffscreenScreen(this); - m_screens.append(offscreenScreen); - QWindowSystemInterface::handleScreenAdded(offscreenScreen); - return; - } + if (!hasConfigFile) + return std::nullopt; // Read config file if (configFilePath.isEmpty()) @@ -179,28 +200,145 @@ void QOffscreenIntegration::configure(const QStringList& paramList) if (config.isNull()) qFatal("Platform config file parse error: %s", qPrintable(error.errorString())); - // Apply configuration (create screens) - bool synchronousWindowSystemEvents = config["synchronousWindowSystemEvents"].toBool(false); + return config.object(); +} + + +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); - m_windowFrameMarginsEnabled = config["windowFrameMargins"].toBool(true); - QJsonArray screens = config["screens"].toArray(); - for (QJsonValue screenValue : screens) { - QJsonObject screen = screenValue.toObject(); - if (screen.isEmpty()) { - qWarning("QOffscreenIntegration::initializeWithPlatformArguments: empty screen object"); + + m_windowFrameMarginsEnabled = configuration["windowFrameMargins"].toBool( + m_configuration["windowFrameMargins"].toBool(true)); + + // Diff screens array, using the screen name as the screen identity. + QJsonArray currentScreens = m_configuration["screens"].toArray(); + QJsonArray newScreens = configuration["screens"].toArray(); + + auto getScreenNames = [](const QJsonArray &screens) -> QList { + QList 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 present; + std::set_intersection(currentNames.begin(), currentNames.end(), newNames.begin(), newNames.end(), + std::inserter(present, present.begin())); + QList added; + std::set_difference(newNames.begin(), newNames.end(), currentNames.begin(), currentNames.end(), + std::inserter(added, added.begin())); + QList removed; + std::set_difference(currentNames.begin(), currentNames.end(), newNames.begin(), newNames.end(), + std::inserter(removed, removed.begin())); + + auto platformScreenByName = [](const QString &name, QList 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; } QOffscreenScreen *offscreenScreen = new QOffscreenScreen(this); - offscreenScreen->m_name = screen["name"].toString(); - offscreenScreen->m_geometry = QRect(screen["x"].toInt(0), screen["y"].toInt(0), - screen["width"].toInt(640), screen["height"].toInt(480)); - offscreenScreen->m_logicalDpi = screen["logicalDpi"].toInt(96); - offscreenScreen->m_logicalBaseDpi = screen["logicalBaseDpi"].toInt(96); - offscreenScreen->m_dpr = screen["dpr"].toDouble(1.0); - + offscreenScreen->m_name = config["name"].toString(); + offscreenScreen->m_geometry = geometryFromConfig(config); + offscreenScreen->m_logicalDpi = config["logicalDpi"].toInt(96); + offscreenScreen->m_logicalBaseDpi = config["logicalBaseDpi"].toInt(96); + offscreenScreen->m_dpr = config["dpr"].toDouble(1.0); m_screens.append(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() @@ -323,17 +461,15 @@ QOffscreenIntegration *QOffscreenIntegration::createOffscreenIntegration(const Q #if QT_CONFIG(xlib) && QT_CONFIG(opengl) && !QT_CONFIG(opengles2) QByteArray glx = qgetenv("QT_QPA_OFFSCREEN_NO_GLX"); if (glx.isEmpty()) - offscreenIntegration = new QOffscreenX11Integration; + offscreenIntegration = new QOffscreenX11Integration(paramList); #endif if (!offscreenIntegration) - offscreenIntegration = new QOffscreenIntegration; - - offscreenIntegration->configure(paramList); + offscreenIntegration = new QOffscreenIntegration(paramList); return offscreenIntegration; } -QList QOffscreenIntegration::screens() const +QList QOffscreenIntegration::screens() const { return m_screens; } diff --git a/src/plugins/platforms/offscreen/qoffscreenintegration.h b/src/plugins/platforms/offscreen/qoffscreenintegration.h index 38d145eee3..a490cf5bed 100644 --- a/src/plugins/platforms/offscreen/qoffscreenintegration.h +++ b/src/plugins/platforms/offscreen/qoffscreenintegration.h @@ -44,18 +44,24 @@ #include #include +#include QT_BEGIN_NAMESPACE class QOffscreenBackendData; +class QOffscreenScreen; class QOffscreenIntegration : public QPlatformIntegration { public: - QOffscreenIntegration(); + QOffscreenIntegration(const QStringList& paramList); ~QOffscreenIntegration(); - void configure(const QStringList& paramList); + QJsonObject defaultConfiguration() const; + std::optional resolveConfigFileConfiguration(const QStringList& paramList) const; + void setConfiguration(const QJsonObject &configuration); + QJsonObject configuration() const; + void initialize() override; bool hasCapability(QPlatformIntegration::Capability cap) const override; @@ -78,7 +84,7 @@ public: static QOffscreenIntegration *createOffscreenIntegration(const QStringList& paramList); - QList screens() const; + QList screens() const; protected: QScopedPointer m_fontDatabase; #if QT_CONFIG(draganddrop) @@ -87,8 +93,9 @@ protected: QScopedPointer m_inputContext; QScopedPointer m_services; mutable QScopedPointer m_nativeInterface; - QList m_screens; + QList m_screens; bool m_windowFrameMarginsEnabled = true; + QJsonObject m_configuration; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/offscreen/qoffscreenintegration_x11.cpp b/src/plugins/platforms/offscreen/qoffscreenintegration_x11.cpp index 1e533f87dc..3c2fdb1f0f 100644 --- a/src/plugins/platforms/offscreen/qoffscreenintegration_x11.cpp +++ b/src/plugins/platforms/offscreen/qoffscreenintegration_x11.cpp @@ -76,6 +76,12 @@ private: QOffscreenX11Connection *m_connection; }; +QOffscreenX11Integration::QOffscreenX11Integration(const QStringList& paramList) +: QOffscreenIntegration(paramList) +{ + +} + QOffscreenX11Integration::~QOffscreenX11Integration() = default; bool QOffscreenX11Integration::hasCapability(QPlatformIntegration::Capability cap) const @@ -106,10 +112,16 @@ QPlatformOpenGLContext *QOffscreenX11Integration::createPlatformOpenGLContext(QO QOffscreenX11PlatformNativeInterface *QOffscreenX11Integration::nativeInterface() const { if (!m_nativeInterface) - m_nativeInterface.reset(new QOffscreenX11PlatformNativeInterface); + m_nativeInterface.reset(new QOffscreenX11PlatformNativeInterface(const_cast(this))); return static_cast(m_nativeInterface.data()); } +QOffscreenX11PlatformNativeInterface::QOffscreenX11PlatformNativeInterface(QOffscreenIntegration *integration) +:QOffscreenPlatformNativeInterface(integration) +{ + +} + QOffscreenX11PlatformNativeInterface::~QOffscreenX11PlatformNativeInterface() = default; void *QOffscreenX11PlatformNativeInterface::nativeResourceForScreen(const QByteArray &resource, QScreen *screen) diff --git a/src/plugins/platforms/offscreen/qoffscreenintegration_x11.h b/src/plugins/platforms/offscreen/qoffscreenintegration_x11.h index 7e26b76759..afd30d7b4b 100644 --- a/src/plugins/platforms/offscreen/qoffscreenintegration_x11.h +++ b/src/plugins/platforms/offscreen/qoffscreenintegration_x11.h @@ -56,6 +56,7 @@ class QOffscreenX11Info; class QOffscreenX11PlatformNativeInterface : public QOffscreenPlatformNativeInterface { public: + QOffscreenX11PlatformNativeInterface(QOffscreenIntegration *integration); ~QOffscreenX11PlatformNativeInterface(); void *nativeResourceForScreen(const QByteArray &resource, QScreen *screen) override; @@ -69,6 +70,7 @@ public: class QOffscreenX11Integration : public QOffscreenIntegration { public: + QOffscreenX11Integration(const QStringList& paramList); ~QOffscreenX11Integration(); bool hasCapability(QPlatformIntegration::Capability cap) const override;