1// Copyright (C) 2017 The Qt Company Ltd.
2// Copyright (C) 2016 Pelagicore AG
3// Copyright (C) 2015 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
4// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
5
6#include "qkmsdevice_p.h"
7
8#include <QtCore/QJsonDocument>
9#include <QtCore/QJsonObject>
10#include <QtCore/QJsonArray>
11#include <QtCore/QFile>
12#include <QtCore/QLoggingCategory>
13
14#include <errno.h>
15
16#define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0])
17
18QT_BEGIN_NAMESPACE
19
20using namespace Qt::StringLiterals;
21
22Q_LOGGING_CATEGORY(qLcKmsDebug, "qt.qpa.eglfs.kms")
23
24enum OutputConfiguration {
25 OutputConfigOff,
26 OutputConfigPreferred,
27 OutputConfigCurrent,
28 OutputConfigSkip,
29 OutputConfigMode,
30 OutputConfigModeline
31};
32
33int QKmsDevice::crtcForConnector(drmModeResPtr resources, drmModeConnectorPtr connector)
34{
35 int candidate = -1;
36
37 for (int i = 0; i < connector->count_encoders; i++) {
38 drmModeEncoderPtr encoder = drmModeGetEncoder(fd: m_dri_fd, encoder_id: connector->encoders[i]);
39 if (!encoder) {
40 qWarning(msg: "Failed to get encoder");
41 continue;
42 }
43
44 quint32 encoderId = encoder->encoder_id;
45 quint32 crtcId = encoder->crtc_id;
46 quint32 possibleCrtcs = encoder->possible_crtcs;
47 drmModeFreeEncoder(ptr: encoder);
48
49 for (int j = 0; j < resources->count_crtcs; j++) {
50 bool isPossible = possibleCrtcs & (1 << j);
51 bool isAvailable = !(m_crtc_allocator & (1 << j));
52 // Preserve the existing CRTC -> encoder -> connector routing if
53 // any. It makes the initialization faster, and may be better
54 // since we have a very dumb picking algorithm.
55 bool isBestChoice = (!connector->encoder_id ||
56 (connector->encoder_id == encoderId &&
57 resources->crtcs[j] == crtcId));
58
59 if (isPossible && isAvailable && isBestChoice) {
60 return j;
61 } else if (isPossible && isAvailable) {
62 candidate = j;
63 }
64 }
65 }
66
67 return candidate;
68}
69
70static const char * const connector_type_names[] = { // must match DRM_MODE_CONNECTOR_*
71 "None",
72 "VGA",
73 "DVI",
74 "DVI",
75 "DVI",
76 "Composite",
77 "TV",
78 "LVDS",
79 "CTV",
80 "DIN",
81 "DP",
82 "HDMI",
83 "HDMI",
84 "TV",
85 "eDP",
86 "Virtual",
87 "DSI"
88};
89
90static QByteArray nameForConnector(const drmModeConnectorPtr connector)
91{
92 QByteArray connectorName("UNKNOWN");
93
94 if (connector->connector_type < ARRAY_LENGTH(connector_type_names))
95 connectorName = connector_type_names[connector->connector_type];
96
97 connectorName += QByteArray::number(connector->connector_type_id);
98
99 return connectorName;
100}
101
102static bool parseModeline(const QByteArray &text, drmModeModeInfoPtr mode)
103{
104 char hsync[16];
105 char vsync[16];
106 float fclock;
107
108 mode->type = DRM_MODE_TYPE_USERDEF;
109 mode->hskew = 0;
110 mode->vscan = 0;
111 mode->vrefresh = 0;
112 mode->flags = 0;
113
114 if (sscanf(s: text.constData(), format: "%f %hd %hd %hd %hd %hd %hd %hd %hd %15s %15s",
115 &fclock,
116 &mode->hdisplay,
117 &mode->hsync_start,
118 &mode->hsync_end,
119 &mode->htotal,
120 &mode->vdisplay,
121 &mode->vsync_start,
122 &mode->vsync_end,
123 &mode->vtotal, hsync, vsync) != 11)
124 return false;
125
126 mode->clock = fclock * 1000;
127
128 if (strcmp(s1: hsync, s2: "+hsync") == 0)
129 mode->flags |= DRM_MODE_FLAG_PHSYNC;
130 else if (strcmp(s1: hsync, s2: "-hsync") == 0)
131 mode->flags |= DRM_MODE_FLAG_NHSYNC;
132 else
133 return false;
134
135 if (strcmp(s1: vsync, s2: "+vsync") == 0)
136 mode->flags |= DRM_MODE_FLAG_PVSYNC;
137 else if (strcmp(s1: vsync, s2: "-vsync") == 0)
138 mode->flags |= DRM_MODE_FLAG_NVSYNC;
139 else
140 return false;
141
142 return true;
143}
144
145static inline void assignPlane(QKmsOutput *output, QKmsPlane *plane)
146{
147 if (output->eglfs_plane)
148 output->eglfs_plane->activeCrtcId = 0;
149
150 plane->activeCrtcId = output->crtc_id;
151 output->eglfs_plane = plane;
152}
153
154QPlatformScreen *QKmsDevice::createScreenForConnector(drmModeResPtr resources,
155 drmModeConnectorPtr connector,
156 ScreenInfo *vinfo)
157{
158 Q_ASSERT(vinfo);
159 const QByteArray connectorName = nameForConnector(connector);
160
161 const int crtc = crtcForConnector(resources, connector);
162 if (crtc < 0) {
163 qWarning() << "No usable crtc/encoder pair for connector" << connectorName;
164 return nullptr;
165 }
166
167 OutputConfiguration configuration;
168 QSize configurationSize;
169 int configurationRefresh = 0;
170 drmModeModeInfo configurationModeline;
171
172 auto userConfig = m_screenConfig->outputSettings();
173 QVariantMap userConnectorConfig = userConfig.value(key: QString::fromUtf8(ba: connectorName));
174 // default to the preferred mode unless overridden in the config
175 const QByteArray mode = userConnectorConfig.value(QStringLiteral("mode"), QStringLiteral("preferred"))
176 .toByteArray().toLower();
177 if (mode == "off") {
178 configuration = OutputConfigOff;
179 } else if (mode == "preferred") {
180 configuration = OutputConfigPreferred;
181 } else if (mode == "current") {
182 configuration = OutputConfigCurrent;
183 } else if (mode == "skip") {
184 configuration = OutputConfigSkip;
185 } else if (sscanf(s: mode.constData(), format: "%dx%d@%d", &configurationSize.rwidth(), &configurationSize.rheight(),
186 &configurationRefresh) == 3)
187 {
188 configuration = OutputConfigMode;
189 } else if (sscanf(s: mode.constData(), format: "%dx%d", &configurationSize.rwidth(), &configurationSize.rheight()) == 2) {
190 configuration = OutputConfigMode;
191 } else if (parseModeline(text: mode, mode: &configurationModeline)) {
192 configuration = OutputConfigModeline;
193 } else {
194 qWarning(msg: "Invalid mode \"%s\" for output %s", mode.constData(), connectorName.constData());
195 configuration = OutputConfigPreferred;
196 }
197
198 *vinfo = ScreenInfo();
199 vinfo->virtualIndex = userConnectorConfig.value(QStringLiteral("virtualIndex"), INT_MAX).toInt();
200 if (userConnectorConfig.contains(QStringLiteral("virtualPos"))) {
201 const QByteArray vpos = userConnectorConfig.value(QStringLiteral("virtualPos")).toByteArray();
202 const QByteArrayList vposComp = vpos.split(sep: ',');
203 if (vposComp.size() == 2)
204 vinfo->virtualPos = QPoint(vposComp[0].trimmed().toInt(), vposComp[1].trimmed().toInt());
205 }
206 if (userConnectorConfig.value(QStringLiteral("primary")).toBool())
207 vinfo->isPrimary = true;
208
209 const uint32_t crtc_id = resources->crtcs[crtc];
210
211 if (configuration == OutputConfigOff) {
212 qCDebug(qLcKmsDebug) << "Turning off output" << connectorName;
213 drmModeSetCrtc(fd: m_dri_fd, crtcId: crtc_id, bufferId: 0, x: 0, y: 0, connectors: 0, count: 0, mode: nullptr);
214 return nullptr;
215 }
216
217 // Skip disconnected output
218 if (configuration == OutputConfigPreferred && connector->connection == DRM_MODE_DISCONNECTED) {
219 qCDebug(qLcKmsDebug) << "Skipping disconnected output" << connectorName;
220 return nullptr;
221 }
222
223 if (configuration == OutputConfigSkip) {
224 qCDebug(qLcKmsDebug) << "Skipping output" << connectorName;
225 return nullptr;
226 }
227
228 // Get the current mode on the current crtc
229 drmModeModeInfo crtc_mode;
230 memset(s: &crtc_mode, c: 0, n: sizeof crtc_mode);
231 if (drmModeEncoderPtr encoder = drmModeGetEncoder(fd: m_dri_fd, encoder_id: connector->encoder_id)) {
232 drmModeCrtcPtr crtc = drmModeGetCrtc(fd: m_dri_fd, crtcId: encoder->crtc_id);
233 drmModeFreeEncoder(ptr: encoder);
234
235 if (!crtc)
236 return nullptr;
237
238 if (crtc->mode_valid)
239 crtc_mode = crtc->mode;
240
241 drmModeFreeCrtc(ptr: crtc);
242 }
243
244 QList<drmModeModeInfo> modes;
245 modes.reserve(asize: connector->count_modes);
246 qCDebug(qLcKmsDebug) << connectorName << "mode count:" << connector->count_modes
247 << "crtc index:" << crtc << "crtc id:" << crtc_id;
248 for (int i = 0; i < connector->count_modes; i++) {
249 const drmModeModeInfo &mode = connector->modes[i];
250 qCDebug(qLcKmsDebug) << "mode" << i << mode.hdisplay << "x" << mode.vdisplay
251 << '@' << mode.vrefresh << "hz";
252 modes << connector->modes[i];
253 }
254
255 int preferred = -1;
256 int current = -1;
257 int configured = -1;
258 int best = -1;
259
260 for (int i = modes.size() - 1; i >= 0; i--) {
261 const drmModeModeInfo &m = modes.at(i);
262
263 if (configuration == OutputConfigMode
264 && m.hdisplay == configurationSize.width()
265 && m.vdisplay == configurationSize.height()
266 && (!configurationRefresh || m.vrefresh == uint32_t(configurationRefresh)))
267 {
268 configured = i;
269 }
270
271 if (!memcmp(s1: &crtc_mode, s2: &m, n: sizeof m))
272 current = i;
273
274 if (m.type & DRM_MODE_TYPE_PREFERRED)
275 preferred = i;
276
277 best = i;
278 }
279
280 if (configuration == OutputConfigModeline) {
281 modes << configurationModeline;
282 configured = modes.size() - 1;
283 }
284
285 if (current < 0 && crtc_mode.clock != 0) {
286 modes << crtc_mode;
287 current = modes.size() - 1;
288 }
289
290 if (configuration == OutputConfigCurrent)
291 configured = current;
292
293 int selected_mode = -1;
294
295 if (configured >= 0)
296 selected_mode = configured;
297 else if (preferred >= 0)
298 selected_mode = preferred;
299 else if (current >= 0)
300 selected_mode = current;
301 else if (best >= 0)
302 selected_mode = best;
303
304 if (selected_mode < 0) {
305 qWarning() << "No modes available for output" << connectorName;
306 return nullptr;
307 } else {
308 int width = modes[selected_mode].hdisplay;
309 int height = modes[selected_mode].vdisplay;
310 int refresh = modes[selected_mode].vrefresh;
311 qCDebug(qLcKmsDebug) << "Selected mode" << selected_mode << ":" << width << "x" << height
312 << '@' << refresh << "hz for output" << connectorName;
313 }
314
315 // physical size from connector < config values < env vars
316 int pwidth = qEnvironmentVariableIntValue(varName: "QT_QPA_EGLFS_PHYSICAL_WIDTH");
317 if (!pwidth)
318 pwidth = qEnvironmentVariableIntValue(varName: "QT_QPA_PHYSICAL_WIDTH");
319 int pheight = qEnvironmentVariableIntValue(varName: "QT_QPA_EGLFS_PHYSICAL_HEIGHT");
320 if (!pheight)
321 pheight = qEnvironmentVariableIntValue(varName: "QT_QPA_PHYSICAL_HEIGHT");
322 QSizeF physSize(pwidth, pheight);
323 if (physSize.isEmpty()) {
324 physSize = QSize(userConnectorConfig.value(QStringLiteral("physicalWidth")).toInt(),
325 userConnectorConfig.value(QStringLiteral("physicalHeight")).toInt());
326 if (physSize.isEmpty()) {
327 physSize.setWidth(connector->mmWidth);
328 physSize.setHeight(connector->mmHeight);
329 }
330 }
331 qCDebug(qLcKmsDebug) << "Physical size is" << physSize << "mm" << "for output" << connectorName;
332
333 const QByteArray formatStr = userConnectorConfig.value(QStringLiteral("format"), defaultValue: QString())
334 .toByteArray().toLower();
335 uint32_t drmFormat;
336 bool drmFormatExplicit = true;
337 if (formatStr.isEmpty()) {
338 drmFormat = DRM_FORMAT_XRGB8888;
339 drmFormatExplicit = false;
340 } else if (formatStr == "xrgb8888") {
341 drmFormat = DRM_FORMAT_XRGB8888;
342 } else if (formatStr == "xbgr8888") {
343 drmFormat = DRM_FORMAT_XBGR8888;
344 } else if (formatStr == "argb8888") {
345 drmFormat = DRM_FORMAT_ARGB8888;
346 } else if (formatStr == "abgr8888") {
347 drmFormat = DRM_FORMAT_ABGR8888;
348 } else if (formatStr == "rgb565") {
349 drmFormat = DRM_FORMAT_RGB565;
350 } else if (formatStr == "bgr565") {
351 drmFormat = DRM_FORMAT_BGR565;
352 } else if (formatStr == "xrgb2101010") {
353 drmFormat = DRM_FORMAT_XRGB2101010;
354 } else if (formatStr == "xbgr2101010") {
355 drmFormat = DRM_FORMAT_XBGR2101010;
356 } else if (formatStr == "argb2101010") {
357 drmFormat = DRM_FORMAT_ARGB2101010;
358 } else if (formatStr == "abgr2101010") {
359 drmFormat = DRM_FORMAT_ABGR2101010;
360 } else {
361 qWarning(msg: "Invalid pixel format \"%s\" for output %s", formatStr.constData(), connectorName.constData());
362 drmFormat = DRM_FORMAT_XRGB8888;
363 drmFormatExplicit = false;
364 }
365 qCDebug(qLcKmsDebug) << "Format is" << Qt::hex << drmFormat << Qt::dec << "requested_by_user =" << drmFormatExplicit
366 << "for output" << connectorName;
367
368 const QString cloneSource = userConnectorConfig.value(QStringLiteral("clones")).toString();
369 if (!cloneSource.isEmpty())
370 qCDebug(qLcKmsDebug) << "Output" << connectorName << " clones output " << cloneSource;
371
372 QSize framebufferSize;
373 bool framebufferSizeSet = false;
374 const QByteArray fbsize = userConnectorConfig.value(QStringLiteral("size")).toByteArray().toLower();
375 if (!fbsize.isEmpty()) {
376 if (sscanf(s: fbsize.constData(), format: "%dx%d", &framebufferSize.rwidth(), &framebufferSize.rheight()) == 2) {
377#if QT_CONFIG(drm_atomic)
378 if (hasAtomicSupport())
379 framebufferSizeSet = true;
380#endif
381 if (!framebufferSizeSet)
382 qWarning(msg: "Setting framebuffer size is only available with DRM atomic API");
383 } else {
384 qWarning(msg: "Invalid framebuffer size '%s'", fbsize.constData());
385 }
386 }
387 if (!framebufferSizeSet) {
388 framebufferSize.setWidth(modes[selected_mode].hdisplay);
389 framebufferSize.setHeight(modes[selected_mode].vdisplay);
390 }
391
392 qCDebug(qLcKmsDebug) << "Output" << connectorName << "framebuffer size is " << framebufferSize;
393
394 QKmsOutput output;
395 output.name = QString::fromUtf8(ba: connectorName);
396 output.connector_id = connector->connector_id;
397 output.crtc_index = crtc;
398 output.crtc_id = crtc_id;
399 output.physical_size = physSize;
400 output.preferred_mode = preferred >= 0 ? preferred : selected_mode;
401 output.mode = selected_mode;
402 output.mode_set = false;
403 output.saved_crtc = drmModeGetCrtc(fd: m_dri_fd, crtcId: crtc_id);
404 output.modes = modes;
405 output.subpixel = connector->subpixel;
406 output.dpms_prop = connectorProperty(connector, QByteArrayLiteral("DPMS"));
407 output.edid_blob = connectorPropertyBlob(connector, QByteArrayLiteral("EDID"));
408 output.wants_forced_plane = false;
409 output.forced_plane_id = 0;
410 output.forced_plane_set = false;
411 output.drm_format = drmFormat;
412 output.drm_format_requested_by_user = drmFormatExplicit;
413 output.clone_source = cloneSource;
414 output.size = framebufferSize;
415
416#if QT_CONFIG(drm_atomic)
417 if (drmModeCreatePropertyBlob(fd: m_dri_fd, data: &modes[selected_mode], size: sizeof(drmModeModeInfo),
418 id: &output.mode_blob_id) != 0) {
419 qCDebug(qLcKmsDebug) << "Failed to create mode blob for mode" << selected_mode;
420 }
421
422 parseConnectorProperties(connectorId: output.connector_id, output: &output);
423 parseCrtcProperties(crtcId: output.crtc_id, output: &output);
424#endif
425
426 QString planeListStr;
427 for (QKmsPlane &plane : m_planes) {
428 if (plane.possibleCrtcs & (1 << output.crtc_index)) {
429 output.available_planes.append(t: plane);
430 planeListStr.append(s: QString::number(plane.id));
431 planeListStr.append(c: u' ');
432
433 // Choose the first primary plane that is not already assigned to
434 // another screen's associated crtc.
435 if (!output.eglfs_plane && plane.type == QKmsPlane::PrimaryPlane && !plane.activeCrtcId)
436 assignPlane(output: &output, plane: &plane);
437 }
438 }
439 qCDebug(qLcKmsDebug, "Output %s can use %d planes: %s",
440 connectorName.constData(), int(output.available_planes.size()), qPrintable(planeListStr));
441
442 // This is for the EGLDevice/EGLStream backend. On some of those devices one
443 // may want to target a pre-configured plane. It is probably useless for
444 // eglfs_kms and others. Do not confuse with generic plane support (available_planes).
445 bool ok;
446 int idx = qEnvironmentVariableIntValue(varName: "QT_QPA_EGLFS_KMS_PLANE_INDEX", ok: &ok);
447 if (ok) {
448 drmModePlaneRes *planeResources = drmModeGetPlaneResources(fd: m_dri_fd);
449 if (planeResources) {
450 if (idx >= 0 && idx < int(planeResources->count_planes)) {
451 drmModePlane *plane = drmModeGetPlane(fd: m_dri_fd, plane_id: planeResources->planes[idx]);
452 if (plane) {
453 output.wants_forced_plane = true;
454 output.forced_plane_id = plane->plane_id;
455 qCDebug(qLcKmsDebug, "Forcing plane index %d, plane id %u (belongs to crtc id %u)",
456 idx, plane->plane_id, plane->crtc_id);
457
458 for (QKmsPlane &kmsplane : m_planes) {
459 if (kmsplane.id == output.forced_plane_id) {
460 assignPlane(output: &output, plane: &kmsplane);
461 break;
462 }
463 }
464
465 drmModeFreePlane(ptr: plane);
466 }
467 } else {
468 qWarning(msg: "Invalid plane index %d, must be between 0 and %u", idx, planeResources->count_planes - 1);
469 }
470 }
471 }
472
473 // A more useful version: allows specifying "crtc_id,plane_id:crtc_id,plane_id:..."
474 // in order to allow overriding the plane used for a given crtc.
475 if (qEnvironmentVariableIsSet(varName: "QT_QPA_EGLFS_KMS_PLANES_FOR_CRTCS")) {
476 const QString val = qEnvironmentVariable(varName: "QT_QPA_EGLFS_KMS_PLANES_FOR_CRTCS");
477 qCDebug(qLcKmsDebug, "crtc_id:plane_id override list: %s", qPrintable(val));
478 const QStringList crtcPlanePairs = val.split(sep: u':');
479 for (const QString &crtcPlanePair : crtcPlanePairs) {
480 const QStringList values = crtcPlanePair.split(sep: u',');
481 if (values.size() == 2 && uint(values[0].toInt()) == output.crtc_id) {
482 uint planeId = values[1].toInt();
483 for (QKmsPlane &kmsplane : m_planes) {
484 if (kmsplane.id == planeId) {
485 assignPlane(output: &output, plane: &kmsplane);
486 break;
487 }
488 }
489 }
490 }
491 }
492
493 if (output.eglfs_plane) {
494 qCDebug(qLcKmsDebug, "Chose plane %u for output %s (crtc id %u) (may not be applicable)",
495 output.eglfs_plane->id, connectorName.constData(), output.crtc_id);
496 }
497
498#if QT_CONFIG(drm_atomic)
499 if (hasAtomicSupport() && !output.eglfs_plane) {
500 qCDebug(qLcKmsDebug, "No plane associated with output %s (crtc id %u) and atomic modesetting is enabled. This is bad.",
501 connectorName.constData(), output.crtc_id);
502 }
503#endif
504
505 m_crtc_allocator |= (1 << output.crtc_index);
506
507 vinfo->output = output;
508
509 return createScreen(output);
510}
511
512drmModePropertyPtr QKmsDevice::connectorProperty(drmModeConnectorPtr connector, const QByteArray &name)
513{
514 drmModePropertyPtr prop;
515
516 for (int i = 0; i < connector->count_props; i++) {
517 prop = drmModeGetProperty(fd: m_dri_fd, propertyId: connector->props[i]);
518 if (!prop)
519 continue;
520 if (strcmp(s1: prop->name, s2: name.constData()) == 0)
521 return prop;
522 drmModeFreeProperty(ptr: prop);
523 }
524
525 return nullptr;
526}
527
528drmModePropertyBlobPtr QKmsDevice::connectorPropertyBlob(drmModeConnectorPtr connector, const QByteArray &name)
529{
530 drmModePropertyPtr prop;
531 drmModePropertyBlobPtr blob = nullptr;
532
533 for (int i = 0; i < connector->count_props && !blob; i++) {
534 prop = drmModeGetProperty(fd: m_dri_fd, propertyId: connector->props[i]);
535 if (!prop)
536 continue;
537 if ((prop->flags & DRM_MODE_PROP_BLOB) && (strcmp(s1: prop->name, s2: name.constData()) == 0))
538 blob = drmModeGetPropertyBlob(fd: m_dri_fd, blob_id: connector->prop_values[i]);
539 drmModeFreeProperty(ptr: prop);
540 }
541
542 return blob;
543}
544
545QKmsDevice::QKmsDevice(QKmsScreenConfig *screenConfig, const QString &path)
546 : m_screenConfig(screenConfig)
547 , m_path(path)
548 , m_dri_fd(-1)
549 , m_has_atomic_support(false)
550 , m_crtc_allocator(0)
551{
552 if (m_path.isEmpty()) {
553 m_path = m_screenConfig->devicePath();
554 qCDebug(qLcKmsDebug, "Using DRM device %s specified in config file", qPrintable(m_path));
555 if (m_path.isEmpty())
556 qFatal(msg: "No DRM device given");
557 } else {
558 qCDebug(qLcKmsDebug, "Using backend-provided DRM device %s", qPrintable(m_path));
559 }
560}
561
562QKmsDevice::~QKmsDevice()
563{
564#if QT_CONFIG(drm_atomic)
565 threadLocalAtomicReset();
566#endif
567}
568
569struct OrderedScreen
570{
571 OrderedScreen() : screen(nullptr) { }
572 OrderedScreen(QPlatformScreen *screen, const QKmsDevice::ScreenInfo &vinfo)
573 : screen(screen), vinfo(vinfo) { }
574 QPlatformScreen *screen;
575 QKmsDevice::ScreenInfo vinfo;
576};
577
578QDebug operator<<(QDebug dbg, const OrderedScreen &s)
579{
580 QDebugStateSaver saver(dbg);
581 dbg.nospace() << "OrderedScreen(QPlatformScreen=" << s.screen << " (" << s.screen->name() << ") : "
582 << s.vinfo.virtualIndex
583 << " / " << s.vinfo.virtualPos
584 << " / primary: " << s.vinfo.isPrimary
585 << ")";
586 return dbg;
587}
588
589static bool orderedScreenLessThan(const OrderedScreen &a, const OrderedScreen &b)
590{
591 return a.vinfo.virtualIndex < b.vinfo.virtualIndex;
592}
593
594void QKmsDevice::createScreens()
595{
596 // Headless mode using a render node: cannot do any output related DRM
597 // stuff. Skip it all and register a dummy screen.
598 if (m_screenConfig->headless()) {
599 QPlatformScreen *screen = createHeadlessScreen();
600 if (screen) {
601 qCDebug(qLcKmsDebug, "Headless mode enabled");
602 registerScreen(screen, isPrimary: true, virtualPos: QPoint(0, 0), virtualSiblings: QList<QPlatformScreen *>());
603 return;
604 } else {
605 qWarning(msg: "QKmsDevice: Requested headless mode without support in the backend. Request is ignored.");
606 }
607 }
608
609 drmSetClientCap(fd: m_dri_fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, value: 1);
610
611#if QT_CONFIG(drm_atomic)
612 // check atomic support
613 m_has_atomic_support = !drmSetClientCap(fd: m_dri_fd, DRM_CLIENT_CAP_ATOMIC, value: 1);
614 if (m_has_atomic_support) {
615 qCDebug(qLcKmsDebug, "Atomic reported as supported");
616 if (qEnvironmentVariableIntValue(varName: "QT_QPA_EGLFS_KMS_ATOMIC")) {
617 qCDebug(qLcKmsDebug, "Atomic enabled");
618 } else {
619 qCDebug(qLcKmsDebug, "Atomic disabled");
620 m_has_atomic_support = false;
621 }
622 }
623#endif
624
625 drmModeResPtr resources = drmModeGetResources(fd: m_dri_fd);
626 if (!resources) {
627 qErrnoWarning(errno, msg: "drmModeGetResources failed");
628 return;
629 }
630
631 discoverPlanes();
632
633 QList<OrderedScreen> screens;
634
635 int wantedConnectorIndex = -1;
636 bool ok;
637 int idx = qEnvironmentVariableIntValue(varName: "QT_QPA_EGLFS_KMS_CONNECTOR_INDEX", ok: &ok);
638 if (ok) {
639 if (idx >= 0 && idx < resources->count_connectors)
640 wantedConnectorIndex = idx;
641 else
642 qWarning(msg: "Invalid connector index %d, must be between 0 and %u", idx, resources->count_connectors - 1);
643 }
644
645 for (int i = 0; i < resources->count_connectors; i++) {
646 if (wantedConnectorIndex >= 0 && i != wantedConnectorIndex)
647 continue;
648
649 drmModeConnectorPtr connector = drmModeGetConnector(fd: m_dri_fd, connectorId: resources->connectors[i]);
650 if (!connector)
651 continue;
652
653 ScreenInfo vinfo;
654 QPlatformScreen *screen = createScreenForConnector(resources, connector, vinfo: &vinfo);
655 if (screen)
656 screens.append(t: OrderedScreen(screen, vinfo));
657
658 drmModeFreeConnector(ptr: connector);
659 }
660
661 drmModeFreeResources(ptr: resources);
662
663 // Use stable sort to preserve the original (DRM connector) order
664 // for outputs with unspecified indices.
665 std::stable_sort(first: screens.begin(), last: screens.end(), comp: orderedScreenLessThan);
666 qCDebug(qLcKmsDebug) << "Sorted screen list:" << screens;
667
668 // The final list of screens is available, so do the second phase setup.
669 // Hook up clone sources and targets.
670 for (const OrderedScreen &orderedScreen : screens) {
671 QList<QPlatformScreen *> screensCloningThisScreen;
672 for (const OrderedScreen &s : screens) {
673 if (s.vinfo.output.clone_source == orderedScreen.vinfo.output.name)
674 screensCloningThisScreen.append(t: s.screen);
675 }
676 QPlatformScreen *screenThisScreenClones = nullptr;
677 if (!orderedScreen.vinfo.output.clone_source.isEmpty()) {
678 for (const OrderedScreen &s : screens) {
679 if (s.vinfo.output.name == orderedScreen.vinfo.output.clone_source) {
680 screenThisScreenClones = s.screen;
681 break;
682 }
683 }
684 }
685 if (screenThisScreenClones)
686 qCDebug(qLcKmsDebug) << orderedScreen.screen->name() << "clones" << screenThisScreenClones;
687 if (!screensCloningThisScreen.isEmpty())
688 qCDebug(qLcKmsDebug) << orderedScreen.screen->name() << "is cloned by" << screensCloningThisScreen;
689
690 registerScreenCloning(screen: orderedScreen.screen, screenThisScreenClones, screensCloningThisScreen);
691 }
692
693 // Figure out the virtual desktop and register the screens to QPA/QGuiApplication.
694 QPoint pos(0, 0);
695 QList<QPlatformScreen *> siblings;
696 QList<QPoint> virtualPositions;
697 int primarySiblingIdx = -1;
698
699 for (const OrderedScreen &orderedScreen : screens) {
700 QPlatformScreen *s = orderedScreen.screen;
701 QPoint virtualPos(0, 0);
702 // set up a horizontal or vertical virtual desktop
703 if (orderedScreen.vinfo.virtualPos.isNull()) {
704 virtualPos = pos;
705 if (m_screenConfig->virtualDesktopLayout() == QKmsScreenConfig::VirtualDesktopLayoutVertical)
706 pos.ry() += s->geometry().height();
707 else
708 pos.rx() += s->geometry().width();
709 } else {
710 virtualPos = orderedScreen.vinfo.virtualPos;
711 }
712 qCDebug(qLcKmsDebug) << "Adding QPlatformScreen" << s << "(" << s->name() << ")"
713 << "to QPA with geometry" << s->geometry()
714 << "and isPrimary=" << orderedScreen.vinfo.isPrimary;
715 // The order in qguiapp's screens list will match the order set by
716 // virtualIndex. This is not only handy but also required since for instance
717 // evdevtouch relies on it when performing touch device - screen mapping.
718 if (!m_screenConfig->separateScreens()) {
719 qCDebug(qLcKmsDebug) << " virtual position is" << virtualPos;
720 siblings.append(t: s);
721 virtualPositions.append(t: virtualPos);
722 if (orderedScreen.vinfo.isPrimary)
723 primarySiblingIdx = siblings.size() - 1;
724 } else {
725 registerScreen(screen: s, isPrimary: orderedScreen.vinfo.isPrimary, virtualPos, virtualSiblings: QList<QPlatformScreen *>() << s);
726 }
727 }
728
729 if (!m_screenConfig->separateScreens()) {
730 // enable the virtual desktop
731 for (int i = 0; i < siblings.size(); ++i)
732 registerScreen(screen: siblings[i], isPrimary: i == primarySiblingIdx, virtualPos: virtualPositions[i], virtualSiblings: siblings);
733 }
734}
735
736QPlatformScreen *QKmsDevice::createHeadlessScreen()
737{
738 // headless mode not supported by default
739 return nullptr;
740}
741
742// not all subclasses support screen cloning
743void QKmsDevice::registerScreenCloning(QPlatformScreen *screen,
744 QPlatformScreen *screenThisScreenClones,
745 const QList<QPlatformScreen *> &screensCloningThisScreen)
746{
747 Q_UNUSED(screen);
748 Q_UNUSED(screenThisScreenClones);
749 Q_UNUSED(screensCloningThisScreen);
750}
751
752// drm_property_type_is is not available in old headers
753static inline bool propTypeIs(drmModePropertyPtr prop, uint32_t type)
754{
755 if (prop->flags & DRM_MODE_PROP_EXTENDED_TYPE)
756 return (prop->flags & DRM_MODE_PROP_EXTENDED_TYPE) == type;
757 return prop->flags & type;
758}
759
760void QKmsDevice::enumerateProperties(drmModeObjectPropertiesPtr objProps, PropCallback callback)
761{
762 for (uint32_t propIdx = 0; propIdx < objProps->count_props; ++propIdx) {
763 drmModePropertyPtr prop = drmModeGetProperty(fd: m_dri_fd, propertyId: objProps->props[propIdx]);
764 if (!prop)
765 continue;
766
767 const quint64 value = objProps->prop_values[propIdx];
768 qCDebug(qLcKmsDebug, " property %d: id = %u name = '%s'", propIdx, prop->prop_id, prop->name);
769
770 if (propTypeIs(prop, DRM_MODE_PROP_SIGNED_RANGE)) {
771 qCDebug(qLcKmsDebug, " type is SIGNED_RANGE, value is %lld, possible values are:", qint64(value));
772 for (int i = 0; i < prop->count_values; ++i)
773 qCDebug(qLcKmsDebug, " %lld", qint64(prop->values[i]));
774 } else if (propTypeIs(prop, DRM_MODE_PROP_RANGE)) {
775 qCDebug(qLcKmsDebug, " type is RANGE, value is %llu, possible values are:", value);
776 for (int i = 0; i < prop->count_values; ++i)
777 qCDebug(qLcKmsDebug, " %llu", quint64(prop->values[i]));
778 } else if (propTypeIs(prop, DRM_MODE_PROP_ENUM)) {
779 qCDebug(qLcKmsDebug, " type is ENUM, value is %llu, possible values are:", value);
780 for (int i = 0; i < prop->count_enums; ++i)
781 qCDebug(qLcKmsDebug, " enum %d: %s - %llu", i, prop->enums[i].name, quint64(prop->enums[i].value));
782 } else if (propTypeIs(prop, DRM_MODE_PROP_BITMASK)) {
783 qCDebug(qLcKmsDebug, " type is BITMASK, value is %llu, possible bits are:", value);
784 for (int i = 0; i < prop->count_enums; ++i)
785 qCDebug(qLcKmsDebug, " bitmask %d: %s - %u", i, prop->enums[i].name, 1 << prop->enums[i].value);
786 } else if (propTypeIs(prop, DRM_MODE_PROP_BLOB)) {
787 qCDebug(qLcKmsDebug, " type is BLOB");
788 } else if (propTypeIs(prop, DRM_MODE_PROP_OBJECT)) {
789 qCDebug(qLcKmsDebug, " type is OBJECT");
790 }
791
792 callback(prop, value);
793
794 drmModeFreeProperty(ptr: prop);
795 }
796}
797
798void QKmsDevice::discoverPlanes()
799{
800 m_planes.clear();
801
802 drmModePlaneResPtr planeResources = drmModeGetPlaneResources(fd: m_dri_fd);
803 if (!planeResources)
804 return;
805
806 const int countPlanes = planeResources->count_planes;
807 qCDebug(qLcKmsDebug, "Found %d planes", countPlanes);
808 for (int planeIdx = 0; planeIdx < countPlanes; ++planeIdx) {
809 drmModePlanePtr drmplane = drmModeGetPlane(fd: m_dri_fd, plane_id: planeResources->planes[planeIdx]);
810 if (!drmplane) {
811 qCDebug(qLcKmsDebug, "Failed to query plane %d, ignoring", planeIdx);
812 continue;
813 }
814
815 QKmsPlane plane;
816 plane.id = drmplane->plane_id;
817 plane.possibleCrtcs = drmplane->possible_crtcs;
818
819 const int countFormats = drmplane->count_formats;
820 QString formatStr;
821 for (int i = 0; i < countFormats; ++i) {
822 uint32_t f = drmplane->formats[i];
823 plane.supportedFormats.append(t: f);
824 formatStr += QString::asprintf(format: "%c%c%c%c ", f, f >> 8, f >> 16, f >> 24);
825 }
826
827 qCDebug(qLcKmsDebug, "plane %d: id = %u countFormats = %d possibleCrtcs = 0x%x supported formats = %s",
828 planeIdx, plane.id, countFormats, plane.possibleCrtcs, qPrintable(formatStr));
829
830 drmModeFreePlane(ptr: drmplane);
831
832 drmModeObjectPropertiesPtr objProps = drmModeObjectGetProperties(fd: m_dri_fd, object_id: plane.id, DRM_MODE_OBJECT_PLANE);
833 if (!objProps) {
834 qCDebug(qLcKmsDebug, "Failed to query plane %d object properties, ignoring", planeIdx);
835 continue;
836 }
837
838 enumerateProperties(objProps, callback: [&plane](drmModePropertyPtr prop, quint64 value) {
839 if (!strcmp(s1: prop->name, s2: "type")) {
840 plane.type = QKmsPlane::Type(value);
841 } else if (!strcmp(s1: prop->name, s2: "rotation")) {
842 plane.initialRotation = QKmsPlane::Rotations(int(value));
843 plane.availableRotations = { };
844 if (propTypeIs(prop, DRM_MODE_PROP_BITMASK)) {
845 for (int i = 0; i < prop->count_enums; ++i)
846 plane.availableRotations |= QKmsPlane::Rotation(1 << prop->enums[i].value);
847 }
848 plane.rotationPropertyId = prop->prop_id;
849 } else if (!strcasecmp(s1: prop->name, s2: "crtc_id")) {
850 plane.crtcPropertyId = prop->prop_id;
851 } else if (!strcasecmp(s1: prop->name, s2: "fb_id")) {
852 plane.framebufferPropertyId = prop->prop_id;
853 } else if (!strcasecmp(s1: prop->name, s2: "src_w")) {
854 plane.srcwidthPropertyId = prop->prop_id;
855 } else if (!strcasecmp(s1: prop->name, s2: "src_h")) {
856 plane.srcheightPropertyId = prop->prop_id;
857 } else if (!strcasecmp(s1: prop->name, s2: "crtc_w")) {
858 plane.crtcwidthPropertyId = prop->prop_id;
859 } else if (!strcasecmp(s1: prop->name, s2: "crtc_h")) {
860 plane.crtcheightPropertyId = prop->prop_id;
861 } else if (!strcasecmp(s1: prop->name, s2: "src_x")) {
862 plane.srcXPropertyId = prop->prop_id;
863 } else if (!strcasecmp(s1: prop->name, s2: "src_y")) {
864 plane.srcYPropertyId = prop->prop_id;
865 } else if (!strcasecmp(s1: prop->name, s2: "crtc_x")) {
866 plane.crtcXPropertyId = prop->prop_id;
867 } else if (!strcasecmp(s1: prop->name, s2: "crtc_y")) {
868 plane.crtcYPropertyId = prop->prop_id;
869 } else if (!strcasecmp(s1: prop->name, s2: "zpos")) {
870 plane.zposPropertyId = prop->prop_id;
871 } else if (!strcasecmp(s1: prop->name, s2: "blend_op")) {
872 plane.blendOpPropertyId = prop->prop_id;
873 }
874 });
875
876 m_planes.append(t: plane);
877
878 drmModeFreeObjectProperties(ptr: objProps);
879 }
880
881 drmModeFreePlaneResources(ptr: planeResources);
882}
883
884int QKmsDevice::fd() const
885{
886 return m_dri_fd;
887}
888
889QString QKmsDevice::devicePath() const
890{
891 return m_path;
892}
893
894void QKmsDevice::setFd(int fd)
895{
896 m_dri_fd = fd;
897}
898
899
900bool QKmsDevice::hasAtomicSupport()
901{
902 return m_has_atomic_support;
903}
904
905#if QT_CONFIG(drm_atomic)
906drmModeAtomicReq *QKmsDevice::threadLocalAtomicRequest()
907{
908 if (!m_has_atomic_support)
909 return nullptr;
910
911 AtomicReqs &a(m_atomicReqs.localData());
912 if (!a.request)
913 a.request = drmModeAtomicAlloc();
914
915 return a.request;
916}
917
918bool QKmsDevice::threadLocalAtomicCommit(void *user_data)
919{
920 if (!m_has_atomic_support)
921 return false;
922
923 AtomicReqs &a(m_atomicReqs.localData());
924 if (!a.request)
925 return false;
926
927 int ret = drmModeAtomicCommit(fd: m_dri_fd, req: a.request,
928 DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_ALLOW_MODESET,
929 user_data);
930
931 if (ret) {
932 qWarning(msg: "Failed to commit atomic request (code=%d)", ret);
933 return false;
934 }
935
936 a.previous_request = a.request;
937 a.request = nullptr;
938
939 return true;
940}
941
942void QKmsDevice::threadLocalAtomicReset()
943{
944 if (!m_has_atomic_support)
945 return;
946
947 AtomicReqs &a(m_atomicReqs.localData());
948 if (a.previous_request) {
949 drmModeAtomicFree(req: a.previous_request);
950 a.previous_request = nullptr;
951 }
952}
953#endif
954
955void QKmsDevice::parseConnectorProperties(uint32_t connectorId, QKmsOutput *output)
956{
957 drmModeObjectPropertiesPtr objProps = drmModeObjectGetProperties(fd: m_dri_fd, object_id: connectorId, DRM_MODE_OBJECT_CONNECTOR);
958 if (!objProps) {
959 qCDebug(qLcKmsDebug, "Failed to query connector %d object properties", connectorId);
960 return;
961 }
962
963 enumerateProperties(objProps, callback: [output](drmModePropertyPtr prop, quint64 value) {
964 Q_UNUSED(value);
965 if (!strcasecmp(s1: prop->name, s2: "crtc_id"))
966 output->crtcIdPropertyId = prop->prop_id;
967 });
968
969 drmModeFreeObjectProperties(ptr: objProps);
970}
971
972void QKmsDevice::parseCrtcProperties(uint32_t crtcId, QKmsOutput *output)
973{
974 drmModeObjectPropertiesPtr objProps = drmModeObjectGetProperties(fd: m_dri_fd, object_id: crtcId, DRM_MODE_OBJECT_CRTC);
975 if (!objProps) {
976 qCDebug(qLcKmsDebug, "Failed to query crtc %d object properties", crtcId);
977 return;
978 }
979
980 enumerateProperties(objProps, callback: [output](drmModePropertyPtr prop, quint64 value) {
981 Q_UNUSED(value);
982 if (!strcasecmp(s1: prop->name, s2: "mode_id"))
983 output->modeIdPropertyId = prop->prop_id;
984 else if (!strcasecmp(s1: prop->name, s2: "active"))
985 output->activePropertyId = prop->prop_id;
986 });
987
988 drmModeFreeObjectProperties(ptr: objProps);
989}
990
991QKmsScreenConfig *QKmsDevice::screenConfig() const
992{
993 return m_screenConfig;
994}
995
996QKmsScreenConfig::QKmsScreenConfig()
997 : m_headless(false)
998 , m_hwCursor(true)
999 , m_separateScreens(false)
1000 , m_pbuffers(false)
1001 , m_virtualDesktopLayout(VirtualDesktopLayoutHorizontal)
1002{
1003}
1004
1005void QKmsScreenConfig::loadConfig()
1006{
1007 QByteArray json = qgetenv(varName: "QT_QPA_EGLFS_KMS_CONFIG");
1008 if (json.isEmpty()) {
1009 json = qgetenv(varName: "QT_QPA_KMS_CONFIG");
1010 if (json.isEmpty())
1011 return;
1012 }
1013
1014 qCDebug(qLcKmsDebug) << "Loading KMS setup from" << json;
1015
1016 QFile file(QString::fromUtf8(ba: json));
1017 if (!file.open(flags: QFile::ReadOnly)) {
1018 qCWarning(qLcKmsDebug) << "Could not open config file"
1019 << json << "for reading";
1020 return;
1021 }
1022
1023 const QJsonDocument doc = QJsonDocument::fromJson(json: file.readAll());
1024 if (!doc.isObject()) {
1025 qCWarning(qLcKmsDebug) << "Invalid config file" << json
1026 << "- no top-level JSON object";
1027 return;
1028 }
1029
1030 const QJsonObject object = doc.object();
1031
1032 const QString headlessStr = object.value(key: "headless"_L1).toString();
1033 const QByteArray headless = headlessStr.toUtf8();
1034 QSize headlessSize;
1035 if (sscanf(s: headless.constData(), format: "%dx%d", &headlessSize.rwidth(), &headlessSize.rheight()) == 2) {
1036 m_headless = true;
1037 m_headlessSize = headlessSize;
1038 } else {
1039 m_headless = false;
1040 }
1041
1042 m_hwCursor = object.value(key: "hwcursor"_L1).toBool(defaultValue: m_hwCursor);
1043 m_pbuffers = object.value(key: "pbuffers"_L1).toBool(defaultValue: m_pbuffers);
1044 m_devicePath = object.value(key: "device"_L1).toString();
1045 m_separateScreens = object.value(key: "separateScreens"_L1).toBool(defaultValue: m_separateScreens);
1046
1047 const QString vdOriString = object.value(key: "virtualDesktopLayout"_L1).toString();
1048 if (!vdOriString.isEmpty()) {
1049 if (vdOriString == "horizontal"_L1)
1050 m_virtualDesktopLayout = VirtualDesktopLayoutHorizontal;
1051 else if (vdOriString == "vertical"_L1)
1052 m_virtualDesktopLayout = VirtualDesktopLayoutVertical;
1053 else
1054 qCWarning(qLcKmsDebug) << "Unknown virtualDesktopOrientation value" << vdOriString;
1055 }
1056
1057 const QJsonArray outputs = object.value(key: "outputs"_L1).toArray();
1058 for (int i = 0; i < outputs.size(); i++) {
1059 const QVariantMap outputSettings = outputs.at(i).toObject().toVariantMap();
1060
1061 if (outputSettings.contains(QStringLiteral("name"))) {
1062 const QString name = outputSettings.value(QStringLiteral("name")).toString();
1063
1064 if (m_outputSettings.contains(key: name)) {
1065 qCDebug(qLcKmsDebug) << "Output" << name << "configured multiple times!";
1066 }
1067
1068 m_outputSettings.insert(key: name, value: outputSettings);
1069 }
1070 }
1071
1072 qCDebug(qLcKmsDebug) << "Requested configuration (some settings may be ignored):\n"
1073 << "\theadless:" << m_headless << "\n"
1074 << "\thwcursor:" << m_hwCursor << "\n"
1075 << "\tpbuffers:" << m_pbuffers << "\n"
1076 << "\tseparateScreens:" << m_separateScreens << "\n"
1077 << "\tvirtualDesktopLayout:" << m_virtualDesktopLayout << "\n"
1078 << "\toutputs:" << m_outputSettings;
1079}
1080
1081void QKmsOutput::restoreMode(QKmsDevice *device)
1082{
1083 if (mode_set && saved_crtc) {
1084 drmModeSetCrtc(fd: device->fd(),
1085 crtcId: saved_crtc->crtc_id,
1086 bufferId: saved_crtc->buffer_id,
1087 x: 0, y: 0,
1088 connectors: &connector_id, count: 1,
1089 mode: &saved_crtc->mode);
1090 mode_set = false;
1091 }
1092}
1093
1094void QKmsOutput::cleanup(QKmsDevice *device)
1095{
1096 if (dpms_prop) {
1097 drmModeFreeProperty(ptr: dpms_prop);
1098 dpms_prop = nullptr;
1099 }
1100
1101 if (edid_blob) {
1102 drmModeFreePropertyBlob(ptr: edid_blob);
1103 edid_blob = nullptr;
1104 }
1105
1106 restoreMode(device);
1107
1108 if (saved_crtc) {
1109 drmModeFreeCrtc(ptr: saved_crtc);
1110 saved_crtc = nullptr;
1111 }
1112}
1113
1114QPlatformScreen::SubpixelAntialiasingType QKmsOutput::subpixelAntialiasingTypeHint() const
1115{
1116 switch (subpixel) {
1117 default:
1118 case DRM_MODE_SUBPIXEL_UNKNOWN:
1119 case DRM_MODE_SUBPIXEL_NONE:
1120 return QPlatformScreen::Subpixel_None;
1121 case DRM_MODE_SUBPIXEL_HORIZONTAL_RGB:
1122 return QPlatformScreen::Subpixel_RGB;
1123 case DRM_MODE_SUBPIXEL_HORIZONTAL_BGR:
1124 return QPlatformScreen::Subpixel_BGR;
1125 case DRM_MODE_SUBPIXEL_VERTICAL_RGB:
1126 return QPlatformScreen::Subpixel_VRGB;
1127 case DRM_MODE_SUBPIXEL_VERTICAL_BGR:
1128 return QPlatformScreen::Subpixel_VBGR;
1129 }
1130}
1131
1132void QKmsOutput::setPowerState(QKmsDevice *device, QPlatformScreen::PowerState state)
1133{
1134 if (dpms_prop)
1135 drmModeConnectorSetProperty(fd: device->fd(), connector_id,
1136 property_id: dpms_prop->prop_id, value: (int) state);
1137}
1138
1139QT_END_NAMESPACE
1140

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

source code of qtbase/src/platformsupport/kmsconvenience/qkmsdevice.cpp