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