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

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

source code of qtbase/src/plugins/platforms/offscreen/qoffscreenintegration.cpp