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
qkmsdevice.cpp
Go to the documentation of this file.
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_STATIC_LOGGING_CATEGORY(qLcKmsDebug, "qt.qpa.eglfs.kms")
23
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(m_dri_fd, connector->encoders[i]);
39 if (!encoder) {
40 qWarning("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(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(text.constData(), "%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(hsync, "+hsync") == 0)
129 mode->flags |= DRM_MODE_FLAG_PHSYNC;
130 else if (strcmp(hsync, "-hsync") == 0)
131 mode->flags |= DRM_MODE_FLAG_NHSYNC;
132 else
133 return false;
134
135 if (strcmp(vsync, "+vsync") == 0)
136 mode->flags |= DRM_MODE_FLAG_PVSYNC;
137 else if (strcmp(vsync, "-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)
149
150 plane->activeCrtcId = output->crtc_id;
151 output->eglfs_plane = plane;
152}
153
155 const QKmsDevice::OrderedScreen &b)
156{
157 return a.vinfo.virtualIndex < b.vinfo.virtualIndex;
158}
159
161
162QKmsDevice::OrderedScreen::OrderedScreen(QPlatformScreen *screen,
163 const QKmsDevice::ScreenInfo &vinfo)
165{
166}
167
168QDebug operator<<(QDebug dbg, const QPlatformScreen *screen)
169{
170 QDebugStateSaver saver(dbg);
171 dbg.nospace() << "QPlatformScreen=" << (const void *)screen << " ("
172 << (screen ? screen->name() : QString()) << ")";
173 return dbg;
174}
175
176QDebug operator<<(QDebug dbg, const QKmsDevice::OrderedScreen &s)
177{
178 QDebugStateSaver saver(dbg);
179 dbg.nospace() << "OrderedScreen(" << s.screen << ") : " << s.vinfo.virtualIndex << " / "
180 << s.vinfo.virtualPos << " / primary: " << s.vinfo.isPrimary << ")";
181 return dbg;
182}
183
184bool QKmsDevice::createScreenInfoForConnector(drmModeResPtr resources,
185 drmModeConnectorPtr connector, ScreenInfo &vinfo)
186{
187 const QByteArray connectorName = nameForConnector(connector);
188
189 const int crtc = crtcForConnector(resources, connector);
190 if (crtc < 0) {
191 qWarning() << "No usable crtc/encoder pair for connector" << connectorName;
192 return false;
193 }
194
195 OutputConfiguration configuration;
196 QSize configurationSize;
197 int configurationRefresh = 0;
198 drmModeModeInfo configurationModeline;
199
200 auto userConfig = m_screenConfig->outputSettings();
201 QVariantMap userConnectorConfig = userConfig.value(QString::fromUtf8(connectorName));
202 // default to the preferred mode unless overridden in the config
203 const QByteArray mode = userConnectorConfig.value(QStringLiteral("mode"), QStringLiteral("preferred"))
204 .toByteArray().toLower();
205 if (mode == "off") {
206 configuration = OutputConfigOff;
207 } else if (mode == "preferred") {
208 configuration = OutputConfigPreferred;
209 } else if (mode == "current") {
210 configuration = OutputConfigCurrent;
211 } else if (mode == "skip") {
212 configuration = OutputConfigSkip;
213 } else if (sscanf(mode.constData(), "%dx%d@%d", &configurationSize.rwidth(), &configurationSize.rheight(),
214 &configurationRefresh) == 3)
215 {
216 configuration = OutputConfigMode;
217 } else if (sscanf(mode.constData(), "%dx%d", &configurationSize.rwidth(), &configurationSize.rheight()) == 2) {
218 configuration = OutputConfigMode;
219 } else if (parseModeline(mode, &configurationModeline)) {
220 configuration = OutputConfigModeline;
221 } else {
222 qWarning("Invalid mode \"%s\" for output %s", mode.constData(), connectorName.constData());
223 configuration = OutputConfigPreferred;
224 }
225
226 vinfo.virtualIndex = userConnectorConfig.value(QStringLiteral("virtualIndex"), INT_MAX).toInt();
227 if (userConnectorConfig.contains(QStringLiteral("virtualPos"))) {
228 const QByteArray vpos = userConnectorConfig.value(QStringLiteral("virtualPos")).toByteArray();
229 const QByteArrayList vposComp = vpos.split(',');
230 if (vposComp.count() == 2) {
231 vinfo.virtualPos = QPoint(vposComp[0].trimmed().toInt(), vposComp[1].trimmed().toInt());
232 qCDebug(qLcKmsDebug) << "Parsing virtualPos to: " << vinfo.virtualPos;
233 } else {
234 vinfo.virtualPos = QPoint(-1, -1);
235 qCDebug(qLcKmsDebug) << "Could not parse virtualPos,"
236 << "will be calculated based on virtualIndex";
237 }
238 } else {
239 vinfo.virtualPos = QPoint(-1, -1);
240 }
241
242 if (userConnectorConfig.value(QStringLiteral("primary")).toBool())
243 vinfo.isPrimary = true;
244
245 const uint32_t crtc_id = resources->crtcs[crtc];
246
247 if (configuration == OutputConfigOff) {
248 qCDebug(qLcKmsDebug) << "Turning off output" << connectorName;
249 drmModeSetCrtc(m_dri_fd, crtc_id, 0, 0, 0, 0, 0, nullptr);
250 return false;
251 }
252
253 // Skip disconnected output
254 if (configuration == OutputConfigPreferred && connector->connection == DRM_MODE_DISCONNECTED) {
255 qCDebug(qLcKmsDebug) << "Skipping disconnected output" << connectorName;
256 return false;
257 }
258
259 if (configuration == OutputConfigSkip) {
260 qCDebug(qLcKmsDebug) << "Skipping output" << connectorName;
261 return false;
262 }
263
264 // Get the current mode on the current crtc
265 drmModeModeInfo crtc_mode;
266 memset(&crtc_mode, 0, sizeof crtc_mode);
267 if (drmModeEncoderPtr encoder = drmModeGetEncoder(m_dri_fd, connector->encoder_id)) {
268
269 drmModeCrtcPtr crtc = drmModeGetCrtc(m_dri_fd, encoder->crtc_id);
270 drmModeFreeEncoder(encoder);
271
272 if (!crtc)
273 return false;
274
275 if (crtc->mode_valid)
276 crtc_mode = crtc->mode;
277
278 drmModeFreeCrtc(crtc);
279 }
280
281 QList<drmModeModeInfo> modes;
282 modes.reserve(connector->count_modes);
283 qCDebug(qLcKmsDebug) << connectorName << "mode count:" << connector->count_modes
284 << "crtc index:" << crtc << "crtc id:" << crtc_id;
285 for (int i = 0; i < connector->count_modes; i++) {
286 const drmModeModeInfo &mode = connector->modes[i];
287 qCDebug(qLcKmsDebug) << "mode" << i << mode.hdisplay << "x" << mode.vdisplay
288 << '@' << mode.vrefresh << "hz";
289 modes << connector->modes[i];
290 }
291
292 int preferred = -1;
293 int current = -1;
294 int configured = -1;
295 int best = -1;
296
297 for (int i = modes.size() - 1; i >= 0; i--) {
298 const drmModeModeInfo &m = modes.at(i);
299
300 if (configuration == OutputConfigMode
301 && m.hdisplay == configurationSize.width()
302 && m.vdisplay == configurationSize.height()
303 && (!configurationRefresh || m.vrefresh == uint32_t(configurationRefresh)))
304 {
305 configured = i;
306 }
307
308 if (!memcmp(&crtc_mode, &m, sizeof m))
309 current = i;
310
311 if (m.type & DRM_MODE_TYPE_PREFERRED)
312 preferred = i;
313
314 best = i;
315 }
316
317 if (configuration == OutputConfigModeline) {
318 modes << configurationModeline;
319 configured = modes.size() - 1;
320 }
321
322 if (current < 0 && crtc_mode.clock != 0) {
323 modes << crtc_mode;
324 current = modes.size() - 1;
325 }
326
327 if (configuration == OutputConfigCurrent)
328 configured = current;
329
330 int selected_mode = -1;
331
332 if (configured >= 0)
333 selected_mode = configured;
334 else if (preferred >= 0)
335 selected_mode = preferred;
336 else if (current >= 0)
337 selected_mode = current;
338 else if (best >= 0)
339 selected_mode = best;
340
341 if (selected_mode < 0) {
342 qWarning() << "No modes available for output" << connectorName;
343 return false;
344 } else {
345 int width = modes[selected_mode].hdisplay;
346 int height = modes[selected_mode].vdisplay;
347 int refresh = modes[selected_mode].vrefresh;
348 qCDebug(qLcKmsDebug) << "Selected mode" << selected_mode << ":" << width << "x" << height
349 << '@' << refresh << "hz for output" << connectorName;
350 }
351
352 // physical size from connector < config values < env vars
353 int pwidth = qEnvironmentVariableIntValue("QT_QPA_EGLFS_PHYSICAL_WIDTH");
354 if (!pwidth)
355 pwidth = qEnvironmentVariableIntValue("QT_QPA_PHYSICAL_WIDTH");
356 int pheight = qEnvironmentVariableIntValue("QT_QPA_EGLFS_PHYSICAL_HEIGHT");
357 if (!pheight)
358 pheight = qEnvironmentVariableIntValue("QT_QPA_PHYSICAL_HEIGHT");
359 QSizeF physSize(pwidth, pheight);
360 if (physSize.isEmpty()) {
361 physSize = QSize(userConnectorConfig.value(QStringLiteral("physicalWidth")).toInt(),
362 userConnectorConfig.value(QStringLiteral("physicalHeight")).toInt());
363 if (physSize.isEmpty()) {
364 physSize.setWidth(connector->mmWidth);
365 physSize.setHeight(connector->mmHeight);
366 }
367 }
368 qCDebug(qLcKmsDebug) << "Physical size is" << physSize << "mm" << "for output" << connectorName;
369
370 const QByteArray formatStr = userConnectorConfig.value(QStringLiteral("format"), QString())
371 .toByteArray().toLower();
372 uint32_t drmFormat;
373 bool drmFormatExplicit = true;
374 if (formatStr.isEmpty()) {
375 drmFormat = DRM_FORMAT_XRGB8888;
376 drmFormatExplicit = false;
377 } else if (formatStr == "xrgb8888") {
378 drmFormat = DRM_FORMAT_XRGB8888;
379 } else if (formatStr == "xbgr8888") {
380 drmFormat = DRM_FORMAT_XBGR8888;
381 } else if (formatStr == "argb8888") {
382 drmFormat = DRM_FORMAT_ARGB8888;
383 } else if (formatStr == "abgr8888") {
384 drmFormat = DRM_FORMAT_ABGR8888;
385 } else if (formatStr == "rgb565") {
386 drmFormat = DRM_FORMAT_RGB565;
387 } else if (formatStr == "bgr565") {
388 drmFormat = DRM_FORMAT_BGR565;
389 } else if (formatStr == "xrgb2101010") {
390 drmFormat = DRM_FORMAT_XRGB2101010;
391 } else if (formatStr == "xbgr2101010") {
392 drmFormat = DRM_FORMAT_XBGR2101010;
393 } else if (formatStr == "argb2101010") {
394 drmFormat = DRM_FORMAT_ARGB2101010;
395 } else if (formatStr == "abgr2101010") {
396 drmFormat = DRM_FORMAT_ABGR2101010;
397 } else {
398 qWarning("Invalid pixel format \"%s\" for output %s", formatStr.constData(), connectorName.constData());
399 drmFormat = DRM_FORMAT_XRGB8888;
400 drmFormatExplicit = false;
401 }
402 qCDebug(qLcKmsDebug) << "Format is" << Qt::hex << drmFormat << Qt::dec << "requested_by_user =" << drmFormatExplicit
403 << "for output" << connectorName;
404
405 const QString cloneSource = userConnectorConfig.value(QStringLiteral("clones")).toString();
406 if (!cloneSource.isEmpty())
407 qCDebug(qLcKmsDebug) << "Output" << connectorName << " clones output " << cloneSource;
408
409 QSize framebufferSize;
410 bool framebufferSizeSet = false;
411 const QByteArray fbsize = userConnectorConfig.value(QStringLiteral("size")).toByteArray().toLower();
412 if (!fbsize.isEmpty()) {
413 if (sscanf(fbsize.constData(), "%dx%d", &framebufferSize.rwidth(), &framebufferSize.rheight()) == 2) {
414#if QT_CONFIG(drm_atomic)
415 if (hasAtomicSupport())
416 framebufferSizeSet = true;
417#endif
418 if (!framebufferSizeSet)
419 qWarning("Setting framebuffer size is only available with DRM atomic API");
420 } else {
421 qWarning("Invalid framebuffer size '%s'", fbsize.constData());
422 }
423 }
424 if (!framebufferSizeSet) {
425 framebufferSize.setWidth(modes[selected_mode].hdisplay);
426 framebufferSize.setHeight(modes[selected_mode].vdisplay);
427 }
428
429 qCDebug(qLcKmsDebug) << "Output" << connectorName << "framebuffer size is " << framebufferSize;
430
431 QKmsOutput output;
432 output.name = QString::fromUtf8(connectorName);
433 output.connector_id = connector->connector_id;
434 output.crtc_index = crtc;
435 output.crtc_id = crtc_id;
436 output.physical_size = physSize;
437 output.preferred_mode = preferred >= 0 ? preferred : selected_mode;
438 output.mode = selected_mode;
439 output.mode_set = false;
440 output.saved_crtc = drmModeGetCrtc(m_dri_fd, crtc_id);
441 output.modes = modes;
442 output.subpixel = connector->subpixel;
443 output.dpms_prop = connectorProperty(connector, QByteArrayLiteral("DPMS"));
444 output.edid_blob = connectorPropertyBlob(connector, QByteArrayLiteral("EDID"));
445 output.wants_forced_plane = false;
446 output.forced_plane_id = 0;
447 output.forced_plane_set = false;
448 output.drm_format = drmFormat;
449 output.drm_format_requested_by_user = drmFormatExplicit;
450 output.clone_source = cloneSource;
451 output.size = framebufferSize;
452
453#if QT_CONFIG(drm_atomic)
454 if (drmModeCreatePropertyBlob(m_dri_fd, &modes[selected_mode], sizeof(drmModeModeInfo),
455 &output.mode_blob_id) != 0) {
456 qCDebug(qLcKmsDebug) << "Failed to create mode blob for mode" << selected_mode;
457 }
458
459 parseConnectorProperties(output.connector_id, &output);
460 parseCrtcProperties(output.crtc_id, &output);
461#endif
462
463 QString planeListStr;
464 for (QKmsPlane &plane : m_planes) {
465 if (plane.possibleCrtcs & (1 << output.crtc_index)) {
466 output.available_planes.append(plane);
467 planeListStr.append(QString::number(plane.id));
468 planeListStr.append(u' ');
469
470 // Choose the first primary plane that is not already assigned to
471 // another screen's associated crtc.
472 if (!output.eglfs_plane && plane.type == QKmsPlane::PrimaryPlane && !plane.activeCrtcId)
473 assignPlane(&output, &plane);
474 }
475 }
476 qCDebug(qLcKmsDebug, "Output %s can use %d planes: %s",
477 connectorName.constData(), int(output.available_planes.size()), qPrintable(planeListStr));
478
479 // This is for the EGLDevice/EGLStream backend. On some of those devices one
480 // may want to target a pre-configured plane. It is probably useless for
481 // eglfs_kms and others. Do not confuse with generic plane support (available_planes).
482 bool ok;
483 int idx = qEnvironmentVariableIntValue("QT_QPA_EGLFS_KMS_PLANE_INDEX", &ok);
484 if (ok) {
485 drmModePlaneRes *planeResources = drmModeGetPlaneResources(m_dri_fd);
486 if (planeResources) {
487 if (idx >= 0 && idx < int(planeResources->count_planes)) {
488 drmModePlane *plane = drmModeGetPlane(m_dri_fd, planeResources->planes[idx]);
489 if (plane) {
490 output.wants_forced_plane = true;
491 output.forced_plane_id = plane->plane_id;
492 qCDebug(qLcKmsDebug, "Forcing plane index %d, plane id %u (belongs to crtc id %u)",
493 idx, plane->plane_id, plane->crtc_id);
494
495 for (QKmsPlane &kmsplane : m_planes) {
496 if (kmsplane.id == output.forced_plane_id) {
497 assignPlane(&output, &kmsplane);
498 break;
499 }
500 }
501
502 drmModeFreePlane(plane);
503 }
504 } else {
505 qWarning("Invalid plane index %d, must be between 0 and %u", idx, planeResources->count_planes - 1);
506 }
507 }
508 }
509
510 // A more useful version: allows specifying "crtc_id,plane_id:crtc_id,plane_id:..."
511 // in order to allow overriding the plane used for a given crtc.
512 if (qEnvironmentVariableIsSet("QT_QPA_EGLFS_KMS_PLANES_FOR_CRTCS")) {
513 const QString val = qEnvironmentVariable("QT_QPA_EGLFS_KMS_PLANES_FOR_CRTCS");
514 qCDebug(qLcKmsDebug, "crtc_id:plane_id override list: %s", qPrintable(val));
515 const QStringList crtcPlanePairs = val.split(u':');
516 for (const QString &crtcPlanePair : crtcPlanePairs) {
517 const QStringList values = crtcPlanePair.split(u',');
518 if (values.size() == 2 && uint(values[0].toInt()) == output.crtc_id) {
519 uint planeId = values[1].toInt();
520 for (QKmsPlane &kmsplane : m_planes) {
521 if (kmsplane.id == planeId) {
522 assignPlane(&output, &kmsplane);
523 break;
524 }
525 }
526 }
527 }
528 }
529
530 if (output.eglfs_plane) {
531 qCDebug(qLcKmsDebug, "Chose plane %u for output %s (crtc id %u) (may not be applicable)",
532 output.eglfs_plane->id, connectorName.constData(), output.crtc_id);
533 }
534
535#if QT_CONFIG(drm_atomic)
536 if (hasAtomicSupport() && !output.eglfs_plane) {
537 qCDebug(qLcKmsDebug, "No plane associated with output %s (crtc id %u) and atomic modesetting is enabled. This is bad.",
538 connectorName.constData(), output.crtc_id);
539 }
540#endif
541
542 m_crtc_allocator |= (1 << output.crtc_index);
543
544 vinfo.output = output;
545 return true;
546}
547
548drmModePropertyPtr QKmsDevice::connectorProperty(drmModeConnectorPtr connector, const QByteArray &name)
549{
550 drmModePropertyPtr prop;
551
552 for (int i = 0; i < connector->count_props; i++) {
553 prop = drmModeGetProperty(m_dri_fd, connector->props[i]);
554 if (!prop)
555 continue;
556 if (strcmp(prop->name, name.constData()) == 0)
557 return prop;
558 drmModeFreeProperty(prop);
559 }
560
561 return nullptr;
562}
563
564drmModePropertyBlobPtr QKmsDevice::connectorPropertyBlob(drmModeConnectorPtr connector, const QByteArray &name)
565{
566 drmModePropertyPtr prop;
567 drmModePropertyBlobPtr blob = nullptr;
568
569 for (int i = 0; i < connector->count_props && !blob; i++) {
570 prop = drmModeGetProperty(m_dri_fd, connector->props[i]);
571 if (!prop)
572 continue;
573 if ((prop->flags & DRM_MODE_PROP_BLOB) && (strcmp(prop->name, name.constData()) == 0))
574 blob = drmModeGetPropertyBlob(m_dri_fd, connector->prop_values[i]);
575 drmModeFreeProperty(prop);
576 }
577
578 return blob;
579}
580
581QKmsDevice::QKmsDevice(QKmsScreenConfig *screenConfig, const QString &path)
582 : m_screenConfig(screenConfig)
583 , m_path(path)
584 , m_dri_fd(-1)
585 , m_has_atomic_support(false)
587{
588 if (m_path.isEmpty()) {
589 m_path = m_screenConfig->devicePath();
590 qCDebug(qLcKmsDebug, "Using DRM device %s specified in config file", qPrintable(m_path));
591 if (m_path.isEmpty())
592 qFatal("No DRM device given");
593 } else {
594 qCDebug(qLcKmsDebug, "Using backend-provided DRM device %s", qPrintable(m_path));
595 }
596}
597
599{
600#if QT_CONFIG(drm_atomic)
601 threadLocalAtomicReset();
602#endif
603}
604
606{
608 return;
609
610 drmModeResPtr resources = drmModeGetResources(m_dri_fd);
611 if (!resources) {
612 qErrnoWarning(errno, "drmModeGetResources failed");
613 return;
614 }
615
616 QList<uint32_t> newConnects;
617 QList<uint32_t> newDisconnects;
618 const QMap<QString, QVariantMap> userConfig = m_screenConfig->outputSettings();
619
620 for (int i = 0; i < resources->count_connectors; i++) {
621 drmModeConnectorPtr connector = drmModeGetConnector(m_dri_fd, resources->connectors[i]);
622 if (!connector) {
623 qErrnoWarning(errno, "drmModeGetConnector failed");
624 continue;
625 }
626
627 const uint32_t id = connector->connector_id;
628
629 const QByteArray connectorName = nameForConnector(connector);
630 const QVariantMap userCConfig = userConfig.value(QString::fromUtf8(connectorName));
631 const QByteArray mode = userCConfig.value(QStringLiteral("mode")).toByteArray().toLower();
632 if (mode == "off" || mode == "skip")
633 continue;
634
635 if (connector->connection == DRM_MODE_CONNECTED) {
636 if (!m_registeredScreens.contains(id))
637 newConnects.append(id);
638 else
639 qCDebug(qLcKmsDebug) << "Connected screen already registered: connector id=" << id;
640 }
641
642 if (connector->connection == DRM_MODE_DISCONNECTED) {
643 if (m_registeredScreens.contains(id))
644 newDisconnects.append(id);
645 else
646 qCDebug(qLcKmsDebug) << "Disconnected screen not registered: connector id=" << id;
647 }
648
649 drmModeFreeConnector(connector);
650 }
651
652 if (newConnects.isEmpty() && newDisconnects.isEmpty()) {
653 qCDebug(qLcKmsDebug) << "EGLFS/KMS: KMS-device-change but no new connects or disconnects "
654 << "to process - exiting";
655 return;
656 } else {
657 qCDebug(qLcKmsDebug) << "EGLFS/KMS: KMS-device-change, new connects:" << newConnects
658 << ", and disconnected: " << newDisconnects;
659 }
660
661 const int remainingScreenCount = m_registeredScreens.count() - newDisconnects.count();
662 if (remainingScreenCount == 0 && m_headlessScreen == nullptr) {
663 qCDebug(qLcKmsDebug) << "EGLFS/KMS: creating headless screen before"
664 << "unregistering screens to avoid having no screens";
665 m_headlessScreen = createHeadlessScreen();
666 registerScreen(m_headlessScreen, true, QPoint(),
667 QList<QPlatformScreen *>() << m_headlessScreen);
668 }
669
670 for (uint32_t connectorId : newDisconnects) {
671 OrderedScreen orderedScreen = m_registeredScreens.take(connectorId);
672 QPlatformScreen *screen = orderedScreen.screen;
673
674 // Clear active crtc of the plane associated with the screen output
675 // and, if applicable, disassociate it from the eglfs plane.
676 uint32_t crtcId = (orderedScreen.vinfo.output.eglfs_plane != nullptr) // if we have an assigned plan
677 ? orderedScreen.vinfo.output.eglfs_plane->activeCrtcId // we use the active crtc_id to disable everything
678 : orderedScreen.vinfo.output.crtc_id; // if not, we use the default crtc_id
679
680 if (orderedScreen.vinfo.output.eglfs_plane != nullptr)
681 orderedScreen.vinfo.output.eglfs_plane->activeCrtcId = 0;
682
683 // Clear crtc allocator bit for screen
684 const int crtcIdx = orderedScreen.vinfo.output.crtc_index;
685 m_crtc_allocator &= ~(1 << crtcIdx);
686
687 const int ret = drmModeSetCrtc(m_dri_fd, crtcId, 0, 0, 0, nullptr, 0, nullptr);
688
689 if (ret != 0) {
690 qCWarning(qLcKmsDebug) << "Could not disable CRTC" << crtcId
691 << "on connector" << connectorId << "removal:" << ret;
692 } else {
693 qCDebug(qLcKmsDebug) << "Disabled CRTC" << crtcId
694 << "for connector " << connectorId << "disconnected";
695 }
696
697 // As we've already turned the crtc off, we don't want to restore the saved_crtc
698 if (orderedScreen.vinfo.output.saved_crtc) {
699 drmModeFreeCrtc(orderedScreen.vinfo.output.saved_crtc);
700 orderedScreen.vinfo.output.saved_crtc = nullptr;
701 updateScreenOutput(orderedScreen.screen, orderedScreen.vinfo.output);
702 }
703
704 unregisterScreen(screen);
705 }
706
707 for (uint32_t connectorId : newConnects) {
708 drmModeConnectorPtr connector = drmModeGetConnector(m_dri_fd, connectorId);
709 if (!connector) {
710 qErrnoWarning(errno, "drmModeGetConnector failed");
711 continue;
712 }
713
714 ScreenInfo vinfo;
715 bool succ = createScreenInfoForConnector(resources, connector, vinfo);
716 drmModeFreeConnector(connector);
717 if (!succ)
718 continue;
719
720 QPlatformScreen *screen = createScreen(vinfo.output);
721 if (!screen)
722 continue;
723
724 OrderedScreen orderedScreen(screen, vinfo);
725 m_registeredScreens[connectorId] = orderedScreen;
726 }
727
728 drmModeFreeResources(resources);
729
730 registerScreens(newConnects);
731}
732
734{
736 return;
737
738 drmModeResPtr resources = drmModeGetResources(m_dri_fd);
739 if (!resources) {
740 qErrnoWarning(errno, "drmModeGetResources failed");
741 return;
742 }
743
744 QList<uint32_t> newConnects;
745 QList<OrderedScreen> newDisconnects;
746
747 for (int i = 0; i < resources->count_connectors; i++) {
748 drmModeConnectorPtr connector = drmModeGetConnector(m_dri_fd, resources->connectors[i]);
749 if (!connector)
750 continue;
751
752 if (m_registeredScreens.contains(connector->connector_id)) {
753 OrderedScreen &os = m_registeredScreens[connector->connector_id];
754
755 // As we're currently *re*creating the information of an used connector,
756 // we have to "fake" it being not in use at two places:
757 // (note: the only thing we'll restore is, in case of failure, the eglfs_plane
758 // probably not necessary but good practice)
759
760 // 1) crtc_allocator for the crtc
761 const int crtcIdx = os.vinfo.output.crtc_index;
762 m_crtc_allocator &= ~(1 << crtcIdx);
763
764 // 2) the plane itself
765 if (os.vinfo.output.eglfs_plane)
766 os.vinfo.output.eglfs_plane->activeCrtcId = 0;
767
768 // We also save the saved crtc to restore it in case of success
769 // (otherwise QKmsOutput::restoreMode would restore to a second-latest crtc,
770 // rather then the original one)
771 drmModeCrtcPtr saved_saved_crtc = nullptr;
772 if (os.vinfo.output.saved_crtc)
773 saved_saved_crtc = os.vinfo.output.saved_crtc;
774
775 ScreenInfo vinfo;
776 bool succ = createScreenInfoForConnector(resources, connector, vinfo);
777 if (!succ) {
778 // Here we either failed the recreate, or the config turns the screen off.
779 // In either case, we'll treat it as a disconnect
780
781 // Either this connector is disconnected, broken or turned off
782 // In all those cases we don't need or want to restore the previous mode
783 if (os.vinfo.output.saved_crtc) {
784 drmModeFreeCrtc(os.vinfo.output.saved_crtc);
785 os.vinfo.output.saved_crtc = nullptr;
786 updateScreenOutput(os.screen, os.vinfo.output);
787 }
788
789 // move from one container to another - we don't want registerScreens
790 // to deal with this, but need to call registerScreens before the disconnects
791 newDisconnects.append(m_registeredScreens.take(connector->connector_id));
792 drmModeFreeConnector(connector);
793 continue;
794 }
795 drmModeFreeConnector(connector);
796
797 drmModeFreeCrtc(vinfo.output.saved_crtc);
798 vinfo.output.saved_crtc = saved_saved_crtc; // This is vital as config changes should
799 // never override the original saved_crtc
800 os.vinfo = vinfo;
801 updateScreenOutput(os.screen, os.vinfo.output);
802
803 } else {
804 ScreenInfo vinfo;
805 bool succ = createScreenInfoForConnector(resources, connector, vinfo);
806 if (!succ) // If we fail here we do nothing, as there is nothing to restore or cleanup
807 continue;
808
809 QPlatformScreen *screen = createScreen(vinfo.output);
810 OrderedScreen orderedScreen(screen, vinfo);
811 m_registeredScreens[connector->connector_id] = orderedScreen;
812 newConnects.append(connector->connector_id);
813 }
814 }
815
816 // In case we end up with zero screen, we do the fallback first
817 if (m_registeredScreens.count() == 0 && m_headlessScreen == nullptr) {
818 // Create headless screen before unregistering screens to avoid having no screens
819 m_headlessScreen = createHeadlessScreen();
820 registerScreen(m_headlessScreen, true, QPoint(),
821 QList<QPlatformScreen *>() << m_headlessScreen);
822 }
823
824 // Register new and updates existing screens
825 registerScreens(newConnects);
826
827 // Last we unregister the disconncted ones
828 for (const OrderedScreen &os : newDisconnects)
829 unregisterScreen(os.screen);
830
831 drmModeFreeResources(resources);
832}
833
835{
836 // Headless mode using a render node: cannot do any output related DRM
837 // stuff. Skip it all and register a dummy screen.
839 QPlatformScreen *screen = createHeadlessScreen();
840 if (screen) {
841 qCDebug(qLcKmsDebug, "Headless mode enabled");
842 registerScreen(screen, true, QPoint(0, 0),
843 QList<QPlatformScreen *>() << screen);
844 return;
845 } else {
846 qWarning("QKmsDevice: Requested headless mode without support in the backend. Request is ignored.");
847 }
848 }
849
850 drmSetClientCap(m_dri_fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
851
852#if QT_CONFIG(drm_atomic)
853 // check atomic support
854 m_has_atomic_support = !drmSetClientCap(m_dri_fd, DRM_CLIENT_CAP_ATOMIC, 1);
855 if (m_has_atomic_support) {
856 qCDebug(qLcKmsDebug, "Atomic reported as supported");
857 if (qEnvironmentVariableIntValue("QT_QPA_EGLFS_KMS_ATOMIC")) {
858 qCDebug(qLcKmsDebug, "Atomic enabled");
859 } else {
860 qCDebug(qLcKmsDebug, "Atomic disabled");
861 m_has_atomic_support = false;
862 }
863 }
864#endif
865
866 drmModeResPtr resources = drmModeGetResources(m_dri_fd);
867 if (!resources) {
868 qErrnoWarning(errno, "drmModeGetResources failed");
869 return;
870 }
871
873
874 QList<uint32_t> newConnects;
875 int wantedConnectorIndex = -1;
876 bool ok;
877 int idx = qEnvironmentVariableIntValue("QT_QPA_EGLFS_KMS_CONNECTOR_INDEX", &ok);
878 if (ok) {
879 if (idx >= 0 && idx < resources->count_connectors)
880 wantedConnectorIndex = idx;
881 else
882 qWarning("Invalid connector index %d, must be between 0 and %u", idx, resources->count_connectors - 1);
883 }
884
885 for (int i = 0; i < resources->count_connectors; i++) {
886 if (wantedConnectorIndex >= 0 && i != wantedConnectorIndex)
887 continue;
888
889 drmModeConnectorPtr connector = drmModeGetConnector(m_dri_fd, resources->connectors[i]);
890 if (!connector) {
891 qErrnoWarning(errno, "drmModeGetConnector failed");
892 continue;
893 }
894
895 ScreenInfo vinfo;
896 bool succ = createScreenInfoForConnector(resources, connector, vinfo);
897 uint32_t connectorId = connector->connector_id;
898 drmModeFreeConnector(connector);
899 if (!succ)
900 continue;
901
902 QPlatformScreen *screen = createScreen(vinfo.output);
903 if (!screen)
904 continue;
905
906 OrderedScreen orderedScreen(screen, vinfo);
907 m_registeredScreens[connectorId] = orderedScreen;
908 newConnects.append(connectorId);
909 }
910
911 drmModeFreeResources(resources);
912
913 if (!qEnvironmentVariable("QT_QPA_EGLFS_HOTPLUG_ENABLED").isEmpty()
914 && newConnects.empty() && m_headlessScreen == nullptr) {
915 qCDebug(qLcKmsDebug) << "'QT_QPA_EGLFS_HOTPLUG_ENABLED' was set and no screen was connected/found during start-up."
916 << "In order for Qt to operate properly a qt_headless screen will be created."
917 << "It will be automatically removed as soon as the first screen is connected";
918 // Create headless screen before unregistering screens to avoid having no screens
919 m_headlessScreen = createHeadlessScreen();
920 registerScreen(m_headlessScreen, true, QPoint(),
921 QList<QPlatformScreen *>() << m_headlessScreen);
922 }
923
924 registerScreens(newConnects);
925}
926
927void QKmsDevice::registerScreens(QList<uint32_t> newConnects)
928{
929 QList<OrderedScreen> screens = m_registeredScreens.values();
930
931 // Use stable sort to preserve the original (DRM connector) order
932 // for outputs with unspecified indices.
933 std::stable_sort(screens.begin(), screens.end(), orderedScreenLessThan);
934 qCDebug(qLcKmsDebug) << "Sorted screen list:" << screens;
935
936 // The final list of screens is available, so do the second phase setup.
937 // Hook up clone sources and targets.
938 for (const OrderedScreen &orderedScreen : screens) {
939 QList<QPlatformScreen *> screensCloningThisScreen;
940 for (const OrderedScreen &s : screens) {
941 if (s.vinfo.output.clone_source == orderedScreen.vinfo.output.name)
942 screensCloningThisScreen.append(s.screen);
943 }
944 QPlatformScreen *screenThisScreenClones = nullptr;
945 if (!orderedScreen.vinfo.output.clone_source.isEmpty()) {
946 for (const OrderedScreen &s : screens) {
947 if (s.vinfo.output.name == orderedScreen.vinfo.output.clone_source) {
948 screenThisScreenClones = s.screen;
949 break;
950 }
951 }
952 }
953 if (screenThisScreenClones)
954 qCDebug(qLcKmsDebug) << orderedScreen.screen->name() << "clones" << screenThisScreenClones;
955 if (!screensCloningThisScreen.isEmpty())
956 qCDebug(qLcKmsDebug) << orderedScreen.screen->name() << "is cloned by" << screensCloningThisScreen;
957
958 registerScreenCloning(orderedScreen.screen, screenThisScreenClones, screensCloningThisScreen);
959 }
960
961 // Figure out the virtual desktop and register the screens to QPA/QGuiApplication.
962 QPoint pos(0, 0);
963 QList<OrderedScreen> siblings;
964 QList<QPoint> virtualPositions;
965 int primarySiblingIdx = -1;
966 QRegion deskRegion;
967
968 for (const OrderedScreen &orderedScreen : screens) {
969 QPlatformScreen *s = orderedScreen.screen;
970 QPoint virtualPos(0, 0);
971 // set up a horizontal or vertical virtual desktop
972 if (orderedScreen.vinfo.virtualPos.x() == -1 || orderedScreen.vinfo.virtualPos.y() == -1) {
973 if (orderedScreen.vinfo.output.clone_source.isEmpty()) {
974 virtualPos = pos;
975 if (m_screenConfig->virtualDesktopLayout() == QKmsScreenConfig::VirtualDesktopLayoutVertical)
976 pos.ry() += s->geometry().height();
977 else
978 pos.rx() += s->geometry().width();
979 } else {
980 for (int i = 0; i < screens.count(); i++) {
981 const OrderedScreen &os = screens[i];
982 if (os.vinfo.output.name == orderedScreen.vinfo.output.clone_source) {
983 if (i >= virtualPositions.count()) {
984 qCWarning(qLcKmsDebug)
985 << "WARNING: When using clone on kms config,"
986 << "you have to either order your screens (virtualIndex),"
987 << "so clones come after their source,"
988 << "or specify 'virtualPos' for each clone."
989 << "Otherwise desktop-geomerty might not work properly!";
990 virtualPos = pos;
991 } else {
992 virtualPos = virtualPositions[i];
993 }
994 break;
995 }
996 }
997 }
998 } else {
999 virtualPos = orderedScreen.vinfo.virtualPos;
1000 }
1001
1002 // The order in qguiapp's screens list will match the order set by
1003 // virtualIndex. This is not only handy but also required since for instance
1004 // evdevtouch relies on it when performing touch device - screen mapping.
1005 if (!m_screenConfig->separateScreens()) {
1006 siblings.append(orderedScreen);
1007 virtualPositions.append(virtualPos);
1008 if (orderedScreen.vinfo.isPrimary)
1009 primarySiblingIdx = siblings.size() - 1;
1010 } else {
1011 const bool isNewScreen = newConnects.contains(orderedScreen.vinfo.output.connector_id);
1012 if (isNewScreen) {
1013 qCDebug(qLcKmsDebug) << "Adding QPlatformScreen" << s << "(" << s->name() << ")"
1014 << "to QPA with geometry" << s->geometry()
1015 << ", virtual position" << virtualPos
1016 << "and isPrimary=" << orderedScreen.vinfo.isPrimary;
1017 registerScreen(s, orderedScreen.vinfo.isPrimary, virtualPos,
1018 QList<QPlatformScreen *>() << s);
1019 deskRegion += s->geometry();
1020 } else {
1021 qCDebug(qLcKmsDebug) << "Updating QPlatformScreen" << s << "(" << s->name() << ")"
1022 << "to QPA with geometry" << s->geometry()
1023 << ", virtual position" << virtualPos
1024 << "and isPrimary=" << orderedScreen.vinfo.isPrimary;
1025 updateScreen(s, virtualPos, QList<QPlatformScreen *>() << s);
1026 deskRegion += s->geometry();
1027 }
1028 }
1029 }
1030
1032 QList<QPlatformScreen *> platformScreenSiblings;
1033 for (int i = 0; i < siblings.count(); ++i) {
1034 platformScreenSiblings.append(siblings[i].screen);
1035 }
1036
1037 // enable the virtual desktop
1038 for (int i = 0; i < siblings.count(); ++i) {
1039 QPlatformScreen *screen = platformScreenSiblings[i];
1040 const OrderedScreen &orderedScreen = siblings[i];
1041 const bool isNewScreen = newConnects.contains(orderedScreen.vinfo.output.connector_id);
1042 if (isNewScreen) {
1043 qCDebug(qLcKmsDebug) << "Adding QPlatformScreen" << screen
1044 << "(" << screen->name() << ")"
1045 << "to QPA with geometry" << screen->geometry()
1046 << ", virtual position" << virtualPositions[i]
1047 << "and isPrimary=" << orderedScreen.vinfo.isPrimary;
1048 registerScreen(screen, i == primarySiblingIdx, virtualPositions[i],
1049 platformScreenSiblings);
1050 deskRegion += screen->geometry();
1051 } else {
1052 qCDebug(qLcKmsDebug) << "Updating QPlatformScreen" << screen
1053 << "(" << screen->name() << ")"
1054 << "to QPA with geometry" << screen->geometry()
1055 << ", virtual position" << virtualPositions[i]
1056 << "and isPrimary=" << orderedScreen.vinfo.isPrimary;
1057 updateScreen(screen, virtualPositions[i], platformScreenSiblings);
1058 deskRegion += screen->geometry();
1059 }
1060 }
1061 }
1062
1063 // Remove headless screen if other screens have become available
1064 if (!m_registeredScreens.empty() && m_headlessScreen) {
1065 unregisterScreen(m_headlessScreen);
1066 m_headlessScreen = nullptr;
1067 }
1068
1069 // Due to layout changes it's possible that we have to reset/bound
1070 // the cursor into the available space (otherwise the cursor might vanish)
1071 QPoint currCPos = QCursor::pos();
1072 if (!deskRegion.contains(currCPos)) {
1073
1074 // We try boudingRect first
1075 QRect deskRect = deskRegion.boundingRect();
1076 currCPos.setX(qMin(currCPos.x(), deskRect.width()) - 1);
1077 currCPos.setY(qMin(currCPos.y(), deskRect.height()) - 1);
1078
1079 // If boudingRect isn't good enough, we go to 0
1080 if (!deskRegion.contains(currCPos))
1081 currCPos = QPoint(0,0);
1082
1083 qCDebug(qLcKmsDebug) << "Due to desktop layout change, overriding cursor pos."
1084 << "Is: " << QCursor::pos() << ", will be: " << currCPos;
1085 QCursor::setPos(currCPos);
1086 }
1087}
1088
1090{
1091 // headless mode not supported by default
1092 return nullptr;
1093}
1094
1095// not all subclasses support screen cloning
1096void QKmsDevice::registerScreenCloning(QPlatformScreen *screen,
1097 QPlatformScreen *screenThisScreenClones,
1098 const QList<QPlatformScreen *> &screensCloningThisScreen)
1099{
1100 Q_UNUSED(screen);
1101 Q_UNUSED(screenThisScreenClones);
1102 Q_UNUSED(screensCloningThisScreen);
1103}
1104
1105void QKmsDevice::unregisterScreen(QPlatformScreen *screen)
1106{
1107 Q_UNUSED(screen);
1108}
1109
1110void QKmsDevice::updateScreen(QPlatformScreen *screen, const QPoint &virtualPos,
1111 const QList<QPlatformScreen *> &virtualSiblings)
1112{
1113 Q_UNUSED(screen);
1114 Q_UNUSED(virtualPos);
1115 Q_UNUSED(virtualSiblings);
1116}
1117
1118void QKmsDevice::updateScreenOutput(QPlatformScreen *screen, const QKmsOutput &output)
1119{
1120 Q_UNUSED(screen);
1121 Q_UNUSED(output);
1122}
1123
1124// drm_property_type_is is not available in old headers
1125static inline bool propTypeIs(drmModePropertyPtr prop, uint32_t type)
1126{
1127 if (prop->flags & DRM_MODE_PROP_EXTENDED_TYPE)
1128 return (prop->flags & DRM_MODE_PROP_EXTENDED_TYPE) == type;
1129 return prop->flags & type;
1130}
1131
1132void QKmsDevice::enumerateProperties(drmModeObjectPropertiesPtr objProps, PropCallback callback)
1133{
1134 for (uint32_t propIdx = 0; propIdx < objProps->count_props; ++propIdx) {
1135 drmModePropertyPtr prop = drmModeGetProperty(m_dri_fd, objProps->props[propIdx]);
1136 if (!prop)
1137 continue;
1138
1139 const quint64 value = objProps->prop_values[propIdx];
1140 qCDebug(qLcKmsDebug, " property %d: id = %u name = '%s'", propIdx, prop->prop_id, prop->name);
1141
1143 qCDebug(qLcKmsDebug, " type is SIGNED_RANGE, value is %lld, possible values are:", qint64(value));
1144 for (int i = 0; i < prop->count_values; ++i)
1145 qCDebug(qLcKmsDebug, " %lld", qint64(prop->values[i]));
1146 } else if (propTypeIs(prop, DRM_MODE_PROP_RANGE)) {
1147 qCDebug(qLcKmsDebug, " type is RANGE, value is %llu, possible values are:", value);
1148 for (int i = 0; i < prop->count_values; ++i)
1149 qCDebug(qLcKmsDebug, " %llu", quint64(prop->values[i]));
1150 } else if (propTypeIs(prop, DRM_MODE_PROP_ENUM)) {
1151 qCDebug(qLcKmsDebug, " type is ENUM, value is %llu, possible values are:", value);
1152 for (int i = 0; i < prop->count_enums; ++i)
1153 qCDebug(qLcKmsDebug, " enum %d: %s - %llu", i, prop->enums[i].name, quint64(prop->enums[i].value));
1154 } else if (propTypeIs(prop, DRM_MODE_PROP_BITMASK)) {
1155 qCDebug(qLcKmsDebug, " type is BITMASK, value is %llu, possible bits are:", value);
1156 for (int i = 0; i < prop->count_enums; ++i)
1157 qCDebug(qLcKmsDebug, " bitmask %d: %s - %u", i, prop->enums[i].name, 1 << prop->enums[i].value);
1158 } else if (propTypeIs(prop, DRM_MODE_PROP_BLOB)) {
1159 qCDebug(qLcKmsDebug, " type is BLOB");
1160 } else if (propTypeIs(prop, DRM_MODE_PROP_OBJECT)) {
1161 qCDebug(qLcKmsDebug, " type is OBJECT");
1162 }
1163
1164 callback(prop, value);
1165
1166 drmModeFreeProperty(prop);
1167 }
1168}
1169
1171{
1172 m_planes.clear();
1173
1174 drmModePlaneResPtr planeResources = drmModeGetPlaneResources(m_dri_fd);
1175 if (!planeResources)
1176 return;
1177
1178 const int countPlanes = planeResources->count_planes;
1179 qCDebug(qLcKmsDebug, "Found %d planes", countPlanes);
1180 for (int planeIdx = 0; planeIdx < countPlanes; ++planeIdx) {
1181 drmModePlanePtr drmplane = drmModeGetPlane(m_dri_fd, planeResources->planes[planeIdx]);
1182 if (!drmplane) {
1183 qCDebug(qLcKmsDebug, "Failed to query plane %d, ignoring", planeIdx);
1184 continue;
1185 }
1186
1187 QKmsPlane plane;
1188 plane.id = drmplane->plane_id;
1189 plane.possibleCrtcs = drmplane->possible_crtcs;
1190
1191 const int countFormats = drmplane->count_formats;
1192 QString formatStr;
1193 for (int i = 0; i < countFormats; ++i) {
1194 uint32_t f = drmplane->formats[i];
1195 plane.supportedFormats.append(f);
1196 formatStr += QString::asprintf("%c%c%c%c ", f, f >> 8, f >> 16, f >> 24);
1197 }
1198
1199 qCDebug(qLcKmsDebug, "plane %d: id = %u countFormats = %d possibleCrtcs = 0x%x supported formats = %s",
1200 planeIdx, plane.id, countFormats, plane.possibleCrtcs, qPrintable(formatStr));
1201
1202 drmModeFreePlane(drmplane);
1203
1204 drmModeObjectPropertiesPtr objProps = drmModeObjectGetProperties(m_dri_fd, plane.id, DRM_MODE_OBJECT_PLANE);
1205 if (!objProps) {
1206 qCDebug(qLcKmsDebug, "Failed to query plane %d object properties, ignoring", planeIdx);
1207 continue;
1208 }
1209
1210 enumerateProperties(objProps, [&plane](drmModePropertyPtr prop, quint64 value) {
1211 if (!strcmp(prop->name, "type")) {
1212 plane.type = QKmsPlane::Type(value);
1213 } else if (!strcmp(prop->name, "rotation")) {
1214 plane.initialRotation = QKmsPlane::Rotations(int(value));
1215 plane.availableRotations = { };
1216 if (propTypeIs(prop, DRM_MODE_PROP_BITMASK)) {
1217 for (int i = 0; i < prop->count_enums; ++i)
1218 plane.availableRotations |= QKmsPlane::Rotation(1 << prop->enums[i].value);
1219 }
1220 plane.rotationPropertyId = prop->prop_id;
1221 } else if (!strcasecmp(prop->name, "crtc_id")) {
1222 plane.crtcPropertyId = prop->prop_id;
1223 } else if (!strcasecmp(prop->name, "fb_id")) {
1224 plane.framebufferPropertyId = prop->prop_id;
1225 } else if (!strcasecmp(prop->name, "src_w")) {
1226 plane.srcwidthPropertyId = prop->prop_id;
1227 } else if (!strcasecmp(prop->name, "src_h")) {
1228 plane.srcheightPropertyId = prop->prop_id;
1229 } else if (!strcasecmp(prop->name, "crtc_w")) {
1230 plane.crtcwidthPropertyId = prop->prop_id;
1231 } else if (!strcasecmp(prop->name, "crtc_h")) {
1232 plane.crtcheightPropertyId = prop->prop_id;
1233 } else if (!strcasecmp(prop->name, "src_x")) {
1234 plane.srcXPropertyId = prop->prop_id;
1235 } else if (!strcasecmp(prop->name, "src_y")) {
1236 plane.srcYPropertyId = prop->prop_id;
1237 } else if (!strcasecmp(prop->name, "crtc_x")) {
1238 plane.crtcXPropertyId = prop->prop_id;
1239 } else if (!strcasecmp(prop->name, "crtc_y")) {
1240 plane.crtcYPropertyId = prop->prop_id;
1241 } else if (!strcasecmp(prop->name, "zpos")) {
1242 plane.zposPropertyId = prop->prop_id;
1243 } else if (!strcasecmp(prop->name, "blend_op")) {
1244 plane.blendOpPropertyId = prop->prop_id;
1245 }
1246 });
1247
1248 m_planes.append(plane);
1249
1250 drmModeFreeObjectProperties(objProps);
1251 }
1252
1253 drmModeFreePlaneResources(planeResources);
1254}
1255
1256int QKmsDevice::fd() const
1257{
1258 return m_dri_fd;
1259}
1260
1262{
1263 return m_path;
1264}
1265
1266void QKmsDevice::setFd(int fd)
1267{
1268 m_dri_fd = fd;
1269}
1270
1271
1273{
1274 return m_has_atomic_support;
1275}
1276
1277#if QT_CONFIG(drm_atomic)
1278drmModeAtomicReq *QKmsDevice::threadLocalAtomicRequest()
1279{
1280 if (!m_has_atomic_support)
1281 return nullptr;
1282
1283 AtomicReqs &a(m_atomicReqs.localData());
1284 if (!a.request)
1285 a.request = drmModeAtomicAlloc();
1286
1287 return a.request;
1288}
1289
1290bool QKmsDevice::threadLocalAtomicCommit(void *user_data)
1291{
1292 if (!m_has_atomic_support)
1293 return false;
1294
1295 AtomicReqs &a(m_atomicReqs.localData());
1296 if (!a.request)
1297 return false;
1298
1299 int ret = drmModeAtomicCommit(m_dri_fd, a.request,
1300 DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_ALLOW_MODESET,
1301 user_data);
1302
1303 if (ret) {
1304 qWarning("Failed to commit atomic request (code=%d)", ret);
1305 return false;
1306 }
1307
1308 a.previous_request = a.request;
1309 a.request = nullptr;
1310
1311 return true;
1312}
1313
1314void QKmsDevice::threadLocalAtomicReset()
1315{
1316 if (!m_has_atomic_support)
1317 return;
1318
1319 AtomicReqs &a(m_atomicReqs.localData());
1320 if (a.previous_request) {
1321 drmModeAtomicFree(a.previous_request);
1322 a.previous_request = nullptr;
1323 }
1324}
1325#endif
1326
1327void QKmsDevice::parseConnectorProperties(uint32_t connectorId, QKmsOutput *output)
1328{
1329 drmModeObjectPropertiesPtr objProps = drmModeObjectGetProperties(m_dri_fd, connectorId, DRM_MODE_OBJECT_CONNECTOR);
1330 if (!objProps) {
1331 qCDebug(qLcKmsDebug, "Failed to query connector %d object properties", connectorId);
1332 return;
1333 }
1334
1335 enumerateProperties(objProps, [output](drmModePropertyPtr prop, quint64 value) {
1336 Q_UNUSED(value);
1337 if (!strcasecmp(prop->name, "crtc_id"))
1338 output->crtcIdPropertyId = prop->prop_id;
1339 });
1340
1341 drmModeFreeObjectProperties(objProps);
1342}
1343
1344void QKmsDevice::parseCrtcProperties(uint32_t crtcId, QKmsOutput *output)
1345{
1346 drmModeObjectPropertiesPtr objProps = drmModeObjectGetProperties(m_dri_fd, crtcId, DRM_MODE_OBJECT_CRTC);
1347 if (!objProps) {
1348 qCDebug(qLcKmsDebug, "Failed to query crtc %d object properties", crtcId);
1349 return;
1350 }
1351
1352 enumerateProperties(objProps, [output](drmModePropertyPtr prop, quint64 value) {
1353 Q_UNUSED(value);
1354 if (!strcasecmp(prop->name, "mode_id"))
1355 output->modeIdPropertyId = prop->prop_id;
1356 else if (!strcasecmp(prop->name, "active"))
1357 output->activePropertyId = prop->prop_id;
1358 });
1359
1360 drmModeFreeObjectProperties(objProps);
1361}
1362
1364{
1365 return m_screenConfig;
1366}
1367
1376
1378{
1379 m_outputSettings.clear();
1381}
1382
1384{
1385 QByteArray json = qgetenv("QT_QPA_EGLFS_KMS_CONFIG");
1386 if (json.isEmpty()) {
1387 json = qgetenv("QT_QPA_KMS_CONFIG");
1388 if (json.isEmpty())
1389 return;
1390 }
1391
1392 qCDebug(qLcKmsDebug) << "Loading KMS setup from" << json;
1393
1394 QFile file(QString::fromUtf8(json));
1395 if (!file.open(QFile::ReadOnly)) {
1396 qCWarning(qLcKmsDebug) << "Could not open config file"
1397 << json << "for reading";
1398 return;
1399 }
1400
1401 const QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
1402 if (!doc.isObject()) {
1403 qCWarning(qLcKmsDebug) << "Invalid config file" << json
1404 << "- no top-level JSON object";
1405 return;
1406 }
1407
1408 const QJsonObject object = doc.object();
1409
1410 const QString headlessStr = object.value("headless"_L1).toString();
1411 const QByteArray headless = headlessStr.toUtf8();
1412 QSize headlessSize;
1413 if (sscanf(headless.constData(), "%dx%d", &headlessSize.rwidth(), &headlessSize.rheight()) == 2) {
1414 m_headless = true;
1415 m_headlessSize = headlessSize;
1416 } else {
1417 m_headless = false;
1418 }
1419
1420 const QString headlessSizeStr = object.value(QLatin1String("headlessSize")).toString();
1421 if (sscanf(headlessSizeStr.toUtf8().constData(), "%dx%d", &headlessSize.rwidth(),
1422 &headlessSize.rheight()) == 2)
1423 m_headlessSize = headlessSize;
1424
1425 m_hwCursor = object.value("hwcursor"_L1).toBool(m_hwCursor);
1426 m_pbuffers = object.value("pbuffers"_L1).toBool(m_pbuffers);
1427 m_devicePath = object.value("device"_L1).toString();
1428 m_separateScreens = object.value("separateScreens"_L1).toBool(m_separateScreens);
1429
1430 const auto vdOriString = object.value("virtualDesktopLayout"_L1).toStringView();
1431 if (!vdOriString.isEmpty()) {
1432 if (vdOriString == "horizontal"_L1)
1434 else if (vdOriString == "vertical"_L1)
1436 else
1437 qCWarning(qLcKmsDebug, "Unknown virtualDesktopOrientation value %ls",
1438 qUtf16Printable(vdOriString.toString()));
1439 }
1440
1441 const QJsonArray outputs = object.value("outputs"_L1).toArray();
1442 for (int i = 0; i < outputs.size(); i++) {
1443 const QVariantMap outputSettings = outputs.at(i).toObject().toVariantMap();
1444
1445 if (outputSettings.contains(QStringLiteral("name"))) {
1446 const QString name = outputSettings.value(QStringLiteral("name")).toString();
1447
1448 if (m_outputSettings.contains(name)) {
1449 qCDebug(qLcKmsDebug) << "Output" << name << "configured multiple times!";
1450 }
1451
1452 m_outputSettings.insert(name, outputSettings);
1453 }
1454 }
1455
1456 qCDebug(qLcKmsDebug) << "Requested configuration (some settings may be ignored):\n"
1457 << "\theadless:" << m_headless << "\n"
1458 << "\thwcursor:" << m_hwCursor << "\n"
1459 << "\tpbuffers:" << m_pbuffers << "\n"
1460 << "\tseparateScreens:" << m_separateScreens << "\n"
1461 << "\tvirtualDesktopLayout:" << m_virtualDesktopLayout << "\n"
1462 << "\toutputs:" << m_outputSettings;
1463}
1464
1466{
1467 if (mode_set && saved_crtc) {
1468 drmModeSetCrtc(device->fd(),
1469 saved_crtc->crtc_id,
1470 saved_crtc->buffer_id,
1471 0, 0,
1472 &connector_id, 1,
1473 &saved_crtc->mode);
1474 mode_set = false;
1475 }
1476}
1477
1479{
1480 if (dpms_prop) {
1481 drmModeFreeProperty(dpms_prop);
1482 dpms_prop = nullptr;
1483 }
1484
1485 if (edid_blob) {
1486 drmModeFreePropertyBlob(edid_blob);
1487 edid_blob = nullptr;
1488 }
1489
1490 restoreMode(device);
1491
1492 if (saved_crtc) {
1493 drmModeFreeCrtc(saved_crtc);
1494 saved_crtc = nullptr;
1495 }
1496}
1497
1499{
1500 switch (subpixel) {
1501 default:
1502 case DRM_MODE_SUBPIXEL_UNKNOWN:
1503 case DRM_MODE_SUBPIXEL_NONE:
1504 return QPlatformScreen::Subpixel_None;
1505 case DRM_MODE_SUBPIXEL_HORIZONTAL_RGB:
1506 return QPlatformScreen::Subpixel_RGB;
1507 case DRM_MODE_SUBPIXEL_HORIZONTAL_BGR:
1508 return QPlatformScreen::Subpixel_BGR;
1509 case DRM_MODE_SUBPIXEL_VERTICAL_RGB:
1510 return QPlatformScreen::Subpixel_VRGB;
1511 case DRM_MODE_SUBPIXEL_VERTICAL_BGR:
1512 return QPlatformScreen::Subpixel_VBGR;
1513 }
1514}
1515
1516void QKmsOutput::setPowerState(QKmsDevice *device, QPlatformScreen::PowerState state)
1517{
1518 if (dpms_prop)
1519 drmModeConnectorSetProperty(device->fd(), connector_id,
1520 dpms_prop->prop_id, (int) state);
1521}
1522
1523QT_END_NAMESPACE
void enumerateProperties(drmModeObjectPropertiesPtr objProps, PropCallback callback)
int fd() const
void discoverPlanes()
void checkConnectedScreens()
drmModePropertyPtr connectorProperty(drmModeConnectorPtr connector, const QByteArray &name)
virtual void unregisterScreen(QPlatformScreen *screen)
void parseCrtcProperties(uint32_t crtcId, QKmsOutput *output)
void setFd(int fd)
QKmsScreenConfig * screenConfig() const
virtual QPlatformScreen * createHeadlessScreen()
virtual ~QKmsDevice()
void updateScreens()
QKmsScreenConfig * m_screenConfig
void createScreens()
int crtcForConnector(drmModeResPtr resources, drmModeConnectorPtr connector)
std::function< void(drmModePropertyPtr, quint64)> PropCallback
void parseConnectorProperties(uint32_t connectorId, QKmsOutput *output)
QKmsDevice(QKmsScreenConfig *screenConfig, const QString &path=QString())
QString devicePath() const
virtual void updateScreen(QPlatformScreen *screen, const QPoint &virtualPos, const QList< QPlatformScreen * > &virtualSiblings)
bool m_has_atomic_support
drmModePropertyBlobPtr connectorPropertyBlob(drmModeConnectorPtr connector, const QByteArray &name)
bool createScreenInfoForConnector(drmModeResPtr resources, drmModeConnectorPtr connector, ScreenInfo &vinfo)
virtual void registerScreenCloning(QPlatformScreen *screen, QPlatformScreen *screenThisScreenClones, const QList< QPlatformScreen * > &screensCloningThisScreen)
virtual void updateScreenOutput(QPlatformScreen *screen, const QKmsOutput &output)
void registerScreens(QList< uint32_t > newConnects=QList< uint32_t >())
bool hasAtomicSupport()
bool headless() const
bool separateScreens() const
virtual void loadConfig()
VirtualDesktopLayout m_virtualDesktopLayout
Q_STATIC_LOGGING_CATEGORY(lcAccessibilityCore, "qt.accessibility.core")
QDebug operator<<(QDebug dbg, const NSObject *nsObject)
Definition qcore_mac.mm:201
static void assignPlane(QKmsOutput *output, QKmsPlane *plane)
static const char *const connector_type_names[]
QDebug operator<<(QDebug dbg, const QKmsDevice::OrderedScreen &s)
static bool orderedScreenLessThan(const QKmsDevice::OrderedScreen &a, const QKmsDevice::OrderedScreen &b)
static bool propTypeIs(drmModePropertyPtr prop, uint32_t type)
static QByteArray nameForConnector(const drmModeConnectorPtr connector)
static bool parseModeline(const QByteArray &text, drmModeModeInfoPtr mode)
OutputConfiguration
@ OutputConfigModeline
@ OutputConfigSkip
@ OutputConfigPreferred
@ OutputConfigMode
@ OutputConfigOff
@ OutputConfigCurrent
#define ARRAY_LENGTH(a)
#define DRM_CLIENT_CAP_UNIVERSAL_PLANES
#define DRM_MODE_PROP_SIGNED_RANGE
#define DRM_MODE_PROP_EXTENDED_TYPE
#define DRM_MODE_PROP_OBJECT
OrderedScreen(QPlatformScreen *screen, const ScreenInfo &vinfo)
void restoreMode(QKmsDevice *device)
uint32_t modeIdPropertyId
uint32_t forced_plane_id
struct QKmsPlane * eglfs_plane
drmModePropertyPtr dpms_prop
void setPowerState(QKmsDevice *device, QPlatformScreen::PowerState state)
uint32_t crtc_id
bool forced_plane_set
drmModeCrtcPtr saved_crtc
drmModePropertyBlobPtr edid_blob
QPlatformScreen::SubpixelAntialiasingType subpixelAntialiasingTypeHint() const
void cleanup(QKmsDevice *device)
uint32_t activePropertyId
bool drm_format_requested_by_user
uint32_t crtc_index
uint32_t crtcIdPropertyId
bool wants_forced_plane
uint32_t connector_id
uint32_t srcheightPropertyId
uint32_t zposPropertyId
uint32_t crtcXPropertyId
uint32_t activeCrtcId
uint32_t srcXPropertyId
uint32_t framebufferPropertyId
uint32_t crtcwidthPropertyId
uint32_t crtcPropertyId
uint32_t rotationPropertyId
uint32_t srcYPropertyId
uint32_t crtcheightPropertyId
uint32_t crtcYPropertyId
int possibleCrtcs
uint32_t srcwidthPropertyId
uint32_t blendOpPropertyId