summaryrefslogtreecommitdiffstats
path: root/src/platformsupport/kmsconvenience
diff options
context:
space:
mode:
Diffstat (limited to 'src/platformsupport/kmsconvenience')
-rw-r--r--src/platformsupport/kmsconvenience/kmsconvenience.pro20
-rw-r--r--src/platformsupport/kmsconvenience/qkmsdevice.cpp662
-rw-r--r--src/platformsupport/kmsconvenience/qkmsdevice_p.h170
3 files changed, 852 insertions, 0 deletions
diff --git a/src/platformsupport/kmsconvenience/kmsconvenience.pro b/src/platformsupport/kmsconvenience/kmsconvenience.pro
new file mode 100644
index 0000000000..d0ff0d4efb
--- /dev/null
+++ b/src/platformsupport/kmsconvenience/kmsconvenience.pro
@@ -0,0 +1,20 @@
+TARGET = QtKmsSupport
+MODULE = kms_support
+
+QT = core-private gui-private
+CONFIG += static internal_module
+
+DEFINES += QT_NO_CAST_FROM_ASCII
+PRECOMPILED_HEADER = ../../corelib/global/qt_pch.h
+
+HEADERS +=
+ qkmsdevice_p.h
+
+SOURCES += \
+ qkmsdevice.cpp
+
+QMAKE_USE += drm
+
+LIBS_PRIVATE += $$QMAKE_LIBS_DYNLOAD
+
+load(qt_module)
diff --git a/src/platformsupport/kmsconvenience/qkmsdevice.cpp b/src/platformsupport/kmsconvenience/qkmsdevice.cpp
new file mode 100644
index 0000000000..669abab331
--- /dev/null
+++ b/src/platformsupport/kmsconvenience/qkmsdevice.cpp
@@ -0,0 +1,662 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2016 Pelagicore AG
+** Copyright (C) 2015 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the plugins of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qkmsdevice_p.h"
+
+#include <QtCore/QJsonDocument>
+#include <QtCore/QJsonObject>
+#include <QtCore/QJsonArray>
+#include <QtCore/QFile>
+#include <QtCore/QLoggingCategory>
+
+#define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0])
+
+QT_BEGIN_NAMESPACE
+
+Q_LOGGING_CATEGORY(qLcKmsDebug, "qt.qpa.eglfs.kms")
+
+enum OutputConfiguration {
+ OutputConfigOff,
+ OutputConfigPreferred,
+ OutputConfigCurrent,
+ OutputConfigMode,
+ OutputConfigModeline
+};
+
+int QKmsDevice::crtcForConnector(drmModeResPtr resources, drmModeConnectorPtr connector)
+{
+ for (int i = 0; i < connector->count_encoders; i++) {
+ drmModeEncoderPtr encoder = drmModeGetEncoder(m_dri_fd, connector->encoders[i]);
+ if (!encoder) {
+ qWarning("Failed to get encoder");
+ continue;
+ }
+
+ quint32 possibleCrtcs = encoder->possible_crtcs;
+ drmModeFreeEncoder(encoder);
+
+ for (int j = 0; j < resources->count_crtcs; j++) {
+ bool isPossible = possibleCrtcs & (1 << j);
+ bool isAvailable = !(m_crtc_allocator & 1 << resources->crtcs[j]);
+
+ if (isPossible && isAvailable)
+ return j;
+ }
+ }
+
+ return -1;
+}
+
+static const char * const connector_type_names[] = { // must match DRM_MODE_CONNECTOR_*
+ "None",
+ "VGA",
+ "DVI",
+ "DVI",
+ "DVI",
+ "Composite",
+ "TV",
+ "LVDS",
+ "CTV",
+ "DIN",
+ "DP",
+ "HDMI",
+ "HDMI",
+ "TV",
+ "eDP",
+ "Virtual",
+ "DSI"
+};
+
+static QByteArray nameForConnector(const drmModeConnectorPtr connector)
+{
+ QByteArray connectorName("UNKNOWN");
+
+ if (connector->connector_type < ARRAY_LENGTH(connector_type_names))
+ connectorName = connector_type_names[connector->connector_type];
+
+ connectorName += QByteArray::number(connector->connector_type_id);
+
+ return connectorName;
+}
+
+static bool parseModeline(const QByteArray &text, drmModeModeInfoPtr mode)
+{
+ char hsync[16];
+ char vsync[16];
+ float fclock;
+
+ mode->type = DRM_MODE_TYPE_USERDEF;
+ mode->hskew = 0;
+ mode->vscan = 0;
+ mode->vrefresh = 0;
+ mode->flags = 0;
+
+ if (sscanf(text.constData(), "%f %hd %hd %hd %hd %hd %hd %hd %hd %15s %15s",
+ &fclock,
+ &mode->hdisplay,
+ &mode->hsync_start,
+ &mode->hsync_end,
+ &mode->htotal,
+ &mode->vdisplay,
+ &mode->vsync_start,
+ &mode->vsync_end,
+ &mode->vtotal, hsync, vsync) != 11)
+ return false;
+
+ mode->clock = fclock * 1000;
+
+ if (strcmp(hsync, "+hsync") == 0)
+ mode->flags |= DRM_MODE_FLAG_PHSYNC;
+ else if (strcmp(hsync, "-hsync") == 0)
+ mode->flags |= DRM_MODE_FLAG_NHSYNC;
+ else
+ return false;
+
+ if (strcmp(vsync, "+vsync") == 0)
+ mode->flags |= DRM_MODE_FLAG_PVSYNC;
+ else if (strcmp(vsync, "-vsync") == 0)
+ mode->flags |= DRM_MODE_FLAG_NVSYNC;
+ else
+ return false;
+
+ return true;
+}
+
+QPlatformScreen *QKmsDevice::createScreenForConnector(drmModeResPtr resources,
+ drmModeConnectorPtr connector,
+ VirtualDesktopInfo *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 Q_NULLPTR;
+ }
+
+ OutputConfiguration configuration;
+ QSize configurationSize;
+ drmModeModeInfo configurationModeline;
+
+ auto userConfig = m_screenConfig->outputSettings();
+ auto userConnectorConfig = userConfig.value(QString::fromUtf8(connectorName));
+ // default to the preferred mode unless overridden in the config
+ const QByteArray mode = userConnectorConfig.value(QStringLiteral("mode"), QStringLiteral("preferred"))
+ .toByteArray().toLower();
+ if (mode == "off") {
+ configuration = OutputConfigOff;
+ } else if (mode == "preferred") {
+ configuration = OutputConfigPreferred;
+ } else if (mode == "current") {
+ configuration = OutputConfigCurrent;
+ } else if (sscanf(mode.constData(), "%dx%d", &configurationSize.rwidth(), &configurationSize.rheight()) == 2) {
+ configuration = OutputConfigMode;
+ } else if (parseModeline(mode, &configurationModeline)) {
+ configuration = OutputConfigModeline;
+ } else {
+ qWarning("Invalid mode \"%s\" for output %s", mode.constData(), connectorName.constData());
+ configuration = OutputConfigPreferred;
+ }
+ if (vinfo) {
+ *vinfo = VirtualDesktopInfo();
+ 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.count() == 2)
+ vinfo->virtualPos = QPoint(vposComp[0].trimmed().toInt(), vposComp[1].trimmed().toInt());
+ }
+ if (userConnectorConfig.value(QStringLiteral("primary")).toBool())
+ 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, Q_NULLPTR);
+ return Q_NULLPTR;
+ }
+
+ // Skip disconnected output
+ if (configuration == OutputConfigPreferred && connector->connection == DRM_MODE_DISCONNECTED) {
+ qCDebug(qLcKmsDebug) << "Skipping disconnected output" << connectorName;
+ return Q_NULLPTR;
+ }
+
+ // 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->connector_id)) {
+ drmModeCrtcPtr crtc = drmModeGetCrtc(m_dri_fd, encoder->crtc_id);
+ drmModeFreeEncoder(encoder);
+
+ if (!crtc)
+ return Q_NULLPTR;
+
+ if (crtc->mode_valid)
+ crtc_mode = crtc->mode;
+
+ drmModeFreeCrtc(crtc);
+ }
+
+ QList<drmModeModeInfo> modes;
+ modes.reserve(connector->count_modes);
+ qCDebug(qLcKmsDebug) << connectorName << "mode count:" << connector->count_modes;
+ for (int i = 0; i < connector->count_modes; i++) {
+ const drmModeModeInfo &mode = connector->modes[i];
+ qCDebug(qLcKmsDebug) << "mode" << i << mode.hdisplay << "x" << mode.vdisplay
+ << '@' << mode.vrefresh << "hz";
+ modes << connector->modes[i];
+ }
+
+ int preferred = -1;
+ int current = -1;
+ int configured = -1;
+ int best = -1;
+
+ for (int i = modes.size() - 1; i >= 0; i--) {
+ const drmModeModeInfo &m = modes.at(i);
+
+ if (configuration == OutputConfigMode &&
+ m.hdisplay == configurationSize.width() &&
+ m.vdisplay == configurationSize.height()) {
+ configured = i;
+ }
+
+ if (!memcmp(&crtc_mode, &m, sizeof m))
+ current = i;
+
+ if (m.type & DRM_MODE_TYPE_PREFERRED)
+ preferred = i;
+
+ best = i;
+ }
+
+ if (configuration == OutputConfigModeline) {
+ modes << configurationModeline;
+ configured = modes.size() - 1;
+ }
+
+ if (current < 0 && crtc_mode.clock != 0) {
+ modes << crtc_mode;
+ current = mode.size() - 1;
+ }
+
+ if (configuration == OutputConfigCurrent)
+ configured = current;
+
+ int selected_mode = -1;
+
+ if (configured >= 0)
+ selected_mode = configured;
+ else if (preferred >= 0)
+ selected_mode = preferred;
+ else if (current >= 0)
+ selected_mode = current;
+ else if (best >= 0)
+ selected_mode = best;
+
+ if (selected_mode < 0) {
+ qWarning() << "No modes available for output" << connectorName;
+ return Q_NULLPTR;
+ } else {
+ int width = modes[selected_mode].hdisplay;
+ int height = modes[selected_mode].vdisplay;
+ int refresh = modes[selected_mode].vrefresh;
+ qCDebug(qLcKmsDebug) << "Selected mode" << selected_mode << ":" << width << "x" << height
+ << '@' << refresh << "hz for output" << connectorName;
+ }
+
+ // physical size from connector < config values < env vars
+ int pwidth = qEnvironmentVariableIntValue("QT_QPA_EGLFS_PHYSICAL_WIDTH");
+ if (!pwidth)
+ pwidth = qEnvironmentVariableIntValue("QT_QPA_PHYSICAL_WIDTH");
+ int pheight = qEnvironmentVariableIntValue("QT_QPA_EGLFS_PHYSICAL_HEIGHT");
+ if (!pheight)
+ pheight = qEnvironmentVariableIntValue("QT_QPA_PHYSICAL_HEIGHT");
+ QSizeF physSize(pwidth, pheight);
+ if (physSize.isEmpty()) {
+ physSize = QSize(userConnectorConfig.value(QStringLiteral("physicalWidth")).toInt(),
+ userConnectorConfig.value(QStringLiteral("physicalHeight")).toInt());
+ if (physSize.isEmpty()) {
+ physSize.setWidth(connector->mmWidth);
+ physSize.setHeight(connector->mmHeight);
+ }
+ }
+ qCDebug(qLcKmsDebug) << "Physical size is" << physSize << "mm" << "for output" << connectorName;
+
+ QKmsOutput output = {
+ QString::fromUtf8(connectorName),
+ connector->connector_id,
+ crtc_id,
+ physSize,
+ selected_mode,
+ false,
+ drmModeGetCrtc(m_dri_fd, crtc_id),
+ modes,
+ connector->subpixel,
+ connectorProperty(connector, QByteArrayLiteral("DPMS")),
+ false,
+ 0,
+ false
+ };
+
+ bool ok;
+ int idx = qEnvironmentVariableIntValue("QT_QPA_EGLFS_KMS_PLANE_INDEX", &ok);
+ if (ok) {
+ drmModePlaneRes *planeResources = drmModeGetPlaneResources(m_dri_fd);
+ if (planeResources) {
+ if (idx >= 0 && idx < int(planeResources->count_planes)) {
+ drmModePlane *plane = drmModeGetPlane(m_dri_fd, planeResources->planes[idx]);
+ if (plane) {
+ output.wants_plane = true;
+ output.plane_id = plane->plane_id;
+ qCDebug(qLcKmsDebug, "Forcing plane index %d, plane id %u (belongs to crtc id %u)",
+ idx, plane->plane_id, plane->crtc_id);
+ drmModeFreePlane(plane);
+ }
+ } else {
+ qWarning("Invalid plane index %d, must be between 0 and %u", idx, planeResources->count_planes - 1);
+ }
+ }
+ }
+
+ m_crtc_allocator |= (1 << output.crtc_id);
+ m_connector_allocator |= (1 << output.connector_id);
+
+ return createScreen(output);
+}
+
+drmModePropertyPtr QKmsDevice::connectorProperty(drmModeConnectorPtr connector, const QByteArray &name)
+{
+ drmModePropertyPtr prop;
+
+ for (int i = 0; i < connector->count_props; i++) {
+ prop = drmModeGetProperty(m_dri_fd, connector->props[i]);
+ if (!prop)
+ continue;
+ if (strcmp(prop->name, name.constData()) == 0)
+ return prop;
+ drmModeFreeProperty(prop);
+ }
+
+ return Q_NULLPTR;
+}
+
+QKmsDevice::QKmsDevice(QKmsScreenConfig *screenConfig, const QString &path)
+ : m_screenConfig(screenConfig)
+ , m_path(path)
+ , m_dri_fd(-1)
+ , m_crtc_allocator(0)
+ , m_connector_allocator(0)
+{
+ if (m_path.isEmpty()) {
+ m_path = m_screenConfig->devicePath();
+ qCDebug(qLcKmsDebug, "Using DRM device %s specified in config file", qPrintable(m_path));
+ if (m_path.isEmpty())
+ qFatal("No DRM device given");
+ } else {
+ qCDebug(qLcKmsDebug, "Using backend-provided DRM device %s", qPrintable(m_path));
+ }
+}
+
+QKmsDevice::~QKmsDevice()
+{
+}
+
+struct OrderedScreen
+{
+ OrderedScreen() : screen(nullptr) { }
+ OrderedScreen(QPlatformScreen *screen, const QKmsDevice::VirtualDesktopInfo &vinfo)
+ : screen(screen), vinfo(vinfo) { }
+ QPlatformScreen *screen;
+ QKmsDevice::VirtualDesktopInfo vinfo;
+};
+
+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;
+}
+
+static bool orderedScreenLessThan(const OrderedScreen &a, const OrderedScreen &b)
+{
+ return a.vinfo.virtualIndex < b.vinfo.virtualIndex;
+}
+
+void QKmsDevice::createScreens()
+{
+ drmModeResPtr resources = drmModeGetResources(m_dri_fd);
+ if (!resources) {
+ qWarning("drmModeGetResources failed");
+ return;
+ }
+
+ QVector<OrderedScreen> screens;
+
+ int wantedConnectorIndex = -1;
+ bool ok;
+ int idx = qEnvironmentVariableIntValue("QT_QPA_EGLFS_KMS_CONNECTOR_INDEX", &ok);
+ if (ok) {
+ if (idx >= 0 && idx < resources->count_connectors)
+ wantedConnectorIndex = idx;
+ else
+ qWarning("Invalid connector index %d, must be between 0 and %u", idx, resources->count_connectors - 1);
+ }
+
+ for (int i = 0; i < resources->count_connectors; i++) {
+ if (wantedConnectorIndex >= 0 && i != wantedConnectorIndex)
+ continue;
+
+ drmModeConnectorPtr connector = drmModeGetConnector(m_dri_fd, resources->connectors[i]);
+ if (!connector)
+ continue;
+
+ VirtualDesktopInfo vinfo;
+ QPlatformScreen *screen = createScreenForConnector(resources, connector, &vinfo);
+ if (screen)
+ screens.append(OrderedScreen(screen, vinfo));
+
+ drmModeFreeConnector(connector);
+ }
+
+ drmModeFreeResources(resources);
+
+ // Use stable sort to preserve the original (DRM connector) order
+ // for outputs with unspecified indices.
+ std::stable_sort(screens.begin(), screens.end(), orderedScreenLessThan);
+ qCDebug(qLcKmsDebug) << "Sorted screen list:" << screens;
+
+ QPoint pos(0, 0);
+ QList<QPlatformScreen *> siblings;
+ QVector<QPoint> virtualPositions;
+ int primarySiblingIdx = -1;
+
+ 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();
+ } else {
+ virtualPos = orderedScreen.vinfo.virtualPos;
+ }
+ qCDebug(qLcKmsDebug) << "Adding QPlatformScren" << 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()) {
+ siblings.append(s);
+ virtualPositions.append(virtualPos);
+ if (orderedScreen.vinfo.isPrimary)
+ primarySiblingIdx = siblings.count() - 1;
+ } else {
+ registerScreen(s, orderedScreen.vinfo.isPrimary, virtualPos, QList<QPlatformScreen *>() << s);
+ }
+ }
+
+ if (!m_screenConfig->separateScreens()) {
+ // enable the virtual desktop
+ for (int i = 0; i < siblings.count(); ++i)
+ registerScreen(siblings[i], i == primarySiblingIdx, virtualPositions[i], siblings);
+ }
+}
+
+int QKmsDevice::fd() const
+{
+ return m_dri_fd;
+}
+
+QString QKmsDevice::devicePath() const
+{
+ return m_path;
+}
+
+void QKmsDevice::setFd(int fd)
+{
+ m_dri_fd = fd;
+}
+
+QKmsScreenConfig *QKmsDevice::screenConfig() const
+{
+ return m_screenConfig;
+}
+
+QKmsScreenConfig::QKmsScreenConfig()
+ : m_hwCursor(true)
+ , m_separateScreens(false)
+ , m_pbuffers(false)
+ , m_virtualDesktopLayout(VirtualDesktopLayoutHorizontal)
+{
+ loadConfig();
+}
+
+void QKmsScreenConfig::loadConfig()
+{
+ QByteArray json = qgetenv("QT_QPA_EGLFS_KMS_CONFIG");
+ if (json.isEmpty()) {
+ json = qgetenv("QT_QPA_KMS_CONFIG");
+ if (json.isEmpty())
+ return;
+ }
+
+ qCDebug(qLcKmsDebug) << "Loading KMS setup from" << json;
+
+ QFile file(QString::fromUtf8(json));
+ if (!file.open(QFile::ReadOnly)) {
+ qCWarning(qLcKmsDebug) << "Could not open config file"
+ << json << "for reading";
+ return;
+ }
+
+ const QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
+ if (!doc.isObject()) {
+ qCWarning(qLcKmsDebug) << "Invalid config file" << json
+ << "- no top-level JSON object";
+ return;
+ }
+
+ const QJsonObject object = doc.object();
+
+ m_hwCursor = object.value(QLatin1String("hwcursor")).toBool(m_hwCursor);
+ m_pbuffers = object.value(QLatin1String("pbuffers")).toBool(m_pbuffers);
+ m_devicePath = object.value(QLatin1String("device")).toString();
+ m_separateScreens = object.value(QLatin1String("separateScreens")).toBool(m_separateScreens);
+
+ const QString vdOriString = object.value(QLatin1String("virtualDesktopLayout")).toString();
+ if (!vdOriString.isEmpty()) {
+ if (vdOriString == QLatin1String("horizontal"))
+ m_virtualDesktopLayout = VirtualDesktopLayoutHorizontal;
+ else if (vdOriString == QLatin1String("vertical"))
+ m_virtualDesktopLayout = VirtualDesktopLayoutVertical;
+ else
+ qCWarning(qLcKmsDebug) << "Unknown virtualDesktopOrientation value" << vdOriString;
+ }
+
+ const QJsonArray outputs = object.value(QLatin1String("outputs")).toArray();
+ for (int i = 0; i < outputs.size(); i++) {
+ const QVariantMap outputSettings = outputs.at(i).toObject().toVariantMap();
+
+ if (outputSettings.contains(QStringLiteral("name"))) {
+ const QString name = outputSettings.value(QStringLiteral("name")).toString();
+
+ if (m_outputSettings.contains(name)) {
+ qCDebug(qLcKmsDebug) << "Output" << name << "configured multiple times!";
+ }
+
+ m_outputSettings.insert(name, outputSettings);
+ }
+ }
+
+ qCDebug(qLcKmsDebug) << "Requested configuration (some settings may be ignored):\n"
+ << "\thwcursor:" << m_hwCursor << "\n"
+ << "\tpbuffers:" << m_pbuffers << "\n"
+ << "\tseparateScreens:" << m_separateScreens << "\n"
+ << "\tvirtualDesktopLayout:" << m_virtualDesktopLayout << "\n"
+ << "\toutputs:" << m_outputSettings;
+}
+
+void QKmsOutput::restoreMode(QKmsDevice *device)
+{
+ if (mode_set && saved_crtc) {
+ drmModeSetCrtc(device->fd(),
+ saved_crtc->crtc_id,
+ saved_crtc->buffer_id,
+ 0, 0,
+ &connector_id, 1,
+ &saved_crtc->mode);
+ mode_set = false;
+ }
+}
+
+void QKmsOutput::cleanup(QKmsDevice *device)
+{
+ if (dpms_prop) {
+ drmModeFreeProperty(dpms_prop);
+ dpms_prop = nullptr;
+ }
+
+ restoreMode(device);
+
+ if (saved_crtc) {
+ drmModeFreeCrtc(saved_crtc);
+ saved_crtc = nullptr;
+ }
+}
+
+QPlatformScreen::SubpixelAntialiasingType QKmsOutput::subpixelAntialiasingTypeHint() const
+{
+ switch (subpixel) {
+ default:
+ case DRM_MODE_SUBPIXEL_UNKNOWN:
+ case DRM_MODE_SUBPIXEL_NONE:
+ return QPlatformScreen::Subpixel_None;
+ case DRM_MODE_SUBPIXEL_HORIZONTAL_RGB:
+ return QPlatformScreen::Subpixel_RGB;
+ case DRM_MODE_SUBPIXEL_HORIZONTAL_BGR:
+ return QPlatformScreen::Subpixel_BGR;
+ case DRM_MODE_SUBPIXEL_VERTICAL_RGB:
+ return QPlatformScreen::Subpixel_VRGB;
+ case DRM_MODE_SUBPIXEL_VERTICAL_BGR:
+ return QPlatformScreen::Subpixel_VBGR;
+ }
+}
+
+void QKmsOutput::setPowerState(QKmsDevice *device, QPlatformScreen::PowerState state)
+{
+ if (dpms_prop)
+ drmModeConnectorSetProperty(device->fd(), connector_id,
+ dpms_prop->prop_id, (int) state);
+}
+
+QT_END_NAMESPACE
diff --git a/src/platformsupport/kmsconvenience/qkmsdevice_p.h b/src/platformsupport/kmsconvenience/qkmsdevice_p.h
new file mode 100644
index 0000000000..35a51c18b1
--- /dev/null
+++ b/src/platformsupport/kmsconvenience/qkmsdevice_p.h
@@ -0,0 +1,170 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2016 Pelagicore AG
+** Copyright (C) 2015 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the plugins of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QKMSDEVICE_P_H
+#define QKMSDEVICE_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <qpa/qplatformscreen.h>
+#include <QtCore/QMap>
+#include <QtCore/QVariant>
+
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+
+QT_BEGIN_NAMESPACE
+
+class QKmsDevice;
+
+class QKmsScreenConfig
+{
+public:
+ enum VirtualDesktopLayout {
+ VirtualDesktopLayoutHorizontal,
+ VirtualDesktopLayoutVertical
+ };
+
+ QKmsScreenConfig();
+
+ QString devicePath() const { return m_devicePath; }
+
+ bool hwCursor() const { return m_hwCursor; }
+ bool separateScreens() const { return m_separateScreens; }
+ bool supportsPBuffers() const { return m_pbuffers; }
+ VirtualDesktopLayout virtualDesktopLayout() const { return m_virtualDesktopLayout; }
+
+ QMap<QString, QVariantMap> outputSettings() const { return m_outputSettings; }
+
+private:
+ void loadConfig();
+
+ QString m_devicePath;
+ bool m_hwCursor;
+ bool m_separateScreens;
+ bool m_pbuffers;
+ VirtualDesktopLayout m_virtualDesktopLayout;
+ QMap<QString, QVariantMap> m_outputSettings;
+};
+
+struct QKmsOutput
+{
+ QString name;
+ uint32_t connector_id;
+ uint32_t crtc_id;
+ QSizeF physical_size;
+ int mode; // index of selected mode in list below
+ bool mode_set;
+ drmModeCrtcPtr saved_crtc;
+ QList<drmModeModeInfo> modes;
+ int subpixel;
+ drmModePropertyPtr dpms_prop;
+ bool wants_plane;
+ uint32_t plane_id;
+ bool plane_set;
+
+ void restoreMode(QKmsDevice *device);
+ void cleanup(QKmsDevice *device);
+ QPlatformScreen::SubpixelAntialiasingType subpixelAntialiasingTypeHint() const;
+ void setPowerState(QKmsDevice *device, QPlatformScreen::PowerState state);
+};
+
+class QKmsDevice
+{
+public:
+ struct VirtualDesktopInfo {
+ VirtualDesktopInfo() : virtualIndex(0), isPrimary(false) { }
+ int virtualIndex;
+ QPoint virtualPos;
+ bool isPrimary;
+ };
+
+ QKmsDevice(QKmsScreenConfig *screenConfig, const QString &path = QString());
+ virtual ~QKmsDevice();
+
+ virtual bool open() = 0;
+ virtual void close() = 0;
+ virtual void *nativeDisplay() const = 0;
+
+ void createScreens();
+
+ int fd() const;
+ QString devicePath() const;
+
+ QKmsScreenConfig *screenConfig() const;
+
+protected:
+ virtual QPlatformScreen *createScreen(const QKmsOutput &output) = 0;
+ virtual void registerScreen(QPlatformScreen *screen,
+ bool isPrimary,
+ const QPoint &virtualPos,
+ const QList<QPlatformScreen *> &virtualSiblings) = 0;
+
+ void setFd(int fd);
+ int crtcForConnector(drmModeResPtr resources, drmModeConnectorPtr connector);
+ QPlatformScreen *createScreenForConnector(drmModeResPtr resources,
+ drmModeConnectorPtr connector,
+ VirtualDesktopInfo *vinfo);
+ drmModePropertyPtr connectorProperty(drmModeConnectorPtr connector, const QByteArray &name);
+
+ QKmsScreenConfig *m_screenConfig;
+ QString m_path;
+ int m_dri_fd;
+
+ quint32 m_crtc_allocator;
+ quint32 m_connector_allocator;
+
+private:
+ Q_DISABLE_COPY(QKmsDevice)
+};
+
+QT_END_NAMESPACE
+
+#endif