Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qoffscreenintegration.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
8
9#if defined(Q_OS_UNIX)
10#include <QtGui/private/qgenericunixeventdispatcher_p.h>
11#if defined(Q_OS_APPLE)
12#include <qpa/qplatformfontdatabase.h>
13#include <QtGui/private/qcoretextfontdatabase_p.h>
14#else
15#include <QtGui/private/qgenericunixfontdatabase_p.h>
16#endif
17#elif defined(Q_OS_WIN)
18#include <QtGui/private/qfreetypefontdatabase_p.h>
19#include <QtCore/private/qeventdispatcher_win_p.h>
20#endif
21
22#include <QtCore/qfile.h>
23#include <QtCore/qjsonarray.h>
24#include <QtCore/qjsondocument.h>
25#include <QtCore/qjsonobject.h>
26#include <QtCore/qjsonvalue.h>
27#include <QtGui/private/qpixmap_raster_p.h>
28#include <QtGui/private/qguiapplication_p.h>
29#include <qpa/qplatforminputcontextfactory_p.h>
30#include <qpa/qplatforminputcontext.h>
31#include <qpa/qplatformtheme.h>
32#include <qpa/qwindowsysteminterface.h>
33
34#include <qpa/qplatformservices.h>
35
36#if QT_CONFIG(xlib) && QT_CONFIG(opengl) && !QT_CONFIG(opengles2)
37#include "qoffscreenintegration_x11.h"
38#endif
39
41
42using namespace Qt::StringLiterals;
43
44class QCoreTextFontEngine;
45
46template <typename BaseEventDispatcher>
47class QOffscreenEventDispatcher : public BaseEventDispatcher
48{
49public:
50 explicit QOffscreenEventDispatcher(QObject *parent = nullptr)
51 : BaseEventDispatcher(parent)
52 {
53 }
54
55 bool processEvents(QEventLoop::ProcessEventsFlags flags) override
56 {
57 bool didSendEvents = BaseEventDispatcher::processEvents(flags);
58
59 return QWindowSystemInterface::sendWindowSystemEvents(flags) || didSendEvents;
60 }
61};
62
63QOffscreenIntegration::QOffscreenIntegration(const QStringList& paramList)
64{
65#if defined(Q_OS_UNIX)
66#if defined(Q_OS_APPLE)
67 m_fontDatabase.reset(new QCoreTextFontDatabaseEngineFactory<QCoreTextFontEngine>);
68#else
69 m_fontDatabase.reset(new QGenericUnixFontDatabase());
70#endif
71#elif defined(Q_OS_WIN)
72 m_fontDatabase.reset(new QFreeTypeFontDatabase());
73#endif
74
75#if QT_CONFIG(draganddrop)
76 m_drag.reset(new QOffscreenDrag);
77#endif
78
79 QJsonObject config = resolveConfigFileConfiguration(paramList).value_or(defaultConfiguration());
80 setConfiguration(config);
81}
82
84{
85 while (!m_screens.isEmpty())
86 QWindowSystemInterface::handleScreenRemoved(m_screens.takeLast());
87}
88
89/*
90 The offscren platform plugin is configurable with a JSON configuration.
91 The confiuration can be provided either from a file on disk on startup,
92 or at by calling setConfiguration().
93
94 To provide a configuration on startuip, write the config to disk and pass
95 the file path as a platform argument:
96
97 ./myapp -platform offscreen:configfile=/path/to/config.json
98
99 The supported top-level config keys are:
100 {
101 "synchronousWindowSystemEvents": <bool>
102 "windowFrameMargins": <bool>,
103 "screens": [<screens>],
104 }
105
106 "screens" is an array of:
107 {
108 "name": string,
109 "x": int,
110 "y": int,
111 "width": int,
112 "height": int,
113 "logicalDpi": int,
114 "logicalBaseDpi": int,
115 "dpr": double,
116 }
117*/
118
120{
121 const auto defaultScreen = QJsonObject {
122 {"name", ""},
123 {"x", 0},
124 {"y", 0},
125 {"width", 800},
126 {"height", 800},
127 {"logicalDpi", 96},
128 {"logicalBaseDpi", 96},
129 {"dpr", 1.0},
130 };
131 const auto defaultConfiguration = QJsonObject {
132 {"synchronousWindowSystemEvents", false},
133 {"windowFrameMargins", true},
134 {"screens", QJsonArray { defaultScreen } },
135 };
136 return defaultConfiguration;
137}
138
140{
141 bool hasConfigFile = false;
142 QString configFilePath;
143 for (const QString &param : paramList) {
144 // Look for "configfile=/path/to/file/"
145 QString configPrefix("configfile="_L1);
146 if (param.startsWith(configPrefix)) {
147 hasConfigFile = true;
148 configFilePath = param.mid(configPrefix.size());
149 }
150 }
151 if (!hasConfigFile)
152 return std::nullopt;
153
154 // Read config file
155 if (configFilePath.isEmpty())
156 qFatal("Missing file path for -configfile platform option");
157 QFile configFile(configFilePath);
158 if (!configFile.exists())
159 qFatal("Could not find platform config file %s", qPrintable(configFilePath));
160 if (!configFile.open(QIODevice::ReadOnly))
161 qFatal("Could not open platform config file for reading %s, %s", qPrintable(configFilePath), qPrintable(configFile.errorString()));
162
163 QByteArray json = configFile.readAll();
164 QJsonParseError error;
165 QJsonDocument config = QJsonDocument::fromJson(json, &error);
166 if (config.isNull())
167 qFatal("Platform config file parse error: %s", qPrintable(error.errorString()));
168
169 return config.object();
170}
171
172
173void QOffscreenIntegration::setConfiguration(const QJsonObject &configuration)
174{
175 // Apply the new configuration, diffing against the current m_configuration
176
177 const bool synchronousWindowSystemEvents = configuration["synchronousWindowSystemEvents"].toBool(
178 m_configuration["synchronousWindowSystemEvents"].toBool(false));
179 QWindowSystemInterface::setSynchronousWindowSystemEvents(synchronousWindowSystemEvents);
180
181 m_windowFrameMarginsEnabled = configuration["windowFrameMargins"].toBool(
182 m_configuration["windowFrameMargins"].toBool(true));
183
184 // Diff screens array, using the screen name as the screen identity.
185 QJsonArray currentScreens = m_configuration["screens"].toArray();
186 QJsonArray newScreens = configuration["screens"].toArray();
187
188 auto getScreenNames = [](const QJsonArray &screens) -> QList<QString> {
189 QList<QString> names;
190 for (QJsonValue screen : screens) {
191 names.append(screen["name"].toString());
192 };
193 std::sort(names.begin(), names.end());
194 return names;
195 };
196
197 auto currentNames = getScreenNames(currentScreens);
198 auto newNames = getScreenNames(newScreens);
199
200 QList<QString> present;
201 std::set_intersection(currentNames.begin(), currentNames.end(), newNames.begin(), newNames.end(),
202 std::inserter(present, present.begin()));
203 QList<QString> added;
204 std::set_difference(newNames.begin(), newNames.end(), currentNames.begin(), currentNames.end(),
205 std::inserter(added, added.begin()));
206 QList<QString> removed;
207 std::set_difference(currentNames.begin(), currentNames.end(), newNames.begin(), newNames.end(),
208 std::inserter(removed, removed.begin()));
209
210 auto platformScreenByName = [](const QString &name, QList<QOffscreenScreen *> screens) -> QOffscreenScreen * {
211 for (QOffscreenScreen *screen : screens) {
212 if (screen->m_name == name)
213 return screen;
214 }
215 Q_UNREACHABLE();
216 };
217
218 auto screenConfigByName = [](const QString &name, QJsonArray screenConfigs) -> QJsonValue {
219 for (QJsonValue screenConfig : screenConfigs) {
220 if (screenConfig["name"].toString() == name)
221 return screenConfig;
222 }
223 Q_UNREACHABLE();
224 };
225
226 auto geometryFromConfig = [](const QJsonObject &config) -> QRect {
227 return QRect(config["x"].toInt(0), config["y"].toInt(0), config["width"].toInt(640), config["height"].toInt(480));
228 };
229
230 // Remove removed screens
231 for (const QString &remove : removed) {
232 QOffscreenScreen *screen = platformScreenByName(remove, m_screens);
233 m_screens.removeAll(screen);
234 QWindowSystemInterface::handleScreenRemoved(screen);
235 }
236
237 // Add new screens
238 for (const QString &add : added) {
239 QJsonValue configValue = screenConfigByName(add, newScreens);
240 QJsonObject config = configValue.toObject();
241 if (config.isEmpty()) {
242 qWarning("empty screen object");
243 continue;
244 }
245 QOffscreenScreen *offscreenScreen = new QOffscreenScreen(this);
246 offscreenScreen->m_name = config["name"].toString();
247 offscreenScreen->m_geometry = geometryFromConfig(config);
248 offscreenScreen->m_logicalDpi = config["logicalDpi"].toInt(96);
249 offscreenScreen->m_logicalBaseDpi = config["logicalBaseDpi"].toInt(96);
250 offscreenScreen->m_dpr = config["dpr"].toDouble(1.0);
251 m_screens.append(offscreenScreen);
252 QWindowSystemInterface::handleScreenAdded(offscreenScreen);
253 }
254
255 // Update present screens
256 for (const QString &pres : present) {
257 QOffscreenScreen *screen = platformScreenByName(pres, m_screens);
258 Q_ASSERT(screen);
259 QJsonObject currentConfig = screenConfigByName(pres, currentScreens).toObject();
260 QJsonObject newConfig = screenConfigByName(pres, newScreens).toObject();
261
262 // Name can't change, because it'd be a different screen
263 Q_ASSERT(currentConfig["name"] == newConfig["name"]);
264
265 // Geometry
266 QRect currentGeomtry = geometryFromConfig(currentConfig);
267 QRect newGeomtry = geometryFromConfig(newConfig);
268 if (currentGeomtry != newGeomtry) {
269 screen->m_geometry = newGeomtry;
270 QWindowSystemInterface::handleScreenGeometryChange(screen->screen(), newGeomtry, newGeomtry);
271 }
272
273 // logical DPI
274 int currentLogicalDpi = currentConfig["logicalDpi"].toInt(96);
275 int newLogicalDpi = newConfig["logicalDpi"].toInt(96);
276 if (currentLogicalDpi != newLogicalDpi) {
277 screen->m_logicalDpi = newLogicalDpi;
278 QWindowSystemInterface::handleScreenLogicalDotsPerInchChange(screen->screen(), newLogicalDpi, newLogicalDpi);
279 }
280
281 // The base DPI is more of a platform constant, and should not change, and
282 // there is no handleChange function for it. Print a warning.
283 int currentLogicalBaseDpi = currentConfig["logicalBaseDpi"].toInt(96);
284 int newLogicalBaseDpi = newConfig["logicalBaseDpi"].toInt(96);
285 if (currentLogicalBaseDpi != newLogicalBaseDpi) {
286 screen->m_logicalBaseDpi = newLogicalBaseDpi;
287 qWarning("You ain't supposed to change logicalBaseDpi - its a platform constant. Qt may not react to the change");
288 }
289
290 // DPR. There is also no handleChange function in Qt at this point, instead
291 // the new DPR value will be used during the next repaint. We could repaint
292 // all windows here, but don't. Print a warning.
293 double currentDpr = currentConfig["dpr"].toDouble(1);
294 double newDpr = newConfig["dpr"].toDouble(1);
295 if (currentDpr != newDpr) {
296 screen->m_dpr = newDpr;
297 qWarning("DPR change notifications is not implemented - Qt may not react to the change");
298 }
299 }
300
301 // Now the new configuration is the current configuration
302 m_configuration = configuration;
303}
304
306{
307 return m_configuration;
308}
309
311{
312 m_inputContext.reset(QPlatformInputContextFactory::create());
313}
314
316{
317 return m_inputContext.data();
318}
319
320bool QOffscreenIntegration::hasCapability(QPlatformIntegration::Capability cap) const
321{
322 switch (cap) {
323 case ThreadedPixmaps: return true;
324 case MultipleWindows: return true;
325 case RhiBasedRendering: return false;
326 default: return QPlatformIntegration::hasCapability(cap);
327 }
328}
329
331{
332 Q_UNUSED(window);
333 QPlatformWindow *w = new QOffscreenWindow(window, m_windowFrameMarginsEnabled);
334 w->requestActivateWindow();
335 return w;
336}
337
339{
340 return new QOffscreenBackingStore(window);
341}
342
344{
345#if defined(Q_OS_UNIX)
346 return createUnixEventDispatcher();
347#elif defined(Q_OS_WIN)
348 return new QOffscreenEventDispatcher<QEventDispatcherWin32>();
349#else
350 return 0;
351#endif
352}
353
355{
356 if (!m_nativeInterface)
357 m_nativeInterface.reset(new QOffscreenPlatformNativeInterface(const_cast<QOffscreenIntegration*>(this)));
358 return m_nativeInterface.get();
359}
360
361static QString themeName() { return QStringLiteral("offscreen"); }
362
364{
365 return QStringList(themeName());
366}
367
368// Restrict the styles to "fusion" to prevent native styles requiring native
369// window handles (eg Windows Vista style) from being used.
371{
372public:
374
375 QVariant themeHint(ThemeHint h) const override
376 {
377 switch (h) {
378 case StyleNames:
379 return QVariant(QStringList(QStringLiteral("Fusion")));
380 default:
381 break;
382 }
383 return QPlatformTheme::themeHint(h);
384 }
385
386 virtual const QFont *font(Font type = SystemFont) const override
387 {
388 static QFont systemFont("Sans Serif"_L1, 9);
389 static QFont fixedFont("monospace"_L1, 9);
390 switch (type) {
391 case QPlatformTheme::SystemFont:
392 return &systemFont;
393 case QPlatformTheme::FixedFont:
394 return &fixedFont;
395 default:
396 return nullptr;
397 }
398 }
399};
400
402{
403 return name == themeName() ? new OffscreenTheme() : nullptr;
404}
405
407{
408 return m_fontDatabase.data();
409}
410
411#if QT_CONFIG(draganddrop)
412QPlatformDrag *QOffscreenIntegration::drag() const
413{
414 return m_drag.data();
415}
416#endif
417
419{
420 if (m_services.isNull())
421 m_services.reset(new QPlatformServices);
422
423 return m_services.data();
424}
425
426QOffscreenIntegration *QOffscreenIntegration::createOffscreenIntegration(const QStringList& paramList)
427{
428 QOffscreenIntegration *offscreenIntegration = nullptr;
429
430#if QT_CONFIG(xlib) && QT_CONFIG(opengl) && !QT_CONFIG(opengles2)
431 QByteArray glx = qgetenv("QT_QPA_OFFSCREEN_NO_GLX");
432 if (glx.isEmpty())
433 offscreenIntegration = new QOffscreenX11Integration(paramList);
434#endif
435
436 if (!offscreenIntegration)
437 offscreenIntegration = new QOffscreenIntegration(paramList);
438 return offscreenIntegration;
439}
440
442{
443 return m_screens;
444}
445
446QT_END_NAMESPACE
QVariant themeHint(ThemeHint h) const override
virtual const QFont * font(Font type=SystemFont) const override
\inmodule QtCore\reentrant
Definition qjsonobject.h:34
QOffscreenEventDispatcher(QObject *parent=nullptr)
bool processEvents(QEventLoop::ProcessEventsFlags flags) override
QPlatformFontDatabase * fontDatabase() const override
Accessor for the platform integration's fontdatabase.
std::optional< QJsonObject > resolveConfigFileConfiguration(const QStringList &paramList) const
void initialize() override
Performs initialization steps that depend on having an event dispatcher available.
QList< QOffscreenScreen * > screens() const
QPlatformWindow * createPlatformWindow(QWindow *window) const override
Factory function for QPlatformWindow.
QJsonObject configuration() const
QPlatformBackingStore * createPlatformBackingStore(QWindow *window) const override
Factory function for QPlatformBackingStore.
QPlatformTheme * createPlatformTheme(const QString &name) const override
QPlatformInputContext * inputContext() const override
Returns the platforms input context.
QOffscreenIntegration(const QStringList &paramList)
QPlatformServices * services() const override
QPlatformNativeInterface * nativeInterface() const override
bool hasCapability(QPlatformIntegration::Capability cap) const override
QJsonObject defaultConfiguration() const
QStringList themeNames() const override
void setConfiguration(const QJsonObject &configuration)
QAbstractEventDispatcher * createEventDispatcher() const override
Factory function for the GUI event dispatcher.
Combined button and popup list for selecting options.
static QString themeName()