diff options
author | Thomas Senyk <[email protected]> | 2024-09-04 11:22:06 -0400 |
---|---|---|
committer | Thomas Senyk <[email protected]> | 2025-07-02 12:46:27 +0200 |
commit | b85ec0a01f72b44d1e54e96632374fe33c6dbfaf (patch) | |
tree | 01df5c9e6645a2b8fc6cac9259d9f38a3ffe5466 /src | |
parent | 7fec2a2594de9dcfe019546cc8026bccabd12648 (diff) |
eglfs/kms: hot-plug and hot-reload
This patch extends eglfs/kms to react to events during runtime:
- screens/displays getting connected ("plug in")
as well as disconnected ("unplug")
This is only enabled when setting QT_QPA_EGLFS_HOTPLUG_ENABLED
- changes to the kms-config-file referenced by QT_QPA_EGLFS_KMS_CONFIG or QT_QPA_KMS_CONFIG
eglfs will now reevaluate the connected displays and the referenced
kms-config during runtime and react accordingly.
Features/functionality of hot-plug:
- QKmsDevice::checkConnectedScreens() (new function)
will be called by QDeviceDiscovery::deviceChanged (new signal),
which is fired by QDeviceDiscoveryUDev::handleUDevNotification
when "change"(formerly ignored) is the action driven/created by udev
- disconnected screens will be unregistered via
QKmsDevice::unregisterScreen
(new virtual function, this variant is empty).
The code lives in the two implementations of unregisterScreen:
QEglFSKmsGbmDevice::unregisterScreen will deal with global-cursor,
then call it's parent variant QEglFSKmsDevice::unregisterScreen
to call QWindowSystemInterface::handleScreenRemoved,
feeding the information about the lost screen into the event loop
- each newly connected display will be "configured" via
QKmsDevice::createScreenInfoForConnector (refactored function),
and then the platform class will be created via
QKmsDevice::createScreen
Features/functionality of hot-reload:
- refactor QKmsDevice::createScreenForConnector to
QKmsDevice::createScreenInfoForConnector, as the hot-reload requires
to update QPlatformScreens rather then creating new ones right away
- QFileSystemWatcher is added to QEglFSKmsGbmIntegration in
QEglFSKmsGbmIntegration::createDevice()
It will watch the file referenced by
QT_QPA_EGLFS_KMS_CONFIG or QT_QPA_KMS_CONFIG
When QFileSystemWatcher::fileChanged is fired,
two function will be called:
- KmsScreenConfig::refreshConfig(),
a new function which clears the old config and calls
QKmsScreenConfig::loadConfig()
- QKmsDevice::updateScreens(), a new function that goes over all
plugged-in and enabled screens, recreates their configuration via
QKmsDevice::createScreenInfoForConnector, updates them via
QKmsDevice/QEglFSKmsDevice::updateScreenOutput (new virtual),
and at the end will call QKmsDevice::registerScreens
(new function, see "shared changes" for details)
Shared changes / necessary refactoring:
- QKmsDevice::createScreens was refactored and split, where
QKmsDevice::registerScreens contains code regarding screen-cloning,
calling QKmsDevice::registerScreen (existing, pure virtual) and
calling QKmsDevice::updateScreen (new, virtual function with one
"proper" implementation in QEglFSKmsDevice::updateScreen)
- Adding code regarding creation and destruction of qt_headless
A replacement screen is needed when going to 0 displays,
as that is not supported by Qt.
This applies to all 3 cases: startup, hotplug and hotreload
(while for 'startup' it will only create a headless screen if
QT_QPA_EGLFS_HOTPLUG_ENABLED is set)
- added "headlessSize" to kms-config to allow the user to configure
the specific size of the headless "fake" screen
- default value for "headlessSize" is 1024x768 (used to be 0x0)
Those two changes are necessary/useful as headless
is an implicit feature in this case as the fallback
for when the last screen is disconnected.
(In contrast to explicit 'headless' option)
- a bunch of extra code regarding cloned screens,
because they can now be added and removed during runtime
(via screen-plug-in/out events as well as config changes)
- renaming m_nonThreadedFlipMutex to s_nonThreadedFlipMutex
as it's a static member
- fix virtualPos to allow for 0,0 to be a valid setting
(by having -1,-1 as 'is not set'-value)
- QKmsDevice::registerScreens will now check if the cursor is
outside the current desktopRegion. If it is, it will be
repositioned to be inside (it would vanish otherwise)
- adding wait-timout in waitForFlipWithEventReader
to account for the fact that cloned screens can be created
and added during the runtime.
Without a timeout it can happen that the clonee waits forever,
as the new clone can be added as a flip-dependency
while it isn't scheduled to wake the clonee's wait yet
Change-Id: I08421a1be156b6edb9906ef736cba149dab360ff
Reviewed-by: Laszlo Agocs <[email protected]>
Diffstat (limited to 'src')
21 files changed, 690 insertions, 90 deletions
diff --git a/src/platformsupport/devicediscovery/qdevicediscovery_p.h b/src/platformsupport/devicediscovery/qdevicediscovery_p.h index 59ce3cd8896..61b0c705898 100644 --- a/src/platformsupport/devicediscovery/qdevicediscovery_p.h +++ b/src/platformsupport/devicediscovery/qdevicediscovery_p.h @@ -58,6 +58,7 @@ public: signals: void deviceDetected(const QString &deviceNode); void deviceRemoved(const QString &deviceNode); + void deviceChanged(const QString &deviceNode); protected: QDeviceDiscovery(QDeviceTypes types, QObject *parent) : QObject(parent), m_types(types) { } diff --git a/src/platformsupport/devicediscovery/qdevicediscovery_udev.cpp b/src/platformsupport/devicediscovery/qdevicediscovery_udev.cpp index edb3fc58a22..e4d69101f75 100644 --- a/src/platformsupport/devicediscovery/qdevicediscovery_udev.cpp +++ b/src/platformsupport/devicediscovery/qdevicediscovery_udev.cpp @@ -175,6 +175,9 @@ void QDeviceDiscoveryUDev::handleUDevNotification() if (qstrcmp(action, "remove") == 0) emit deviceRemoved(devNode); + if (qstrcmp(action, "change") == 0) + emit deviceChanged(devNode); + cleanup: udev_device_unref(dev); } diff --git a/src/platformsupport/kmsconvenience/qkmsdevice.cpp b/src/platformsupport/kmsconvenience/qkmsdevice.cpp index cdd2ac2d572..7b2637bfffe 100644 --- a/src/platformsupport/kmsconvenience/qkmsdevice.cpp +++ b/src/platformsupport/kmsconvenience/qkmsdevice.cpp @@ -151,17 +151,45 @@ static inline void assignPlane(QKmsOutput *output, QKmsPlane *plane) output->eglfs_plane = plane; } -QPlatformScreen *QKmsDevice::createScreenForConnector(drmModeResPtr resources, - drmModeConnectorPtr connector, - ScreenInfo *vinfo) +static bool orderedScreenLessThan(const QKmsDevice::OrderedScreen &a, + const QKmsDevice::OrderedScreen &b) +{ + return a.vinfo.virtualIndex < b.vinfo.virtualIndex; +} + +QKmsDevice::OrderedScreen::OrderedScreen() : screen(nullptr) { } + +QKmsDevice::OrderedScreen::OrderedScreen(QPlatformScreen *screen, + const QKmsDevice::ScreenInfo &vinfo) + : screen(screen), vinfo(vinfo) +{ +} + +QDebug operator<<(QDebug dbg, const QPlatformScreen *screen) +{ + QDebugStateSaver saver(dbg); + dbg.nospace() << "QPlatformScreen=" << (const void *)screen << " (" + << (screen ? screen->name() : QString()) << ")"; + return dbg; +} + +QDebug operator<<(QDebug dbg, const QKmsDevice::OrderedScreen &s) +{ + QDebugStateSaver saver(dbg); + dbg.nospace() << "OrderedScreen(" << s.screen << ") : " << s.vinfo.virtualIndex << " / " + << s.vinfo.virtualPos << " / primary: " << s.vinfo.isPrimary << ")"; + return dbg; +} + +bool QKmsDevice::createScreenInfoForConnector(drmModeResPtr resources, + drmModeConnectorPtr connector, ScreenInfo &vinfo) { - Q_ASSERT(vinfo); const QByteArray connectorName = nameForConnector(connector); const int crtc = crtcForConnector(resources, connector); if (crtc < 0) { qWarning() << "No usable crtc/encoder pair for connector" << connectorName; - return nullptr; + return false; } OutputConfiguration configuration; @@ -195,45 +223,54 @@ QPlatformScreen *QKmsDevice::createScreenForConnector(drmModeResPtr resources, configuration = OutputConfigPreferred; } - *vinfo = ScreenInfo(); - vinfo->virtualIndex = userConnectorConfig.value(QStringLiteral("virtualIndex"), INT_MAX).toInt(); + vinfo.virtualIndex = userConnectorConfig.value(QStringLiteral("virtualIndex"), INT_MAX).toInt(); if (userConnectorConfig.contains(QStringLiteral("virtualPos"))) { const QByteArray vpos = userConnectorConfig.value(QStringLiteral("virtualPos")).toByteArray(); const QByteArrayList vposComp = vpos.split(','); - if (vposComp.size() == 2) - vinfo->virtualPos = QPoint(vposComp[0].trimmed().toInt(), vposComp[1].trimmed().toInt()); + if (vposComp.count() == 2) { + vinfo.virtualPos = QPoint(vposComp[0].trimmed().toInt(), vposComp[1].trimmed().toInt()); + qCDebug(qLcKmsDebug) << "Parsing virtualPos to: " << vinfo.virtualPos; + } else { + vinfo.virtualPos = QPoint(-1, -1); + qCDebug(qLcKmsDebug) << "Could not parse virtualPos," + << "will be calculated based on virtualIndex"; + } + } else { + vinfo.virtualPos = QPoint(-1, -1); } + if (userConnectorConfig.value(QStringLiteral("primary")).toBool()) - vinfo->isPrimary = true; + vinfo.isPrimary = true; const uint32_t crtc_id = resources->crtcs[crtc]; if (configuration == OutputConfigOff) { qCDebug(qLcKmsDebug) << "Turning off output" << connectorName; drmModeSetCrtc(m_dri_fd, crtc_id, 0, 0, 0, 0, 0, nullptr); - return nullptr; + return false; } // Skip disconnected output if (configuration == OutputConfigPreferred && connector->connection == DRM_MODE_DISCONNECTED) { qCDebug(qLcKmsDebug) << "Skipping disconnected output" << connectorName; - return nullptr; + return false; } if (configuration == OutputConfigSkip) { qCDebug(qLcKmsDebug) << "Skipping output" << connectorName; - return nullptr; + return false; } // Get the current mode on the current crtc drmModeModeInfo crtc_mode; memset(&crtc_mode, 0, sizeof crtc_mode); if (drmModeEncoderPtr encoder = drmModeGetEncoder(m_dri_fd, connector->encoder_id)) { + drmModeCrtcPtr crtc = drmModeGetCrtc(m_dri_fd, encoder->crtc_id); drmModeFreeEncoder(encoder); if (!crtc) - return nullptr; + return false; if (crtc->mode_valid) crtc_mode = crtc->mode; @@ -303,7 +340,7 @@ QPlatformScreen *QKmsDevice::createScreenForConnector(drmModeResPtr resources, if (selected_mode < 0) { qWarning() << "No modes available for output" << connectorName; - return nullptr; + return false; } else { int width = modes[selected_mode].hdisplay; int height = modes[selected_mode].vdisplay; @@ -504,9 +541,8 @@ QPlatformScreen *QKmsDevice::createScreenForConnector(drmModeResPtr resources, m_crtc_allocator |= (1 << output.crtc_index); - vinfo->output = output; - - return createScreen(output); + vinfo.output = output; + return true; } drmModePropertyPtr QKmsDevice::connectorProperty(drmModeConnectorPtr connector, const QByteArray &name) @@ -566,29 +602,233 @@ QKmsDevice::~QKmsDevice() #endif } -struct OrderedScreen +void QKmsDevice::checkConnectedScreens() { - OrderedScreen() : screen(nullptr) { } - OrderedScreen(QPlatformScreen *screen, const QKmsDevice::ScreenInfo &vinfo) - : screen(screen), vinfo(vinfo) { } - QPlatformScreen *screen; - QKmsDevice::ScreenInfo vinfo; -}; + if (m_screenConfig->headless()) + return; -QDebug operator<<(QDebug dbg, const OrderedScreen &s) -{ - QDebugStateSaver saver(dbg); - dbg.nospace() << "OrderedScreen(QPlatformScreen=" << s.screen << " (" << s.screen->name() << ") : " - << s.vinfo.virtualIndex - << " / " << s.vinfo.virtualPos - << " / primary: " << s.vinfo.isPrimary - << ")"; - return dbg; + drmModeResPtr resources = drmModeGetResources(m_dri_fd); + if (!resources) { + qErrnoWarning(errno, "drmModeGetResources failed"); + return; + } + + QList<uint32_t> newConnects; + QList<uint32_t> newDisconnects; + const QMap<QString, QVariantMap> userConfig = m_screenConfig->outputSettings(); + + for (int i = 0; i < resources->count_connectors; i++) { + drmModeConnectorPtr connector = drmModeGetConnector(m_dri_fd, resources->connectors[i]); + if (!connector) { + qErrnoWarning(errno, "drmModeGetConnector failed"); + continue; + } + + const uint32_t id = connector->connector_id; + + const QByteArray connectorName = nameForConnector(connector); + const QVariantMap userCConfig = userConfig.value(QString::fromUtf8(connectorName)); + const QByteArray mode = userCConfig.value(QStringLiteral("mode")).toByteArray().toLower(); + if (mode == "off" || mode == "skip") + continue; + + if (connector->connection == DRM_MODE_CONNECTED) { + if (!m_registeredScreens.contains(id)) + newConnects.append(id); + else + qCDebug(qLcKmsDebug) << "Connected screen already registered: connector id=" << id; + } + + if (connector->connection == DRM_MODE_DISCONNECTED) { + if (m_registeredScreens.contains(id)) + newDisconnects.append(id); + else + qCDebug(qLcKmsDebug) << "Disconnected screen not registered: connector id=" << id; + } + + drmModeFreeConnector(connector); + } + + if (newConnects.isEmpty() && newDisconnects.isEmpty()) { + qCDebug(qLcKmsDebug) << "EGLFS/KMS: KMS-device-change but no new connects or disconnects " + << "to process - exiting"; + return; + } else { + qCDebug(qLcKmsDebug) << "EGLFS/KMS: KMS-device-change, new connects:" << newConnects + << ", and disconnected: " << newDisconnects; + } + + const int remainingScreenCount = m_registeredScreens.count() - newDisconnects.count(); + if (remainingScreenCount == 0 && m_headlessScreen == nullptr) { + qCDebug(qLcKmsDebug) << "EGLFS/KMS: creating headless screen before" + << "unregistering screens to avoid having no screens"; + m_headlessScreen = createHeadlessScreen(); + registerScreen(m_headlessScreen, true, QPoint(), + QList<QPlatformScreen *>() << m_headlessScreen); + } + + for (uint32_t connectorId : newDisconnects) { + OrderedScreen orderedScreen = m_registeredScreens.take(connectorId); + QPlatformScreen *screen = orderedScreen.screen; + + // Clear active crtc of the plane associated with the screen output + // and, if applicable, disassociate it from the eglfs plane. + uint32_t crtcId = (orderedScreen.vinfo.output.eglfs_plane != nullptr) // if we have an assigned plan + ? orderedScreen.vinfo.output.eglfs_plane->activeCrtcId // we use the active crtc_id to disable everything + : orderedScreen.vinfo.output.crtc_id; // if not, we use the default crtc_id + + if (orderedScreen.vinfo.output.eglfs_plane != nullptr) + orderedScreen.vinfo.output.eglfs_plane->activeCrtcId = 0; + + // Clear crtc allocator bit for screen + const int crtcIdx = orderedScreen.vinfo.output.crtc_index; + m_crtc_allocator &= ~(1 << crtcIdx); + + const int ret = drmModeSetCrtc(m_dri_fd, crtcId, 0, 0, 0, nullptr, 0, nullptr); + + if (ret != 0) { + qCWarning(qLcKmsDebug) << "Could not disable CRTC" << crtcId + << "on connector" << connectorId << "removal:" << ret; + } else { + qCDebug(qLcKmsDebug) << "Disabled CRTC" << crtcId + << "for connector " << connectorId << "disconnected"; + } + + // As we've already turned the crtc off, we don't want to restore the saved_crtc + if (orderedScreen.vinfo.output.saved_crtc) { + drmModeFreeCrtc(orderedScreen.vinfo.output.saved_crtc); + orderedScreen.vinfo.output.saved_crtc = nullptr; + updateScreenOutput(orderedScreen.screen, orderedScreen.vinfo.output); + } + + unregisterScreen(screen); + } + + for (uint32_t connectorId : newConnects) { + drmModeConnectorPtr connector = drmModeGetConnector(m_dri_fd, connectorId); + if (!connector) { + qErrnoWarning(errno, "drmModeGetConnector failed"); + continue; + } + + ScreenInfo vinfo; + bool succ = createScreenInfoForConnector(resources, connector, vinfo); + drmModeFreeConnector(connector); + if (!succ) + continue; + + QPlatformScreen *screen = createScreen(vinfo.output); + if (!screen) + continue; + + OrderedScreen orderedScreen(screen, vinfo); + m_registeredScreens[connectorId] = orderedScreen; + } + + drmModeFreeResources(resources); + + registerScreens(newConnects); } -static bool orderedScreenLessThan(const OrderedScreen &a, const OrderedScreen &b) +void QKmsDevice::updateScreens() { - return a.vinfo.virtualIndex < b.vinfo.virtualIndex; + if (m_screenConfig->headless()) + return; + + drmModeResPtr resources = drmModeGetResources(m_dri_fd); + if (!resources) { + qErrnoWarning(errno, "drmModeGetResources failed"); + return; + } + + QList<uint32_t> newConnects; + QList<OrderedScreen> newDisconnects; + + for (int i = 0; i < resources->count_connectors; i++) { + drmModeConnectorPtr connector = drmModeGetConnector(m_dri_fd, resources->connectors[i]); + if (!connector) + continue; + + if (m_registeredScreens.contains(connector->connector_id)) { + OrderedScreen &os = m_registeredScreens[connector->connector_id]; + + // As we're currently *re*creating the information of an used connector, + // we have to "fake" it being not in use at two places: + // (note: the only thing we'll restore is, in case of failure, the eglfs_plane + // probably not necessary but good practice) + + // 1) crtc_allocator for the crtc + const int crtcIdx = os.vinfo.output.crtc_index; + m_crtc_allocator &= ~(1 << crtcIdx); + + // 2) the plane itself + if (os.vinfo.output.eglfs_plane) + os.vinfo.output.eglfs_plane->activeCrtcId = 0; + + // We also save the saved crtc to restore it in case of success + // (otherwise QKmsOutput::restoreMode would restore to a second-latest crtc, + // rather then the original one) + drmModeCrtcPtr saved_saved_crtc = nullptr; + if (os.vinfo.output.saved_crtc) + saved_saved_crtc = os.vinfo.output.saved_crtc; + + ScreenInfo vinfo; + bool succ = createScreenInfoForConnector(resources, connector, vinfo); + if (!succ) { + // Here we either failed the recreate, or the config turns the screen off. + // In either case, we'll treat it as a disconnect + + // Either this connector is disconnected, broken or turned off + // In all those cases we don't need or want to restore the previous mode + if (os.vinfo.output.saved_crtc) { + drmModeFreeCrtc(os.vinfo.output.saved_crtc); + os.vinfo.output.saved_crtc = nullptr; + updateScreenOutput(os.screen, os.vinfo.output); + } + + // move from one container to another - we don't want registerScreens + // to deal with this, but need to call registerScreens before the disconnects + newDisconnects.append(m_registeredScreens.take(connector->connector_id)); + drmModeFreeConnector(connector); + continue; + } + drmModeFreeConnector(connector); + + drmModeFreeCrtc(vinfo.output.saved_crtc); + vinfo.output.saved_crtc = saved_saved_crtc; // This is vital as config changes should + // never override the original saved_crtc + os.vinfo = vinfo; + updateScreenOutput(os.screen, os.vinfo.output); + + } else { + ScreenInfo vinfo; + bool succ = createScreenInfoForConnector(resources, connector, vinfo); + if (!succ) // If we fail here we do nothing, as there is nothing to restore or cleanup + continue; + + QPlatformScreen *screen = createScreen(vinfo.output); + OrderedScreen orderedScreen(screen, vinfo); + m_registeredScreens[connector->connector_id] = orderedScreen; + newConnects.append(connector->connector_id); + } + } + + // In case we end up with zero screen, we do the fallback first + if (m_registeredScreens.count() == 0 && m_headlessScreen == nullptr) { + // Create headless screen before unregistering screens to avoid having no screens + m_headlessScreen = createHeadlessScreen(); + registerScreen(m_headlessScreen, true, QPoint(), + QList<QPlatformScreen *>() << m_headlessScreen); + } + + // Register new and updates existing screens + registerScreens(newConnects); + + // Last we unregister the disconncted ones + for (const OrderedScreen &os : newDisconnects) + unregisterScreen(os.screen); + + drmModeFreeResources(resources); } void QKmsDevice::createScreens() @@ -599,7 +839,8 @@ void QKmsDevice::createScreens() QPlatformScreen *screen = createHeadlessScreen(); if (screen) { qCDebug(qLcKmsDebug, "Headless mode enabled"); - registerScreen(screen, true, QPoint(0, 0), QList<QPlatformScreen *>()); + registerScreen(screen, true, QPoint(0, 0), + QList<QPlatformScreen *>() << screen); return; } else { qWarning("QKmsDevice: Requested headless mode without support in the backend. Request is ignored."); @@ -630,8 +871,7 @@ void QKmsDevice::createScreens() discoverPlanes(); - QList<OrderedScreen> screens; - + QList<uint32_t> newConnects; int wantedConnectorIndex = -1; bool ok; int idx = qEnvironmentVariableIntValue("QT_QPA_EGLFS_KMS_CONNECTOR_INDEX", &ok); @@ -647,19 +887,47 @@ void QKmsDevice::createScreens() continue; drmModeConnectorPtr connector = drmModeGetConnector(m_dri_fd, resources->connectors[i]); - if (!connector) + if (!connector) { + qErrnoWarning(errno, "drmModeGetConnector failed"); continue; + } ScreenInfo vinfo; - QPlatformScreen *screen = createScreenForConnector(resources, connector, &vinfo); - if (screen) - screens.append(OrderedScreen(screen, vinfo)); - + bool succ = createScreenInfoForConnector(resources, connector, vinfo); + uint32_t connectorId = connector->connector_id; drmModeFreeConnector(connector); + if (!succ) + continue; + + QPlatformScreen *screen = createScreen(vinfo.output); + if (!screen) + continue; + + OrderedScreen orderedScreen(screen, vinfo); + m_registeredScreens[connectorId] = orderedScreen; + newConnects.append(connectorId); } drmModeFreeResources(resources); + if (!qEnvironmentVariable("QT_QPA_EGLFS_HOTPLUG_ENABLED").isEmpty() + && newConnects.empty() && m_headlessScreen == nullptr) { + qCDebug(qLcKmsDebug) << "'QT_QPA_EGLFS_HOTPLUG_ENABLED' was set and no screen was connected/found during start-up." + << "In order for Qt to operate properly a qt_headless screen will be created." + << "It will be automatically removed as soon as the first screen is connected"; + // Create headless screen before unregistering screens to avoid having no screens + m_headlessScreen = createHeadlessScreen(); + registerScreen(m_headlessScreen, true, QPoint(), + QList<QPlatformScreen *>() << m_headlessScreen); + } + + registerScreens(newConnects); +} + +void QKmsDevice::registerScreens(QList<uint32_t> newConnects) +{ + QList<OrderedScreen> screens = m_registeredScreens.values(); + // Use stable sort to preserve the original (DRM connector) order // for outputs with unspecified indices. std::stable_sort(screens.begin(), screens.end(), orderedScreenLessThan); @@ -692,44 +960,129 @@ void QKmsDevice::createScreens() // Figure out the virtual desktop and register the screens to QPA/QGuiApplication. QPoint pos(0, 0); - QList<QPlatformScreen *> siblings; + QList<OrderedScreen> siblings; QList<QPoint> virtualPositions; int primarySiblingIdx = -1; + QRegion deskRegion; for (const OrderedScreen &orderedScreen : screens) { QPlatformScreen *s = orderedScreen.screen; QPoint virtualPos(0, 0); // set up a horizontal or vertical virtual desktop - if (orderedScreen.vinfo.virtualPos.isNull()) { - virtualPos = pos; - if (m_screenConfig->virtualDesktopLayout() == QKmsScreenConfig::VirtualDesktopLayoutVertical) - pos.ry() += s->geometry().height(); - else - pos.rx() += s->geometry().width(); + if (orderedScreen.vinfo.virtualPos.x() == -1 || orderedScreen.vinfo.virtualPos.y() == -1) { + if (orderedScreen.vinfo.output.clone_source.isEmpty()) { + virtualPos = pos; + if (m_screenConfig->virtualDesktopLayout() == QKmsScreenConfig::VirtualDesktopLayoutVertical) + pos.ry() += s->geometry().height(); + else + pos.rx() += s->geometry().width(); + } else { + for (int i = 0; i < screens.count(); i++) { + const OrderedScreen &os = screens[i]; + if (os.vinfo.output.name == orderedScreen.vinfo.output.clone_source) { + if (i >= virtualPositions.count()) { + qCWarning(qLcKmsDebug) + << "WARNING: When using clone on kms config," + << "you have to either order your screens (virtualIndex)," + << "so clones come after their source," + << "or specify 'virtualPos' for each clone." + << "Otherwise desktop-geomerty might not work properly!"; + virtualPos = pos; + } else { + virtualPos = virtualPositions[i]; + } + break; + } + } + } } else { virtualPos = orderedScreen.vinfo.virtualPos; } - qCDebug(qLcKmsDebug) << "Adding QPlatformScreen" << s << "(" << s->name() << ")" - << "to QPA with geometry" << s->geometry() - << "and isPrimary=" << orderedScreen.vinfo.isPrimary; + // The order in qguiapp's screens list will match the order set by // virtualIndex. This is not only handy but also required since for instance // evdevtouch relies on it when performing touch device - screen mapping. if (!m_screenConfig->separateScreens()) { - qCDebug(qLcKmsDebug) << " virtual position is" << virtualPos; - siblings.append(s); + siblings.append(orderedScreen); virtualPositions.append(virtualPos); if (orderedScreen.vinfo.isPrimary) primarySiblingIdx = siblings.size() - 1; } else { - registerScreen(s, orderedScreen.vinfo.isPrimary, virtualPos, QList<QPlatformScreen *>() << s); + const bool isNewScreen = newConnects.contains(orderedScreen.vinfo.output.connector_id); + if (isNewScreen) { + qCDebug(qLcKmsDebug) << "Adding QPlatformScreen" << s << "(" << s->name() << ")" + << "to QPA with geometry" << s->geometry() + << ", virtual position" << virtualPos + << "and isPrimary=" << orderedScreen.vinfo.isPrimary; + registerScreen(s, orderedScreen.vinfo.isPrimary, virtualPos, + QList<QPlatformScreen *>() << s); + deskRegion += s->geometry(); + } else { + qCDebug(qLcKmsDebug) << "Updating QPlatformScreen" << s << "(" << s->name() << ")" + << "to QPA with geometry" << s->geometry() + << ", virtual position" << virtualPos + << "and isPrimary=" << orderedScreen.vinfo.isPrimary; + updateScreen(s, virtualPos, QList<QPlatformScreen *>() << s); + deskRegion += s->geometry(); + } } } if (!m_screenConfig->separateScreens()) { + QList<QPlatformScreen *> platformScreenSiblings; + for (int i = 0; i < siblings.count(); ++i) { + platformScreenSiblings.append(siblings[i].screen); + } + // enable the virtual desktop - for (int i = 0; i < siblings.size(); ++i) - registerScreen(siblings[i], i == primarySiblingIdx, virtualPositions[i], siblings); + for (int i = 0; i < siblings.count(); ++i) { + QPlatformScreen *screen = platformScreenSiblings[i]; + const OrderedScreen &orderedScreen = siblings[i]; + const bool isNewScreen = newConnects.contains(orderedScreen.vinfo.output.connector_id); + if (isNewScreen) { + qCDebug(qLcKmsDebug) << "Adding QPlatformScreen" << screen + << "(" << screen->name() << ")" + << "to QPA with geometry" << screen->geometry() + << ", virtual position" << virtualPositions[i] + << "and isPrimary=" << orderedScreen.vinfo.isPrimary; + registerScreen(screen, i == primarySiblingIdx, virtualPositions[i], + platformScreenSiblings); + deskRegion += screen->geometry(); + } else { + qCDebug(qLcKmsDebug) << "Updating QPlatformScreen" << screen + << "(" << screen->name() << ")" + << "to QPA with geometry" << screen->geometry() + << ", virtual position" << virtualPositions[i] + << "and isPrimary=" << orderedScreen.vinfo.isPrimary; + updateScreen(screen, virtualPositions[i], platformScreenSiblings); + deskRegion += screen->geometry(); + } + } + } + + // Remove headless screen if other screens have become available + if (!m_registeredScreens.empty() && m_headlessScreen) { + unregisterScreen(m_headlessScreen); + m_headlessScreen = nullptr; + } + + // Due to layout changes it's possible that we have to reset/bound + // the cursor into the available space (otherwise the cursor might vanish) + QPoint currCPos = QCursor::pos(); + if (!deskRegion.contains(currCPos)) { + + // We try boudingRect first + QRect deskRect = deskRegion.boundingRect(); + currCPos.setX(qMin(currCPos.x(), deskRect.width()) - 1); + currCPos.setY(qMin(currCPos.y(), deskRect.height()) - 1); + + // If boudingRect isn't good enough, we go to 0 + if (!deskRegion.contains(currCPos)) + currCPos = QPoint(0,0); + + qCDebug(qLcKmsDebug) << "Due to desktop layout change, overriding cursor pos." + << "Is: " << QCursor::pos() << ", will be: " << currCPos; + QCursor::setPos(currCPos); } } @@ -749,6 +1102,25 @@ void QKmsDevice::registerScreenCloning(QPlatformScreen *screen, Q_UNUSED(screensCloningThisScreen); } +void QKmsDevice::unregisterScreen(QPlatformScreen *screen) +{ + Q_UNUSED(screen); +} + +void QKmsDevice::updateScreen(QPlatformScreen *screen, const QPoint &virtualPos, + const QList<QPlatformScreen *> &virtualSiblings) +{ + Q_UNUSED(screen); + Q_UNUSED(virtualPos); + Q_UNUSED(virtualSiblings); +} + +void QKmsDevice::updateScreenOutput(QPlatformScreen *screen, const QKmsOutput &output) +{ + Q_UNUSED(screen); + Q_UNUSED(output); +} + // drm_property_type_is is not available in old headers static inline bool propTypeIs(drmModePropertyPtr prop, uint32_t type) { @@ -1002,6 +1374,12 @@ QKmsScreenConfig::QKmsScreenConfig() { } +void QKmsScreenConfig::refreshConfig() +{ + m_outputSettings.clear(); + loadConfig(); +} + void QKmsScreenConfig::loadConfig() { QByteArray json = qgetenv("QT_QPA_EGLFS_KMS_CONFIG"); @@ -1039,6 +1417,11 @@ void QKmsScreenConfig::loadConfig() m_headless = false; } + const QString headlessSizeStr = object.value(QLatin1String("headlessSize")).toString(); + if (sscanf(headlessSizeStr.toUtf8().constData(), "%dx%d", &headlessSize.rwidth(), + &headlessSize.rheight()) == 2) + m_headlessSize = headlessSize; + m_hwCursor = object.value("hwcursor"_L1).toBool(m_hwCursor); m_pbuffers = object.value("pbuffers"_L1).toBool(m_pbuffers); m_devicePath = object.value("device"_L1).toString(); diff --git a/src/platformsupport/kmsconvenience/qkmsdevice_p.h b/src/platformsupport/kmsconvenience/qkmsdevice_p.h index 050d836cb18..3e6ec108175 100644 --- a/src/platformsupport/kmsconvenience/qkmsdevice_p.h +++ b/src/platformsupport/kmsconvenience/qkmsdevice_p.h @@ -88,11 +88,12 @@ public: QMap<QString, QVariantMap> outputSettings() const { return m_outputSettings; } virtual void loadConfig(); + void refreshConfig(); protected: QString m_devicePath; bool m_headless; - QSize m_headlessSize; + QSize m_headlessSize{ 1024, 768 }; bool m_hwCursor; bool m_separateScreens; bool m_pbuffers; @@ -196,6 +197,14 @@ public: QKmsOutput output; }; + struct OrderedScreen + { + OrderedScreen(); + OrderedScreen(QPlatformScreen *screen, const ScreenInfo &vinfo); + QPlatformScreen *screen = nullptr; + ScreenInfo vinfo; + }; + QKmsDevice(QKmsScreenConfig *screenConfig, const QString &path = QString()); virtual ~QKmsDevice(); @@ -210,6 +219,8 @@ public: bool threadLocalAtomicCommit(void *user_data); void threadLocalAtomicReset(); #endif + void checkConnectedScreens(); + void updateScreens(); void createScreens(); int fd() const; @@ -218,6 +229,7 @@ public: QKmsScreenConfig *screenConfig() const; protected: + void registerScreens(QList<uint32_t> newConnects = QList<uint32_t>()); virtual QPlatformScreen *createScreen(const QKmsOutput &output) = 0; virtual QPlatformScreen *createHeadlessScreen(); virtual void registerScreenCloning(QPlatformScreen *screen, @@ -227,12 +239,15 @@ protected: bool isPrimary, const QPoint &virtualPos, const QList<QPlatformScreen *> &virtualSiblings) = 0; + virtual void unregisterScreen(QPlatformScreen *screen); + virtual void updateScreen(QPlatformScreen *screen, const QPoint &virtualPos, + const QList<QPlatformScreen *> &virtualSiblings); + virtual void updateScreenOutput(QPlatformScreen *screen, const QKmsOutput &output); void setFd(int fd); int crtcForConnector(drmModeResPtr resources, drmModeConnectorPtr connector); - QPlatformScreen *createScreenForConnector(drmModeResPtr resources, - drmModeConnectorPtr connector, - ScreenInfo *vinfo); + bool createScreenInfoForConnector(drmModeResPtr resources, drmModeConnectorPtr connector, + ScreenInfo &vinfo); drmModePropertyPtr connectorProperty(drmModeConnectorPtr connector, const QByteArray &name); drmModePropertyBlobPtr connectorPropertyBlob(drmModeConnectorPtr connector, const QByteArray &name); typedef std::function<void(drmModePropertyPtr, quint64)> PropCallback; @@ -257,6 +272,8 @@ protected: quint32 m_crtc_allocator; QList<QKmsPlane> m_planes; + QMap<uint32_t, OrderedScreen> m_registeredScreens; + QPlatformScreen *m_headlessScreen = nullptr; private: Q_DISABLE_COPY(QKmsDevice) diff --git a/src/plugins/platforms/eglfs/api/qeglfscontext.cpp b/src/plugins/platforms/eglfs/api/qeglfscontext.cpp index 9c10c1a998c..0b9db8039f1 100644 --- a/src/plugins/platforms/eglfs/api/qeglfscontext.cpp +++ b/src/plugins/platforms/eglfs/api/qeglfscontext.cpp @@ -79,8 +79,10 @@ void QEglFSContext::swapBuffers(QPlatformSurface *surface) // draw the cursor if (surface->surface()->surfaceClass() == QSurface::Window) { QPlatformWindow *window = static_cast<QPlatformWindow *>(surface); - if (QEglFSCursor *cursor = qobject_cast<QEglFSCursor *>(window->screen()->cursor())) - cursor->paintOnScreen(); + if (QPlatformScreen *screen = window->screen()) { + if (QEglFSCursor *cursor = qobject_cast<QEglFSCursor *>(screen->cursor())) + cursor->paintOnScreen(); + } } qt_egl_device_integration()->waitForVSync(surface); diff --git a/src/plugins/platforms/eglfs/api/qeglfsintegration.cpp b/src/plugins/platforms/eglfs/api/qeglfsintegration.cpp index 4abe948117e..2f278a474e0 100644 --- a/src/plugins/platforms/eglfs/api/qeglfsintegration.cpp +++ b/src/plugins/platforms/eglfs/api/qeglfsintegration.cpp @@ -154,6 +154,7 @@ QPlatformBackingStore *QEglFSIntegration::createPlatformBackingStore(QWindow *wi if (!window->handle()) window->create(); static_cast<QEglFSWindow *>(window->handle())->setBackingStore(bs); + m_bs = bs; return bs; #else Q_UNUSED(window); @@ -175,6 +176,9 @@ QPlatformWindow *QEglFSIntegration::createPlatformWindow(QWindow *window) const if (window->type() != Qt::ToolTip && window->screen() == QGuiApplication::primaryScreen()) w->requestActivateWindow(); + if (window->isTopLevel()) + w->setBackingStore(static_cast<QOpenGLCompositorBackingStore *>(m_bs)); + return w; } diff --git a/src/plugins/platforms/eglfs/api/qeglfsintegration_p.h b/src/plugins/platforms/eglfs/api/qeglfsintegration_p.h index 2359b7f29f1..3865b7130b7 100644 --- a/src/plugins/platforms/eglfs/api/qeglfsintegration_p.h +++ b/src/plugins/platforms/eglfs/api/qeglfsintegration_p.h @@ -112,6 +112,7 @@ private: QScopedPointer<QFbVtHandler> m_vtHandler; QPointer<QWindow> m_pointerWindow; bool m_disableInputHandlers; + mutable QPlatformBackingStore *m_bs = nullptr; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmcursor_p.h b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmcursor_p.h index a0f78bb3103..cca9097e2f0 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmcursor_p.h +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmcursor_p.h @@ -62,6 +62,8 @@ public: void reevaluateVisibilityForScreens() { setPos(pos()); } + QEglFSKmsGbmScreen *screen() const { return m_screen; } + private: void initCursorAtlas(); diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.cpp index a7592ed55e4..9f19e649f85 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.cpp +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.cpp @@ -113,13 +113,27 @@ QPlatformScreen *QEglFSKmsGbmDevice::createScreen(const QKmsOutput &output) { QEglFSKmsGbmScreen *screen = new QEglFSKmsGbmScreen(this, output, false); - createGlobalCursor(screen); + + // On some platforms (e.g. rpi4), you'll get a kernel warning/error + // if the cursor is created 'at the same time' as the screen is created. + // (drmModeMoveCursor is the specific call that causes the issue) + // When this issue is triggered, the screen's connector is unusable until reboot + // + // Below is a work-around (without negative implications for other platforms). + // + // interval of 0 and QMetaObject::invokeMethod (w/o Qt::QueuedConnection) + // do no help / will still trigger issue + QTimer::singleShot(1, [screen, this](){ + createGlobalCursor(screen); + }); return screen; } QPlatformScreen *QEglFSKmsGbmDevice::createHeadlessScreen() { + destroyGlobalCursor(); + return new QEglFSKmsGbmScreen(this, QKmsOutput(), true); } @@ -127,9 +141,6 @@ void QEglFSKmsGbmDevice::registerScreenCloning(QPlatformScreen *screen, QPlatformScreen *screenThisScreenClones, const QList<QPlatformScreen *> &screensCloningThisScreen) { - if (!screenThisScreenClones && screensCloningThisScreen.isEmpty()) - return; - QEglFSKmsGbmScreen *gbmScreen = static_cast<QEglFSKmsGbmScreen *>(screen); gbmScreen->initCloning(screenThisScreenClones, screensCloningThisScreen); } @@ -144,6 +155,32 @@ void QEglFSKmsGbmDevice::registerScreen(QPlatformScreen *screen, m_globalCursor->reevaluateVisibilityForScreens(); } +void QEglFSKmsGbmDevice::unregisterScreen(QPlatformScreen *screen) +{ + // The global cursor holds a pointer to a QEglFSKmsGbmScreen. + // If that screen is being unregistered, + // this will recreate the global cursor with the first sibling screen. + if (m_globalCursor && screen == m_globalCursor->screen()) { + qCDebug(qLcEglfsKmsDebug) << "Destroying global GBM mouse cursor due to unregistering" + << "it's screen - will probably be recreated right away"; + delete m_globalCursor; + m_globalCursor = nullptr; + + QList<QPlatformScreen *> siblings = screen->virtualSiblings(); + siblings.removeOne(screen); + if (siblings.count() > 0) { + QEglFSKmsGbmScreen *kmsScreen = static_cast<QEglFSKmsGbmScreen *>(siblings.first()); + m_globalCursor = new QEglFSKmsGbmCursor(kmsScreen); + qCDebug(qLcEglfsKmsDebug) << "Creating new global GBM mouse cursor on sibling screen"; + } else { + qCWarning(qLcEglfsKmsDebug) << "Couldn't find a sibling to recreate" + << "the GBM mouse cursor - it might vanish"; + } + } + + QEglFSKmsDevice::unregisterScreen(screen); +} + bool QEglFSKmsGbmDevice::usesEventReader() const { static const bool eventReaderThreadDisabled = qEnvironmentVariableIntValue("QT_QPA_EGLFS_KMS_NO_EVENT_READER_THREAD"); diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice_p.h b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice_p.h index e00992ed291..0ffed0ec4ef 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice_p.h +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice_p.h @@ -51,6 +51,7 @@ public: bool isPrimary, const QPoint &virtualPos, const QList<QPlatformScreen *> &virtualSiblings) override; + void unregisterScreen(QPlatformScreen *screen) override; bool usesEventReader() const; QEglFSKmsEventReader *eventReader() { return &m_eventReader; } diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmintegration.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmintegration.cpp index 05ffb3b212e..eb61de3c534 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmintegration.cpp +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmintegration.cpp @@ -11,6 +11,7 @@ #include "private/qeglfscursor_p.h" #include <QtCore/QLoggingCategory> +#include <QtCore/QFileSystemWatcher> #include <QtGui/QScreen> #include <QtDeviceDiscoverySupport/private/qdevicediscovery_p.h> @@ -23,6 +24,10 @@ QEglFSKmsGbmIntegration::QEglFSKmsGbmIntegration() qCDebug(qLcEglfsKmsDebug, "New DRM/KMS via GBM integration created"); } +QEglFSKmsGbmIntegration::~QEglFSKmsGbmIntegration() +{ +} + #ifndef EGL_EXT_platform_base typedef EGLDisplay (EGLAPIENTRYP PFNEGLGETPLATFORMDISPLAYEXTPROC) (EGLenum platform, void *native_display, const EGLint *attrib_list); #endif @@ -94,14 +99,16 @@ void QEglFSKmsGbmIntegration::presentBuffer(QPlatformSurface *surface) QKmsDevice *QEglFSKmsGbmIntegration::createDevice() { + + m_deviceDiscovery = std::unique_ptr<QDeviceDiscovery>(QDeviceDiscovery::create(QDeviceDiscovery::Device_VideoMask)); + m_kmsConfigWatcher = std::unique_ptr<QFileSystemWatcher>(new QFileSystemWatcher()); + QString path = screenConfig()->devicePath(); if (!path.isEmpty()) { qCDebug(qLcEglfsKmsDebug) << "GBM: Using DRM device" << path << "specified in config file"; } else { - QDeviceDiscovery *d = QDeviceDiscovery::create(QDeviceDiscovery::Device_VideoMask); - const QStringList devices = d->scanConnectedDevices(); + const QStringList devices = m_deviceDiscovery->scanConnectedDevices(); qCDebug(qLcEglfsKmsDebug) << "Found the following video devices:" << devices; - d->deleteLater(); if (Q_UNLIKELY(devices.isEmpty())) qFatal("Could not find DRM device!"); @@ -110,6 +117,35 @@ QKmsDevice *QEglFSKmsGbmIntegration::createDevice() qCDebug(qLcEglfsKmsDebug) << "Using" << path; } + bool hotreload = !qEnvironmentVariable("QT_QPA_EGLFS_HOTPLUG_ENABLED").isEmpty(); + if (hotreload) { + qCWarning(qLcEglfsKmsDebug) << "EGLFS/KMS: Hot-Reload on KMS-events enabled, be aware that" + << "this requires actions in UI code for proper functionallity" + << "(e.g. close/open windows on screen's disconnect/connect)"; + QObject::connect(m_deviceDiscovery.get(), &QDeviceDiscovery::deviceChanged, + m_deviceDiscovery.get(), [this](const QString &deviceNode) { + qCDebug(qLcEglfsKmsDebug) << "KMS device changed:" << deviceNode; + m_device->checkConnectedScreens(); + }); + } + + QString json = qEnvironmentVariable("QT_QPA_EGLFS_KMS_CONFIG"); + if (json.isEmpty()) + json = qEnvironmentVariable("QT_QPA_KMS_CONFIG"); + + if (!json.isEmpty()) { + m_kmsConfigWatcher->addPath(json); + QObject::connect(m_kmsConfigWatcher.get(), &QFileSystemWatcher::fileChanged, + m_kmsConfigWatcher.get(), [this, json]() { + qCDebug(qLcEglfsKmsDebug) << "KMS config-file has changed! path:" + << json; + m_screenConfig->refreshConfig(); + m_device->updateScreens(); + m_kmsConfigWatcher->addPath(json); // as per QFileSystemWatcher doc we have to re-add + // the path in case it's a new file + }); + } + return new QEglFSKmsGbmDevice(screenConfig(), path); } diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmintegration_p.h b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmintegration_p.h index fb118438d25..7c2c2a474d7 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmintegration_p.h +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmintegration_p.h @@ -24,11 +24,14 @@ QT_BEGIN_NAMESPACE class QEglFSKmsDevice; +class QDeviceDiscovery; +class QFileSystemWatcher; class Q_EGLFS_EXPORT QEglFSKmsGbmIntegration : public QEglFSKmsIntegration { public: QEglFSKmsGbmIntegration(); + ~QEglFSKmsGbmIntegration() override; EGLDisplay createDisplay(EGLNativeDisplayType nativeDisplay) override; EGLNativeWindowType createNativeOffscreenWindow(const QSurfaceFormat &format) override; @@ -42,6 +45,8 @@ protected: QKmsDevice *createDevice() override; private: + std::unique_ptr<QDeviceDiscovery> m_deviceDiscovery; + std::unique_ptr<QFileSystemWatcher> m_kmsConfigWatcher; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp index 00fecb87f1f..332030f03f2 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp @@ -20,7 +20,7 @@ QT_BEGIN_NAMESPACE -QMutex QEglFSKmsGbmScreen::m_nonThreadedFlipMutex; +QMutex QEglFSKmsGbmScreen::s_nonThreadedFlipMutex; static inline uint32_t drmFormatToGbmFormat(uint32_t drmFormat) { @@ -92,9 +92,26 @@ QEglFSKmsGbmScreen::QEglFSKmsGbmScreen(QEglFSKmsDevice *device, const QKmsOutput QEglFSKmsGbmScreen::~QEglFSKmsGbmScreen() { const int remainingScreenCount = qGuiApp->screens().count(); - qCDebug(qLcEglfsKmsDebug, "Screen dtor. Remaining screens: %d", remainingScreenCount); + qCDebug(qLcEglfsKmsDebug, "Screen dtor. %p Remaining screens: %d", this, remainingScreenCount); if (!remainingScreenCount && !device()->screenConfig()->separateScreens()) static_cast<QEglFSKmsGbmDevice *>(device())->destroyGlobalCursor(); + + if (m_cloneSource) { + // Remove this screen from the screen that has it as a clone destination + QList<CloneDestination> &dests = m_cloneSource->m_cloneDests; + auto newEnd = std::remove_if(dests.begin(), dests.end(), + [this](CloneDestination &dest) { + return dest.screen == this; + }); + dests.erase(newEnd, dests.end()); + } + + // Other screens can no longer have this screen as a clone source + for (CloneDestination &dest : m_cloneDests) { + dest.screen->m_cloneSource = nullptr; + // Mode must be set again before flipping + dest.screen->m_output.mode_set = false; + } } QPlatformCursor *QEglFSKmsGbmScreen::cursor() const @@ -206,9 +223,12 @@ void QEglFSKmsGbmScreen::initCloning(QPlatformScreen *screenThisScreenClones, if (clonesAnother) { m_cloneSource = static_cast<QEglFSKmsGbmScreen *>(screenThisScreenClones); qCDebug(qLcEglfsKmsDebug, "Screen %s clones %s", qPrintable(name()), qPrintable(m_cloneSource->name())); + } else { + m_cloneSource = nullptr; } // clone sources need to know their additional destinations + m_cloneDests.clear(); for (QPlatformScreen *s : screensCloningThisScreen) { CloneDestination d; d.screen = static_cast<QEglFSKmsGbmScreen *>(s); @@ -271,8 +291,11 @@ void QEglFSKmsGbmScreen::nonThreadedPageFlipHandler(int fd, // note that with cloning involved this callback is called also for screens that clone another one Q_UNUSED(fd); QEglFSKmsGbmScreen *screen = static_cast<QEglFSKmsGbmScreen *>(user_data); - screen->flipFinished(); - screen->pageFlipped(sequence, tv_sec, tv_usec); + // The screen might have been deleted when DRM calls this handler + if (QEglFSKmsScreen::isScreenKnown(screen)) { + screen->flipFinished(); + screen->pageFlipped(sequence, tv_sec, tv_usec); + } } void QEglFSKmsGbmScreen::waitForFlipWithEventReader(QEglFSKmsGbmScreen *screen) @@ -280,7 +303,21 @@ void QEglFSKmsGbmScreen::waitForFlipWithEventReader(QEglFSKmsGbmScreen *screen) m_flipMutex.lock(); QEglFSKmsGbmDevice *dev = static_cast<QEglFSKmsGbmDevice *>(device()); dev->eventReader()->startWaitFlip(screen, &m_flipMutex, &m_flipCond); - m_flipCond.wait(&m_flipMutex); + + // We should only wait forever on this screen, clones should have a timeout + // (e.g. I clone might have been created just before the flip, + // we might wait for it but it might not know about waking us up) + bool succ = false; + if (screen == this) + succ = m_flipCond.wait(&m_flipMutex); + else + succ = m_flipCond.wait(&m_flipMutex, 300); + + if (!succ) + qCWarning(qLcEglfsKmsDebug) << "timeout on waitForFlipWithEventReader, screen to wait for:" + << screen << ", screen waiting (shouldn't be the same screen):" + << this; + m_flipMutex.unlock(); screen->flipFinished(); } @@ -306,7 +343,7 @@ void QEglFSKmsGbmScreen::waitForFlip() waitForFlipWithEventReader(d.screen); } } else { - QMutexLocker lock(&m_nonThreadedFlipMutex); + QMutexLocker lock(&s_nonThreadedFlipMutex); while (m_gbm_bo_next) { drmEventContext drmEvent; memset(&drmEvent, 0, sizeof(drmEvent)); @@ -359,15 +396,10 @@ static void addAtomicFlip(drmModeAtomicReq *request, const QKmsOutput &output, u void QEglFSKmsGbmScreen::flip() { - // For headless screen just return silently. It is not necessarily an error + // For headless or cloned screen just return silently. It is not necessarily an error // to end up here, so show no warnings. - if (m_headless) - return; - - if (m_cloneSource) { - qWarning("Screen %s clones another screen. swapBuffers() not allowed.", qPrintable(name())); + if (m_headless || m_cloneSource) return; - } if (!m_gbm_surface) { qWarning("Cannot sync before platform init!"); diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen_p.h b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen_p.h index aca34fcae21..65625a3c1cd 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen_p.h +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen_p.h @@ -67,7 +67,7 @@ protected: QMutex m_flipMutex; QWaitCondition m_flipCond; - static QMutex m_nonThreadedFlipMutex; + static QMutex s_nonThreadedFlipMutex; QScopedPointer<QEglFSKmsGbmCursor> m_cursor; diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldeviceintegration.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldeviceintegration.cpp index ece19f46a49..ff4921c2b15 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldeviceintegration.cpp +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldeviceintegration.cpp @@ -213,9 +213,13 @@ QEglFSWindow *QEglFSKmsEglDeviceIntegration::createWindow(QWindow *window) const QEglFSKmsEglDeviceWindow *eglWindow = new QEglFSKmsEglDeviceWindow(window, this); m_funcs->initialize(eglWindow->screen()->display()); - if (Q_UNLIKELY(!(m_funcs->has_egl_output_base && m_funcs->has_egl_output_drm && m_funcs->has_egl_stream && - m_funcs->has_egl_stream_producer_eglsurface && m_funcs->has_egl_stream_consumer_egloutput))) + if (Q_UNLIKELY(!(m_funcs->has_egl_output_base && m_funcs->has_egl_output_drm + && m_funcs->has_egl_stream && m_funcs->has_egl_stream_producer_eglsurface + && m_funcs->has_egl_stream_consumer_egloutput))) { + qCDebug(qLcEglfsKmsDebug, "EGL_EXTENSIONS %s", + eglQueryString(eglWindow->screen()->display(), EGL_EXTENSIONS)); qFatal("Required extensions missing!"); + } return eglWindow; } diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldevicescreen.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldevicescreen.cpp index 5af45e63a2f..5775ac3607a 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldevicescreen.cpp +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldevicescreen.cpp @@ -71,7 +71,7 @@ QEglFSKmsEglDeviceScreen::~QEglFSKmsEglDeviceScreen() } const int remainingScreenCount = qGuiApp->screens().size(); - qCDebug(qLcEglfsKmsDebug, "Screen dtor. Remaining screens: %d", remainingScreenCount); + qCDebug(qLcEglfsKmsDebug, "Screen dtor. %p Remaining screens: %d", this, remainingScreenCount); if (!remainingScreenCount && !device()->screenConfig()->separateScreens()) static_cast<QEglFSKmsEglDevice *>(device())->destroyGlobalCursor(); } diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsdevice.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsdevice.cpp index 037b26f023e..59ca53355d6 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsdevice.cpp +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsdevice.cpp @@ -25,4 +25,39 @@ void QEglFSKmsDevice::registerScreen(QPlatformScreen *screen, QWindowSystemInterface::handleScreenAdded(s, isPrimary); } +void QEglFSKmsDevice::unregisterScreen(QPlatformScreen *screen) +{ + QEglFSKmsScreen *s = static_cast<QEglFSKmsScreen *>(screen); + for (QPlatformScreen *sibling : s->virtualSiblings()) + static_cast<QEglFSKmsScreen *>(sibling)->removeSibling(s); + + QWindowSystemInterface::handleScreenRemoved(screen); +} + +void QEglFSKmsDevice::updateScreen(QPlatformScreen *screen, const QPoint &virtualPos, + const QList<QPlatformScreen *> &virtualSiblings) +{ + QEglFSKmsScreen *s = static_cast<QEglFSKmsScreen *>(screen); + QRect before = s->geometry(); + s->setVirtualPosition(virtualPos); + s->setVirtualSiblings(virtualSiblings); + QRect after = s->geometry(); + + if (before != after) + QWindowSystemInterface::handleScreenGeometryChange(s->screen(), after, + s->availableGeometry()); +} + +void QEglFSKmsDevice::updateScreenOutput(QPlatformScreen *screen, const QKmsOutput &output) +{ + QEglFSKmsScreen *s = static_cast<QEglFSKmsScreen *>(screen); + QRect before = s->geometry(); + s->updateOutput(output); + QRect after = s->geometry(); + + if (before != after) + QWindowSystemInterface::handleScreenGeometryChange(s->screen(), after, + s->availableGeometry()); +} + QT_END_NAMESPACE diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsdevice_p.h b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsdevice_p.h index 6e11953a699..49b82d8baad 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsdevice_p.h +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsdevice_p.h @@ -30,6 +30,13 @@ public: bool isPrimary, const QPoint &virtualPos, const QList<QPlatformScreen *> &virtualSiblings) override; + + void unregisterScreen(QPlatformScreen *screen) override; + + void updateScreen(QPlatformScreen *screen, const QPoint &virtualPos, + const QList<QPlatformScreen *> &virtualSiblings) override; + + void updateScreenOutput(QPlatformScreen *screen, const QKmsOutput &output) override; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmseventreader.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmseventreader.cpp index c0c96554962..fa735388bc0 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmseventreader.cpp +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmseventreader.cpp @@ -20,7 +20,10 @@ static void pageFlipHandler(int fd, unsigned int sequence, unsigned int tv_sec, t->eventHost()->handlePageFlipCompleted(user_data); QEglFSKmsScreen *screen = static_cast<QEglFSKmsScreen *>(user_data); - screen->pageFlipped(sequence, tv_sec, tv_usec); + if (QEglFSKmsScreen::isScreenKnown(screen)) + screen->pageFlipped(sequence, tv_sec, tv_usec); + else + qWarning("Deleted screen got it's pageFlipHandler called; Dead pointer: %p", user_data); } class RegisterWaitFlipEvent : public QEvent diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsscreen.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsscreen.cpp index cc7381fb701..a40287bdfed 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsscreen.cpp +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsscreen.cpp @@ -16,6 +16,8 @@ QT_BEGIN_NAMESPACE +QSet<QEglFSKmsScreen *> QEglFSKmsScreen::s_screens; + class QEglFSKmsInterruptHandler : public QObject { public: @@ -59,10 +61,14 @@ QEglFSKmsScreen::QEglFSKmsScreen(QEglFSKmsDevice *device, const QKmsOutput &outp } else { qCDebug(qLcEglfsKmsDebug) << "No EDID data for output" << name(); } + + s_screens.insert(this); } QEglFSKmsScreen::~QEglFSKmsScreen() { + s_screens.remove(this); + m_output.cleanup(m_device); delete m_interruptHandler; } @@ -166,6 +172,11 @@ void QEglFSKmsScreen::waitForFlip() { } +void QEglFSKmsScreen::updateOutput(QKmsOutput output) +{ + m_output = output; +} + void QEglFSKmsScreen::restoreMode() { m_output.restoreMode(m_device); @@ -180,6 +191,11 @@ qreal QEglFSKmsScreen::refreshRate() const return refresh > 0 ? refresh : 60; } +void QEglFSKmsScreen::removeSibling(QPlatformScreen *screen) +{ + m_siblings.removeAll(screen); +} + QList<QPlatformScreen::Mode> QEglFSKmsScreen::modes() const { QList<QPlatformScreen::Mode> list; @@ -227,4 +243,9 @@ void QEglFSKmsScreen::pageFlipped(unsigned int sequence, unsigned int tv_sec, un Q_UNUSED(tv_usec); } +bool QEglFSKmsScreen::isScreenKnown(QEglFSKmsScreen *s) +{ + return s_screens.contains(s); +} + QT_END_NAMESPACE diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsscreen_p.h b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsscreen_p.h index 6fb1f9a1348..2dc49152a97 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsscreen_p.h +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsscreen_p.h @@ -58,6 +58,7 @@ public: QList<QPlatformScreen *> virtualSiblings() const override { return m_siblings; } void setVirtualSiblings(QList<QPlatformScreen *> sl) { m_siblings = sl; } + void removeSibling(QPlatformScreen *screen); QList<QPlatformScreen::Mode> modes() const override; @@ -68,6 +69,7 @@ public: virtual void waitForFlip(); + void updateOutput(QKmsOutput output); QKmsOutput &output() { return m_output; } void restoreMode(); @@ -80,6 +82,8 @@ public: void setCursorOutOfRange(bool b) { m_cursorOutOfRange = b; } virtual void pageFlipped(unsigned int sequence, unsigned int tv_sec, unsigned int tv_usec); + static bool isScreenKnown(QEglFSKmsScreen *s); + protected: QEglFSKmsDevice *m_device; @@ -95,6 +99,8 @@ protected: QEglFSKmsInterruptHandler *m_interruptHandler; bool m_headless; + + static QSet<QEglFSKmsScreen *> s_screens; }; QT_END_NAMESPACE |