185 drmModeConnectorPtr connector,
ScreenInfo &vinfo)
187 const QByteArray connectorName = nameForConnector(connector);
191 qWarning() <<
"No usable crtc/encoder pair for connector" << connectorName;
196 QSize configurationSize;
197 int configurationRefresh = 0;
198 drmModeModeInfo configurationModeline;
201 QVariantMap userConnectorConfig = userConfig.value(QString::fromUtf8(connectorName));
203 const QByteArray mode = userConnectorConfig.value(QStringLiteral(
"mode"), QStringLiteral(
"preferred"))
204 .toByteArray().toLower();
207 }
else if (mode ==
"preferred") {
209 }
else if (mode ==
"current") {
211 }
else if (mode ==
"skip") {
213 }
else if (sscanf(mode.constData(),
"%dx%d@%d", &configurationSize.rwidth(), &configurationSize.rheight(),
214 &configurationRefresh) == 3)
217 }
else if (sscanf(mode.constData(),
"%dx%d", &configurationSize.rwidth(), &configurationSize.rheight()) == 2) {
219 }
else if (parseModeline(mode, &configurationModeline)) {
222 qWarning(
"Invalid mode \"%s\" for output %s", mode.constData(), connectorName.constData());
226 vinfo.virtualIndex = userConnectorConfig.value(QStringLiteral(
"virtualIndex"), INT_MAX).toInt();
227 if (userConnectorConfig.contains(QStringLiteral(
"virtualPos"))) {
228 const QByteArray vpos = userConnectorConfig.value(QStringLiteral(
"virtualPos")).toByteArray();
229 const QByteArrayList vposComp = vpos.split(
',');
230 if (vposComp.count() == 2) {
231 vinfo.virtualPos = QPoint(vposComp[0].trimmed().toInt(), vposComp[1].trimmed().toInt());
232 qCDebug(qLcKmsDebug) <<
"Parsing virtualPos to: " << vinfo.virtualPos;
234 vinfo.virtualPos = QPoint(-1, -1);
235 qCDebug(qLcKmsDebug) <<
"Could not parse virtualPos,"
236 <<
"will be calculated based on virtualIndex";
239 vinfo.virtualPos = QPoint(-1, -1);
242 if (userConnectorConfig.value(QStringLiteral(
"primary")).toBool())
245 const uint32_t crtc_id = resources->crtcs[crtc];
248 qCDebug(qLcKmsDebug) <<
"Turning off output" << connectorName;
249 drmModeSetCrtc(
m_dri_fd, crtc_id, 0, 0, 0, 0, 0,
nullptr);
255 qCDebug(qLcKmsDebug) <<
"Skipping disconnected output" << connectorName;
260 qCDebug(qLcKmsDebug) <<
"Skipping output" << connectorName;
265 drmModeModeInfo crtc_mode;
266 memset(&crtc_mode, 0,
sizeof crtc_mode);
267 if (drmModeEncoderPtr encoder = drmModeGetEncoder(
m_dri_fd, connector->encoder_id)) {
269 drmModeCrtcPtr crtc = drmModeGetCrtc(
m_dri_fd, encoder->crtc_id);
270 drmModeFreeEncoder(encoder);
275 if (crtc->mode_valid)
276 crtc_mode = crtc->mode;
278 drmModeFreeCrtc(crtc);
281 QList<drmModeModeInfo> modes;
282 modes.reserve(connector->count_modes);
283 qCDebug(qLcKmsDebug) << connectorName <<
"mode count:" << connector->count_modes
284 <<
"crtc index:" << crtc <<
"crtc id:" << crtc_id;
285 for (
int i = 0; i < connector->count_modes; i++) {
286 const drmModeModeInfo &mode = connector->modes[i];
287 qCDebug(qLcKmsDebug) <<
"mode" << i << mode.hdisplay <<
"x" << mode.vdisplay
288 <<
'@' << mode.vrefresh <<
"hz";
289 modes << connector->modes[i];
297 for (
int i = modes.size() - 1; i >= 0; i--) {
298 const drmModeModeInfo &m = modes.at(i);
301 && m.hdisplay == configurationSize.width()
302 && m.vdisplay == configurationSize.height()
303 && (!configurationRefresh || m.vrefresh == uint32_t(configurationRefresh)))
308 if (!memcmp(&crtc_mode, &m,
sizeof m))
311 if (m.type & DRM_MODE_TYPE_PREFERRED)
318 modes << configurationModeline;
319 configured = modes.size() - 1;
322 if (current < 0 && crtc_mode.clock != 0) {
324 current = modes.size() - 1;
328 configured = current;
330 int selected_mode = -1;
333 selected_mode = configured;
334 else if (preferred >= 0)
335 selected_mode = preferred;
336 else if (current >= 0)
337 selected_mode = current;
339 selected_mode = best;
341 if (selected_mode < 0) {
342 qWarning() <<
"No modes available for output" << connectorName;
345 int width = modes[selected_mode].hdisplay;
346 int height = modes[selected_mode].vdisplay;
347 int refresh = modes[selected_mode].vrefresh;
348 qCDebug(qLcKmsDebug) <<
"Selected mode" << selected_mode <<
":" << width <<
"x" << height
349 <<
'@' << refresh <<
"hz for output" << connectorName;
353 int pwidth = qEnvironmentVariableIntValue(
"QT_QPA_EGLFS_PHYSICAL_WIDTH");
355 pwidth = qEnvironmentVariableIntValue(
"QT_QPA_PHYSICAL_WIDTH");
356 int pheight = qEnvironmentVariableIntValue(
"QT_QPA_EGLFS_PHYSICAL_HEIGHT");
358 pheight = qEnvironmentVariableIntValue(
"QT_QPA_PHYSICAL_HEIGHT");
359 QSizeF physSize(pwidth, pheight);
360 if (physSize.isEmpty()) {
361 physSize = QSize(userConnectorConfig.value(QStringLiteral(
"physicalWidth")).toInt(),
362 userConnectorConfig.value(QStringLiteral(
"physicalHeight")).toInt());
363 if (physSize.isEmpty()) {
364 physSize.setWidth(connector->mmWidth);
365 physSize.setHeight(connector->mmHeight);
368 qCDebug(qLcKmsDebug) <<
"Physical size is" << physSize <<
"mm" <<
"for output" << connectorName;
370 const QByteArray formatStr = userConnectorConfig.value(QStringLiteral(
"format"), QString())
371 .toByteArray().toLower();
373 bool drmFormatExplicit =
true;
374 if (formatStr.isEmpty()) {
375 drmFormat = DRM_FORMAT_XRGB8888;
376 drmFormatExplicit =
false;
377 }
else if (formatStr ==
"xrgb8888") {
378 drmFormat = DRM_FORMAT_XRGB8888;
379 }
else if (formatStr ==
"xbgr8888") {
380 drmFormat = DRM_FORMAT_XBGR8888;
381 }
else if (formatStr ==
"argb8888") {
382 drmFormat = DRM_FORMAT_ARGB8888;
383 }
else if (formatStr ==
"abgr8888") {
384 drmFormat = DRM_FORMAT_ABGR8888;
385 }
else if (formatStr ==
"rgb565") {
386 drmFormat = DRM_FORMAT_RGB565;
387 }
else if (formatStr ==
"bgr565") {
388 drmFormat = DRM_FORMAT_BGR565;
389 }
else if (formatStr ==
"xrgb2101010") {
390 drmFormat = DRM_FORMAT_XRGB2101010;
391 }
else if (formatStr ==
"xbgr2101010") {
392 drmFormat = DRM_FORMAT_XBGR2101010;
393 }
else if (formatStr ==
"argb2101010") {
394 drmFormat = DRM_FORMAT_ARGB2101010;
395 }
else if (formatStr ==
"abgr2101010") {
396 drmFormat = DRM_FORMAT_ABGR2101010;
398 qWarning(
"Invalid pixel format \"%s\" for output %s", formatStr.constData(), connectorName.constData());
399 drmFormat = DRM_FORMAT_XRGB8888;
400 drmFormatExplicit =
false;
402 qCDebug(qLcKmsDebug) <<
"Format is" << Qt::hex << drmFormat << Qt::dec <<
"requested_by_user =" << drmFormatExplicit
403 <<
"for output" << connectorName;
405 const QString cloneSource = userConnectorConfig.value(QStringLiteral(
"clones")).toString();
406 if (!cloneSource.isEmpty())
407 qCDebug(qLcKmsDebug) <<
"Output" << connectorName <<
" clones output " << cloneSource;
409 QSize framebufferSize;
410 bool framebufferSizeSet =
false;
411 const QByteArray fbsize = userConnectorConfig.value(QStringLiteral(
"size")).toByteArray().toLower();
412 if (!fbsize.isEmpty()) {
413 if (sscanf(fbsize.constData(),
"%dx%d", &framebufferSize.rwidth(), &framebufferSize.rheight()) == 2) {
414#if QT_CONFIG(drm_atomic)
415 if (hasAtomicSupport())
416 framebufferSizeSet =
true;
418 if (!framebufferSizeSet)
419 qWarning(
"Setting framebuffer size is only available with DRM atomic API");
421 qWarning(
"Invalid framebuffer size '%s'", fbsize.constData());
424 if (!framebufferSizeSet) {
425 framebufferSize.setWidth(modes[selected_mode].hdisplay);
426 framebufferSize.setHeight(modes[selected_mode].vdisplay);
429 qCDebug(qLcKmsDebug) <<
"Output" << connectorName <<
"framebuffer size is " << framebufferSize;
432 output.name = QString::fromUtf8(connectorName);
436 output.physical_size = physSize;
438 output
.mode = selected_mode;
441 output.modes = modes;
443 output.dpms_prop = connectorProperty(connector, QByteArrayLiteral(
"DPMS"));
444 output.edid_blob = connectorPropertyBlob(connector, QByteArrayLiteral(
"EDID"));
448 output.drm_format = drmFormat;
450 output.clone_source = cloneSource;
451 output.size = framebufferSize;
453#if QT_CONFIG(drm_atomic)
454 if (drmModeCreatePropertyBlob(m_dri_fd, &modes[selected_mode],
sizeof(drmModeModeInfo),
455 &output.mode_blob_id) != 0) {
456 qCDebug(qLcKmsDebug) <<
"Failed to create mode blob for mode" << selected_mode;
459 parseConnectorProperties(output.connector_id, &output);
460 parseCrtcProperties(output.crtc_id, &output);
463 QString planeListStr;
464 for (QKmsPlane &plane : m_planes) {
465 if (plane.possibleCrtcs & (1 << output.crtc_index)) {
466 output.available_planes.append(plane);
467 planeListStr.append(QString::number(plane.id));
468 planeListStr.append(u' ');
472 if (!output.eglfs_plane && plane.type == QKmsPlane::PrimaryPlane && !plane.activeCrtcId)
473 assignPlane(&output, &plane);
476 qCDebug(qLcKmsDebug,
"Output %s can use %d planes: %s",
477 connectorName.constData(),
int(output.available_planes.size()), qPrintable(planeListStr));
483 int idx = qEnvironmentVariableIntValue(
"QT_QPA_EGLFS_KMS_PLANE_INDEX", &ok);
485 drmModePlaneRes *planeResources = drmModeGetPlaneResources(
m_dri_fd);
486 if (planeResources) {
487 if (idx >= 0 && idx <
int(planeResources->count_planes)) {
488 drmModePlane *plane = drmModeGetPlane(
m_dri_fd, planeResources->planes[idx]);
492 qCDebug(qLcKmsDebug,
"Forcing plane index %d, plane id %u (belongs to crtc id %u)",
493 idx, plane->plane_id, plane->crtc_id);
495 for (QKmsPlane &kmsplane : m_planes) {
496 if (kmsplane.id == output.forced_plane_id) {
497 assignPlane(&output, &kmsplane);
502 drmModeFreePlane(plane);
505 qWarning(
"Invalid plane index %d, must be between 0 and %u", idx, planeResources->count_planes - 1);
512 if (qEnvironmentVariableIsSet(
"QT_QPA_EGLFS_KMS_PLANES_FOR_CRTCS")) {
513 const QString val = qEnvironmentVariable(
"QT_QPA_EGLFS_KMS_PLANES_FOR_CRTCS");
514 qCDebug(qLcKmsDebug,
"crtc_id:plane_id override list: %s", qPrintable(val));
515 const QStringList crtcPlanePairs = val.split(u':');
516 for (
const QString &crtcPlanePair : crtcPlanePairs) {
517 const QStringList values = crtcPlanePair.split(u',');
518 if (values.size() == 2 && uint(values[0].toInt()) == output.crtc_id) {
519 uint planeId = values[1].toInt();
520 for (QKmsPlane &kmsplane : m_planes) {
521 if (kmsplane.id == planeId) {
522 assignPlane(&output, &kmsplane);
531 qCDebug(qLcKmsDebug,
"Chose plane %u for output %s (crtc id %u) (may not be applicable)",
532 output.eglfs_plane->id, connectorName.constData(), output.crtc_id);
535#if QT_CONFIG(drm_atomic)
536 if (hasAtomicSupport() && !output.eglfs_plane) {
537 qCDebug(qLcKmsDebug,
"No plane associated with output %s (crtc id %u) and atomic modesetting is enabled. This is bad.",
538 connectorName.constData(), output.crtc_id);
542 m_crtc_allocator |= (1 << output.crtc_index);
544 vinfo.output = output;
610 drmModeResPtr resources = drmModeGetResources(
m_dri_fd);
612 qErrnoWarning(errno,
"drmModeGetResources failed");
616 QList<uint32_t> newConnects;
617 QList<uint32_t> newDisconnects;
618 const QMap<QString, QVariantMap> userConfig =
m_screenConfig->outputSettings();
620 for (
int i = 0; i < resources->count_connectors; i++) {
621 drmModeConnectorPtr connector = drmModeGetConnector(
m_dri_fd, resources->connectors[i]);
623 qErrnoWarning(errno,
"drmModeGetConnector failed");
627 const uint32_t id = connector->connector_id;
629 const QByteArray connectorName = nameForConnector(connector);
630 const QVariantMap userCConfig = userConfig.value(QString::fromUtf8(connectorName));
631 const QByteArray mode = userCConfig.value(QStringLiteral(
"mode")).toByteArray().toLower();
632 if (mode ==
"off" || mode ==
"skip")
635 if (connector->connection == DRM_MODE_CONNECTED) {
636 if (!m_registeredScreens.contains(id))
637 newConnects.append(id);
639 qCDebug(qLcKmsDebug) <<
"Connected screen already registered: connector id=" << id;
642 if (connector->connection == DRM_MODE_DISCONNECTED) {
643 if (m_registeredScreens.contains(id))
644 newDisconnects.append(id);
646 qCDebug(qLcKmsDebug) <<
"Disconnected screen not registered: connector id=" << id;
649 drmModeFreeConnector(connector);
652 if (newConnects.isEmpty() && newDisconnects.isEmpty()) {
653 qCDebug(qLcKmsDebug) <<
"EGLFS/KMS: KMS-device-change but no new connects or disconnects "
654 <<
"to process - exiting";
657 qCDebug(qLcKmsDebug) <<
"EGLFS/KMS: KMS-device-change, new connects:" << newConnects
658 <<
", and disconnected: " << newDisconnects;
661 const int remainingScreenCount = m_registeredScreens.count() - newDisconnects.count();
662 if (remainingScreenCount == 0 && m_headlessScreen ==
nullptr) {
663 qCDebug(qLcKmsDebug) <<
"EGLFS/KMS: creating headless screen before"
664 <<
"unregistering screens to avoid having no screens";
665 m_headlessScreen = createHeadlessScreen();
666 registerScreen(m_headlessScreen,
true, QPoint(),
667 QList<QPlatformScreen *>() << m_headlessScreen);
670 for (uint32_t connectorId : newDisconnects) {
671 OrderedScreen orderedScreen = m_registeredScreens.take(connectorId);
672 QPlatformScreen *screen = orderedScreen.screen;
676 uint32_t crtcId = (orderedScreen.vinfo.output.eglfs_plane !=
nullptr)
677 ? orderedScreen.vinfo.output.eglfs_plane->activeCrtcId
678 : orderedScreen.vinfo.output.crtc_id;
680 if (orderedScreen.vinfo.output.eglfs_plane !=
nullptr)
681 orderedScreen.vinfo.output.eglfs_plane->activeCrtcId = 0;
684 const int crtcIdx = orderedScreen.vinfo.output.crtc_index;
685 m_crtc_allocator &= ~(1 << crtcIdx);
687 const int ret = drmModeSetCrtc(m_dri_fd, crtcId, 0, 0, 0,
nullptr, 0,
nullptr);
690 qCWarning(qLcKmsDebug) <<
"Could not disable CRTC" << crtcId
691 <<
"on connector" << connectorId <<
"removal:" << ret;
693 qCDebug(qLcKmsDebug) <<
"Disabled CRTC" << crtcId
694 <<
"for connector " << connectorId <<
"disconnected";
698 if (orderedScreen.vinfo.output.saved_crtc) {
699 drmModeFreeCrtc(orderedScreen.vinfo.output.saved_crtc);
700 orderedScreen.vinfo.output.saved_crtc =
nullptr;
701 updateScreenOutput(orderedScreen.screen, orderedScreen.vinfo.output);
704 unregisterScreen(screen);
707 for (uint32_t connectorId : newConnects) {
708 drmModeConnectorPtr connector = drmModeGetConnector(m_dri_fd, connectorId);
710 qErrnoWarning(errno,
"drmModeGetConnector failed");
715 bool succ = createScreenInfoForConnector(resources, connector, vinfo);
716 drmModeFreeConnector(connector);
720 QPlatformScreen *screen = createScreen(vinfo.output);
724 OrderedScreen orderedScreen(screen, vinfo);
725 m_registeredScreens[connectorId] = orderedScreen;
728 drmModeFreeResources(resources);
730 registerScreens(newConnects);
738 drmModeResPtr resources = drmModeGetResources(
m_dri_fd);
740 qErrnoWarning(errno,
"drmModeGetResources failed");
744 QList<uint32_t> newConnects;
745 QList<OrderedScreen> newDisconnects;
747 for (
int i = 0; i < resources->count_connectors; i++) {
748 drmModeConnectorPtr connector = drmModeGetConnector(
m_dri_fd, resources->connectors[i]);
752 if (m_registeredScreens.contains(connector->connector_id)) {
753 OrderedScreen &os = m_registeredScreens[connector->connector_id];
761 const int crtcIdx = os.vinfo.output.crtc_index;
762 m_crtc_allocator &= ~(1 << crtcIdx);
765 if (os.vinfo.output.eglfs_plane)
766 os.vinfo.output.eglfs_plane->activeCrtcId = 0;
771 drmModeCrtcPtr saved_saved_crtc =
nullptr;
772 if (os.vinfo.output.saved_crtc)
773 saved_saved_crtc = os.vinfo.output.saved_crtc;
783 if (os.vinfo.output.saved_crtc) {
784 drmModeFreeCrtc(os.vinfo.output.saved_crtc);
785 os.vinfo.output.saved_crtc =
nullptr;
786 updateScreenOutput(os.screen, os.vinfo.output);
791 newDisconnects.append(m_registeredScreens.take(connector->connector_id));
792 drmModeFreeConnector(connector);
795 drmModeFreeConnector(connector);
797 drmModeFreeCrtc(vinfo.output.saved_crtc);
798 vinfo.output.saved_crtc = saved_saved_crtc;
801 updateScreenOutput(os.screen, os.vinfo.output);
809 QPlatformScreen *screen = createScreen(vinfo.output);
811 m_registeredScreens[connector->connector_id] = orderedScreen;
812 newConnects.append(connector->connector_id);
817 if (m_registeredScreens.count() == 0 && m_headlessScreen ==
nullptr) {
819 m_headlessScreen = createHeadlessScreen();
820 registerScreen(m_headlessScreen,
true, QPoint(),
821 QList<QPlatformScreen *>() << m_headlessScreen);
825 registerScreens(newConnects);
828 for (
const OrderedScreen &os : newDisconnects)
829 unregisterScreen(os.screen);
831 drmModeFreeResources(resources);
839 QPlatformScreen *screen = createHeadlessScreen();
841 qCDebug(qLcKmsDebug,
"Headless mode enabled");
842 registerScreen(screen,
true, QPoint(0, 0),
843 QList<QPlatformScreen *>() << screen);
846 qWarning(
"QKmsDevice: Requested headless mode without support in the backend. Request is ignored.");
852#if QT_CONFIG(drm_atomic)
854 m_has_atomic_support = !drmSetClientCap(m_dri_fd, DRM_CLIENT_CAP_ATOMIC, 1);
855 if (m_has_atomic_support) {
856 qCDebug(qLcKmsDebug,
"Atomic reported as supported");
857 if (qEnvironmentVariableIntValue(
"QT_QPA_EGLFS_KMS_ATOMIC")) {
858 qCDebug(qLcKmsDebug,
"Atomic enabled");
860 qCDebug(qLcKmsDebug,
"Atomic disabled");
861 m_has_atomic_support =
false;
866 drmModeResPtr resources = drmModeGetResources(
m_dri_fd);
868 qErrnoWarning(errno,
"drmModeGetResources failed");
874 QList<uint32_t> newConnects;
875 int wantedConnectorIndex = -1;
877 int idx = qEnvironmentVariableIntValue(
"QT_QPA_EGLFS_KMS_CONNECTOR_INDEX", &ok);
879 if (idx >= 0 && idx < resources->count_connectors)
880 wantedConnectorIndex = idx;
882 qWarning(
"Invalid connector index %d, must be between 0 and %u", idx, resources->count_connectors - 1);
885 for (
int i = 0; i < resources->count_connectors; i++) {
886 if (wantedConnectorIndex >= 0 && i != wantedConnectorIndex)
889 drmModeConnectorPtr connector = drmModeGetConnector(
m_dri_fd, resources->connectors[i]);
891 qErrnoWarning(errno,
"drmModeGetConnector failed");
897 uint32_t connectorId = connector->connector_id;
898 drmModeFreeConnector(connector);
902 QPlatformScreen *screen = createScreen(vinfo.output);
907 m_registeredScreens[connectorId] = orderedScreen;
908 newConnects.append(connectorId);
911 drmModeFreeResources(resources);
913 if (!qEnvironmentVariable(
"QT_QPA_EGLFS_HOTPLUG_ENABLED").isEmpty()
914 && newConnects.empty() && m_headlessScreen ==
nullptr) {
915 qCDebug(qLcKmsDebug) <<
"'QT_QPA_EGLFS_HOTPLUG_ENABLED' was set and no screen was connected/found during start-up."
916 <<
"In order for Qt to operate properly a qt_headless screen will be created."
917 <<
"It will be automatically removed as soon as the first screen is connected";
919 m_headlessScreen = createHeadlessScreen();
920 registerScreen(m_headlessScreen,
true, QPoint(),
921 QList<QPlatformScreen *>() << m_headlessScreen);
924 registerScreens(newConnects);
929 QList<OrderedScreen> screens = m_registeredScreens.values();
933 std::stable_sort(screens.begin(), screens.end(), orderedScreenLessThan);
934 qCDebug(qLcKmsDebug) <<
"Sorted screen list:" << screens;
938 for (
const OrderedScreen &orderedScreen : screens) {
939 QList<QPlatformScreen *> screensCloningThisScreen;
940 for (
const OrderedScreen &s : screens) {
941 if (s.vinfo.output.clone_source == orderedScreen.vinfo.output.name)
942 screensCloningThisScreen.append(s.screen);
944 QPlatformScreen *screenThisScreenClones =
nullptr;
945 if (!orderedScreen.vinfo.output.clone_source.isEmpty()) {
946 for (
const OrderedScreen &s : screens) {
947 if (s.vinfo.output.name == orderedScreen.vinfo.output.clone_source) {
948 screenThisScreenClones = s.screen;
953 if (screenThisScreenClones)
954 qCDebug(qLcKmsDebug) << orderedScreen.screen->name() <<
"clones" << screenThisScreenClones;
955 if (!screensCloningThisScreen.isEmpty())
956 qCDebug(qLcKmsDebug) << orderedScreen.screen->name() <<
"is cloned by" << screensCloningThisScreen;
958 registerScreenCloning(orderedScreen.screen, screenThisScreenClones, screensCloningThisScreen);
963 QList<OrderedScreen> siblings;
964 QList<QPoint> virtualPositions;
965 int primarySiblingIdx = -1;
968 for (
const OrderedScreen &orderedScreen : screens) {
969 QPlatformScreen *s = orderedScreen.screen;
970 QPoint virtualPos(0, 0);
972 if (orderedScreen.vinfo.virtualPos.x() == -1 || orderedScreen.vinfo.virtualPos.y() == -1) {
973 if (orderedScreen.vinfo.output.clone_source.isEmpty()) {
975 if (m_screenConfig->virtualDesktopLayout() == QKmsScreenConfig::VirtualDesktopLayoutVertical)
976 pos.ry() += s->geometry().height();
978 pos.rx() += s->geometry().width();
980 for (
int i = 0; i < screens.count(); i++) {
981 const OrderedScreen &os = screens[i];
982 if (os.vinfo.output.name == orderedScreen.vinfo.output.clone_source) {
983 if (i >= virtualPositions.count()) {
984 qCWarning(qLcKmsDebug)
985 <<
"WARNING: When using clone on kms config,"
986 <<
"you have to either order your screens (virtualIndex),"
987 <<
"so clones come after their source,"
988 <<
"or specify 'virtualPos' for each clone."
989 <<
"Otherwise desktop-geomerty might not work properly!";
992 virtualPos = virtualPositions[i];
999 virtualPos = orderedScreen.vinfo.virtualPos;
1005 if (!m_screenConfig->separateScreens()) {
1006 siblings.append(orderedScreen);
1007 virtualPositions.append(virtualPos);
1008 if (orderedScreen.vinfo.isPrimary)
1009 primarySiblingIdx = siblings.size() - 1;
1011 const bool isNewScreen = newConnects.contains(orderedScreen.vinfo.output.connector_id);
1013 qCDebug(qLcKmsDebug) <<
"Adding QPlatformScreen" << s <<
"(" << s->name() <<
")"
1014 <<
"to QPA with geometry" << s->geometry()
1015 <<
", virtual position" << virtualPos
1016 <<
"and isPrimary=" << orderedScreen.vinfo.isPrimary;
1017 registerScreen(s, orderedScreen.vinfo.isPrimary, virtualPos,
1018 QList<QPlatformScreen *>() << s);
1019 deskRegion += s->geometry();
1021 qCDebug(qLcKmsDebug) <<
"Updating QPlatformScreen" << s <<
"(" << s->name() <<
")"
1022 <<
"to QPA with geometry" << s->geometry()
1023 <<
", virtual position" << virtualPos
1024 <<
"and isPrimary=" << orderedScreen.vinfo.isPrimary;
1025 updateScreen(s, virtualPos, QList<QPlatformScreen *>() << s);
1026 deskRegion += s->geometry();
1032 QList<QPlatformScreen *> platformScreenSiblings;
1033 for (
int i = 0; i < siblings.count(); ++i) {
1034 platformScreenSiblings.append(siblings[i].screen);
1038 for (
int i = 0; i < siblings.count(); ++i) {
1039 QPlatformScreen *screen = platformScreenSiblings[i];
1041 const bool isNewScreen = newConnects.contains(orderedScreen.vinfo.output.connector_id);
1043 qCDebug(qLcKmsDebug) <<
"Adding QPlatformScreen" << screen
1044 <<
"(" << screen->name() <<
")"
1045 <<
"to QPA with geometry" << screen->geometry()
1046 <<
", virtual position" << virtualPositions[i]
1047 <<
"and isPrimary=" << orderedScreen.vinfo.isPrimary;
1048 registerScreen(screen, i == primarySiblingIdx, virtualPositions[i],
1049 platformScreenSiblings);
1050 deskRegion += screen->geometry();
1052 qCDebug(qLcKmsDebug) <<
"Updating QPlatformScreen" << screen
1053 <<
"(" << screen->name() <<
")"
1054 <<
"to QPA with geometry" << screen->geometry()
1055 <<
", virtual position" << virtualPositions[i]
1056 <<
"and isPrimary=" << orderedScreen.vinfo.isPrimary;
1057 updateScreen(screen, virtualPositions[i], platformScreenSiblings);
1058 deskRegion += screen->geometry();
1064 if (!m_registeredScreens.empty() && m_headlessScreen) {
1065 unregisterScreen(m_headlessScreen);
1066 m_headlessScreen =
nullptr;
1071 QPoint currCPos = QCursor::pos();
1072 if (!deskRegion.contains(currCPos)) {
1075 QRect deskRect = deskRegion.boundingRect();
1076 currCPos.setX(qMin(currCPos.x(), deskRect.width()) - 1);
1077 currCPos.setY(qMin(currCPos.y(), deskRect.height()) - 1);
1080 if (!deskRegion.contains(currCPos))
1081 currCPos = QPoint(0,0);
1083 qCDebug(qLcKmsDebug) <<
"Due to desktop layout change, overriding cursor pos."
1084 <<
"Is: " << QCursor::pos() <<
", will be: " << currCPos;
1085 QCursor::setPos(currCPos);
1174 drmModePlaneResPtr planeResources = drmModeGetPlaneResources(
m_dri_fd);
1175 if (!planeResources)
1178 const int countPlanes = planeResources->count_planes;
1179 qCDebug(qLcKmsDebug,
"Found %d planes", countPlanes);
1180 for (
int planeIdx = 0; planeIdx < countPlanes; ++planeIdx) {
1181 drmModePlanePtr drmplane = drmModeGetPlane(
m_dri_fd, planeResources->planes[planeIdx]);
1183 qCDebug(qLcKmsDebug,
"Failed to query plane %d, ignoring", planeIdx);
1188 plane.id = drmplane->plane_id;
1191 const int countFormats = drmplane->count_formats;
1193 for (
int i = 0; i < countFormats; ++i) {
1194 uint32_t f = drmplane->formats[i];
1195 plane.supportedFormats.append(f);
1196 formatStr += QString::asprintf(
"%c%c%c%c ", f, f >> 8, f >> 16, f >> 24);
1199 qCDebug(qLcKmsDebug,
"plane %d: id = %u countFormats = %d possibleCrtcs = 0x%x supported formats = %s",
1200 planeIdx, plane.id, countFormats, plane.possibleCrtcs, qPrintable(formatStr));
1202 drmModeFreePlane(drmplane);
1204 drmModeObjectPropertiesPtr objProps = drmModeObjectGetProperties(m_dri_fd, plane.id, DRM_MODE_OBJECT_PLANE);
1206 qCDebug(qLcKmsDebug,
"Failed to query plane %d object properties, ignoring", planeIdx);
1211 if (!strcmp(prop->name,
"type")) {
1213 }
else if (!strcmp(prop->name,
"rotation")) {
1214 plane.initialRotation = QKmsPlane::Rotations(
int(value));
1215 plane.availableRotations = { };
1216 if (propTypeIs(prop, DRM_MODE_PROP_BITMASK)) {
1217 for (
int i = 0; i < prop->count_enums; ++i)
1218 plane.availableRotations |= QKmsPlane::Rotation(1 << prop->enums[i].value);
1221 }
else if (!strcasecmp(prop->name,
"crtc_id")) {
1223 }
else if (!strcasecmp(prop->name,
"fb_id")) {
1225 }
else if (!strcasecmp(prop->name,
"src_w")) {
1227 }
else if (!strcasecmp(prop->name,
"src_h")) {
1229 }
else if (!strcasecmp(prop->name,
"crtc_w")) {
1231 }
else if (!strcasecmp(prop->name,
"crtc_h")) {
1233 }
else if (!strcasecmp(prop->name,
"src_x")) {
1235 }
else if (!strcasecmp(prop->name,
"src_y")) {
1237 }
else if (!strcasecmp(prop->name,
"crtc_x")) {
1239 }
else if (!strcasecmp(prop->name,
"crtc_y")) {
1241 }
else if (!strcasecmp(prop->name,
"zpos")) {
1243 }
else if (!strcasecmp(prop->name,
"blend_op")) {
1248 m_planes.append(plane);
1250 drmModeFreeObjectProperties(objProps);
1253 drmModeFreePlaneResources(planeResources);
1385 QByteArray json = qgetenv(
"QT_QPA_EGLFS_KMS_CONFIG");
1386 if (json.isEmpty()) {
1387 json = qgetenv(
"QT_QPA_KMS_CONFIG");
1392 qCDebug(qLcKmsDebug) <<
"Loading KMS setup from" << json;
1394 QFile file(QString::fromUtf8(json));
1395 if (!file.open(QFile::ReadOnly)) {
1396 qCWarning(qLcKmsDebug) <<
"Could not open config file"
1397 << json <<
"for reading";
1401 const QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
1402 if (!doc.isObject()) {
1403 qCWarning(qLcKmsDebug) <<
"Invalid config file" << json
1404 <<
"- no top-level JSON object";
1408 const QJsonObject object = doc.object();
1410 const QString headlessStr = object.value(
"headless"_L1).toString();
1411 const QByteArray headless = headlessStr.toUtf8();
1413 if (sscanf(headless.constData(),
"%dx%d", &headlessSize.rwidth(), &headlessSize.rheight()) == 2) {
1415 m_headlessSize = headlessSize;
1420 const QString headlessSizeStr = object.value(QLatin1String(
"headlessSize")).toString();
1421 if (sscanf(headlessSizeStr.toUtf8().constData(),
"%dx%d", &headlessSize.rwidth(),
1422 &headlessSize.rheight()) == 2)
1423 m_headlessSize = headlessSize;
1425 m_hwCursor = object.value(
"hwcursor"_L1).toBool(m_hwCursor);
1426 m_pbuffers = object.value(
"pbuffers"_L1).toBool(m_pbuffers);
1427 m_devicePath = object.value(
"device"_L1).toString();
1428 m_separateScreens = object.value(
"separateScreens"_L1).toBool(m_separateScreens);
1430 const auto vdOriString = object.value(
"virtualDesktopLayout"_L1).toStringView();
1431 if (!vdOriString.isEmpty()) {
1432 if (vdOriString ==
"horizontal"_L1)
1434 else if (vdOriString ==
"vertical"_L1)
1437 qCWarning(qLcKmsDebug,
"Unknown virtualDesktopOrientation value %ls",
1438 qUtf16Printable(vdOriString.toString()));
1441 const QJsonArray outputs = object.value(
"outputs"_L1).toArray();
1442 for (
int i = 0; i < outputs.size(); i++) {
1443 const QVariantMap outputSettings = outputs.at(i).toObject().toVariantMap();
1445 if (outputSettings.contains(QStringLiteral(
"name"))) {
1446 const QString name = outputSettings.value(QStringLiteral(
"name")).toString();
1448 if (m_outputSettings.contains(name)) {
1449 qCDebug(qLcKmsDebug) <<
"Output" << name <<
"configured multiple times!";
1452 m_outputSettings.insert(name, outputSettings);
1456 qCDebug(qLcKmsDebug) <<
"Requested configuration (some settings may be ignored):\n"
1457 <<
"\theadless:" << m_headless <<
"\n"
1458 <<
"\thwcursor:" << m_hwCursor <<
"\n"
1459 <<
"\tpbuffers:" << m_pbuffers <<
"\n"
1460 <<
"\tseparateScreens:" << m_separateScreens <<
"\n"
1461 <<
"\tvirtualDesktopLayout:" << m_virtualDesktopLayout <<
"\n"
1462 <<
"\toutputs:" << m_outputSettings;