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