diff --git a/abstract_wayland_output.cpp b/abstract_wayland_output.cpp index 5df90a00e..d743287ff 100644 --- a/abstract_wayland_output.cpp +++ b/abstract_wayland_output.cpp @@ -1,270 +1,271 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright 2019 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "abstract_wayland_output.h" #include "wayland_server.h" // KWayland #include #include #include // KF5 #include #include namespace KWin { AbstractWaylandOutput::AbstractWaylandOutput(QObject *parent) : AbstractOutput(parent) { } AbstractWaylandOutput::~AbstractWaylandOutput() { delete m_xdgOutput.data(); delete m_waylandOutput.data(); delete m_waylandOutputDevice.data(); } QString AbstractWaylandOutput::name() const { if (!m_waylandOutput) { return i18n("unknown"); } return QStringLiteral("%1 %2").arg(m_waylandOutput->manufacturer()).arg(m_waylandOutput->model()); } QRect AbstractWaylandOutput::geometry() const { return QRect(globalPos(), pixelSize() / scale()); } QSize AbstractWaylandOutput::physicalSize() const { - return orientateSize(m_physicalSize); + return orientateSize(m_waylandOutputDevice->physicalSize()); } int AbstractWaylandOutput::refreshRate() const { if (!m_waylandOutput) { return 60000; } return m_waylandOutput->refreshRate(); } QPoint AbstractWaylandOutput::globalPos() const { return m_waylandOutputDevice->globalPosition(); } void AbstractWaylandOutput::setGlobalPos(const QPoint &pos) { m_waylandOutputDevice->setGlobalPosition(pos); if (m_waylandOutput) { m_waylandOutput->setGlobalPosition(pos); } if (m_xdgOutput) { m_xdgOutput->setLogicalPosition(pos); m_xdgOutput->done(); } } QSize AbstractWaylandOutput::pixelSize() const { return orientateSize(m_waylandOutputDevice->pixelSize()); } qreal AbstractWaylandOutput::scale() const { return m_waylandOutputDevice->scaleF(); } void AbstractWaylandOutput::setScale(qreal scale) { m_waylandOutputDevice->setScaleF(scale); if (m_waylandOutput) { // this is the scale that clients will ideally use for their buffers // this has to be an int which is fine // I don't know whether we want to round or ceil // or maybe even set this to 3 when we're scaling to 1.5 // don't treat this like it's chosen deliberately m_waylandOutput->setScale(std::ceil(scale)); } if (m_xdgOutput) { m_xdgOutput->setLogicalSize(pixelSize() / scale); m_xdgOutput->done(); } } void AbstractWaylandOutput::setChanges(KWayland::Server::OutputChangeSet *changes) { qCDebug(KWIN_CORE) << "Set changes in AbstractWaylandOutput."; bool emitModeChanged = false; //enabledChanged is handled by plugin code if (changes->modeChanged()) { qCDebug(KWIN_CORE) << "Setting new mode:" << changes->mode(); m_waylandOutputDevice->setCurrentMode(changes->mode()); updateMode(changes->mode()); emitModeChanged = true; } if (changes->transformChanged()) { qCDebug(KWIN_CORE) << "Server setting transform: " << (int)(changes->transform()); transform(changes->transform()); emitModeChanged = true; } if (changes->positionChanged()) { qCDebug(KWIN_CORE) << "Server setting position: " << changes->position(); setGlobalPos(changes->position()); // may just work already! } if (changes->scaleChanged()) { qCDebug(KWIN_CORE) << "Setting scale:" << changes->scale(); setScale(changes->scaleF()); emitModeChanged = true; } if (emitModeChanged) { emit modeChanged(); } } void AbstractWaylandOutput::setEnabled(bool enable) { if (enable == isEnabled()) { return; } if (enable) { updateDpms(KWayland::Server::OutputInterface::DpmsMode::On); initWaylandOutput(); } else { updateDpms(KWayland::Server::OutputInterface::DpmsMode::Off); delete waylandOutput().data(); } waylandOutputDevice()->setEnabled(enable ? KWayland::Server::OutputDeviceInterface::Enablement::Enabled : KWayland::Server::OutputDeviceInterface::Enablement::Disabled); } void AbstractWaylandOutput::setWaylandMode(const QSize &size, int refreshRate) { if (m_waylandOutput.isNull()) { return; } m_waylandOutput->setCurrentMode(size, refreshRate); if (m_xdgOutput) { m_xdgOutput->setLogicalSize(pixelSize() / scale()); m_xdgOutput->done(); } } void AbstractWaylandOutput::createXdgOutput() { if (!m_waylandOutput || m_xdgOutput) { return; } m_xdgOutput = waylandServer()->xdgOutputManager()->createXdgOutput(m_waylandOutput, m_waylandOutput); m_xdgOutput->setLogicalSize(pixelSize() / scale()); m_xdgOutput->setLogicalPosition(globalPos()); m_xdgOutput->done(); } void AbstractWaylandOutput::initWaylandOutput() { if (!m_waylandOutput.isNull()) { delete m_waylandOutput.data(); m_waylandOutput.clear(); } m_waylandOutput = waylandServer()->display()->createOutput(); createXdgOutput(); /* * add base wayland output data */ m_waylandOutput->setManufacturer(m_waylandOutputDevice->manufacturer()); m_waylandOutput->setModel(m_waylandOutputDevice->model()); - m_waylandOutput->setPhysicalSize(rawPhysicalSize()); + m_waylandOutput->setPhysicalSize(m_waylandOutputDevice->physicalSize()); /* * add modes */ for(const auto &mode: m_waylandOutputDevice->modes()) { KWayland::Server::OutputInterface::ModeFlags flags; if (mode.flags & KWayland::Server::OutputDeviceInterface::ModeFlag::Current) { flags |= KWayland::Server::OutputInterface::ModeFlag::Current; } if (mode.flags & KWayland::Server::OutputDeviceInterface::ModeFlag::Preferred) { flags |= KWayland::Server::OutputInterface::ModeFlag::Preferred; } m_waylandOutput->addMode(mode.size, flags, mode.refreshRate); } m_waylandOutput->create(); /* * set dpms */ m_waylandOutput->setDpmsSupported(m_supportsDpms); // set to last known mode m_waylandOutput->setDpmsMode(m_dpms); connect(m_waylandOutput.data(), &KWayland::Server::OutputInterface::dpmsModeRequested, this, [this] (KWayland::Server::OutputInterface::DpmsMode mode) { updateDpms(mode); }, Qt::QueuedConnection ); } void AbstractWaylandOutput::initWaylandOutputDevice(const QString &model, const QString &manufacturer, const QByteArray &uuid, + const QSize &physicalSize, const QVector &modes) { Q_ASSERT(m_waylandOutputDevice.isNull()); m_waylandOutputDevice = waylandServer()->display()->createOutputDevice(); m_waylandOutputDevice->setUuid(uuid); if (!manufacturer.isEmpty()) { m_waylandOutputDevice->setManufacturer(manufacturer); } else { m_waylandOutputDevice->setManufacturer(i18n("unknown")); } m_waylandOutputDevice->setModel(model); - m_waylandOutputDevice->setPhysicalSize(m_physicalSize); + m_waylandOutputDevice->setPhysicalSize(physicalSize); int i = 0; for (auto mode : modes) { qCDebug(KWIN_CORE).nospace() << "Adding mode " << ++i << ": " << mode.size << " [" << mode.refreshRate << "]"; m_waylandOutputDevice->addMode(mode); } m_waylandOutputDevice->create(); } QSize AbstractWaylandOutput::orientateSize(const QSize &size) const { if (m_orientation == Qt::PortraitOrientation || m_orientation == Qt::InvertedPortraitOrientation) { return size.transposed(); } return size; } } diff --git a/abstract_wayland_output.h b/abstract_wayland_output.h index d8606f0b6..e13f9f817 100644 --- a/abstract_wayland_output.h +++ b/abstract_wayland_output.h @@ -1,177 +1,170 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright 2019 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_ABSTRACT_WAYLAND_OUTPUT_H #define KWIN_ABSTRACT_WAYLAND_OUTPUT_H #include "abstract_output.h" #include #include #include #include #include #include #include #include #include #include namespace KWayland { namespace Server { class OutputInterface; class OutputDeviceInterface; class OutputChangeSet; class OutputManagementInterface; class XdgOutputInterface; } } namespace KWin { /** * Generic output representation in a Wayland session */ class KWIN_EXPORT AbstractWaylandOutput : public AbstractOutput { Q_OBJECT public: explicit AbstractWaylandOutput(QObject *parent = nullptr); ~AbstractWaylandOutput() override; QString name() const override; bool isEnabled() const { return !m_waylandOutput.isNull(); } QSize pixelSize() const; qreal scale() const override; /** * The geometry of this output in global compositor co-ordinates (i.e scaled) */ QRect geometry() const override; QSize physicalSize() const override; Qt::ScreenOrientation orientation() const override { return m_orientation; } /** * Current refresh rate in 1/ms. */ int refreshRate() const override; bool isInternal() const override { return m_internal; } void setGlobalPos(const QPoint &pos); void setScale(qreal scale); /** * This sets the changes and tests them against the specific output. */ void setChanges(KWayland::Server::OutputChangeSet *changeset); QPointer waylandOutput() const { return m_waylandOutput; } /** * Enable or disable the output. * * This differs from updateDpms as it also removes the wl_output. * The default is on. */ void setEnabled(bool enable); Q_SIGNALS: void modeChanged(); protected: void initWaylandOutput(); void initWaylandOutputDevice(const QString &model, const QString &manufacturer, const QByteArray &uuid, + const QSize &physicalSize, const QVector &modes); QPointer xdgOutput() const { return m_xdgOutput; } void createXdgOutput(); QPointer waylandOutputDevice() const { return m_waylandOutputDevice; } QPoint globalPos() const; - QSize rawPhysicalSize() const { - return m_physicalSize; - } - void setRawPhysicalSize(const QSize &set) { - m_physicalSize = set; - } - void setOrientation(Qt::ScreenOrientation set) { m_orientation = set; } bool internal() const { return m_internal; } void setInternal(bool set) { m_internal = set; } void setDpmsSupported(bool set) { m_supportsDpms = set; } virtual void updateDpms(KWayland::Server::OutputInterface::DpmsMode mode) { Q_UNUSED(mode); } virtual void updateMode(int modeIndex) { Q_UNUSED(modeIndex); } virtual void transform(KWayland::Server::OutputDeviceInterface::Transform transform) { Q_UNUSED(transform); } void setWaylandMode(const QSize &size, int refreshRate); QSize orientateSize(const QSize &size) const; private: QPointer m_waylandOutput; QPointer m_xdgOutput; QPointer m_waylandOutputDevice; KWayland::Server::OutputInterface::DpmsMode m_dpms = KWayland::Server::OutputInterface::DpmsMode::On; - QSize m_physicalSize; Qt::ScreenOrientation m_orientation = Qt::PrimaryOrientation; bool m_internal = false; bool m_supportsDpms = false; }; } #endif // KWIN_OUTPUT_H diff --git a/plugins/platforms/drm/drm_output.cpp b/plugins/platforms/drm/drm_output.cpp index e0003450b..fb2610093 100644 --- a/plugins/platforms/drm/drm_output.cpp +++ b/plugins/platforms/drm/drm_output.cpp @@ -1,1072 +1,1071 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "drm_output.h" #include "drm_backend.h" #include "drm_object_plane.h" #include "drm_object_crtc.h" #include "drm_object_connector.h" #include "composite.h" #include "logind.h" #include "logging.h" #include "main.h" #include "orientation_sensor.h" #include "screens_drm.h" #include "wayland_server.h" // KWayland #include // KF5 #include #include #include // Qt #include #include #include // c++ #include // drm #include #include #include namespace KWin { DrmOutput::DrmOutput(DrmBackend *backend) : AbstractWaylandOutput(backend) , m_backend(backend) { } DrmOutput::~DrmOutput() { Q_ASSERT(!m_pageFlipPending); if (!m_deleted) { teardown(); } } void DrmOutput::teardown() { m_deleted = true; hideCursor(); m_crtc->blank(); if (m_primaryPlane) { // TODO: when having multiple planes, also clean up these m_primaryPlane->setOutput(nullptr); if (m_backend->deleteBufferAfterPageFlip()) { delete m_primaryPlane->current(); } m_primaryPlane->setCurrent(nullptr); } m_crtc->setOutput(nullptr); m_conn->setOutput(nullptr); delete m_cursor[0]; delete m_cursor[1]; if (!m_pageFlipPending) { deleteLater(); } //else will be deleted in the page flip handler //this is needed so that the pageflipcallback handle isn't deleted } void DrmOutput::releaseGbm() { if (DrmBuffer *b = m_crtc->current()) { b->releaseGbm(); } if (m_primaryPlane && m_primaryPlane->current()) { m_primaryPlane->current()->releaseGbm(); } } bool DrmOutput::hideCursor() { return drmModeSetCursor(m_backend->fd(), m_crtc->id(), 0, 0, 0) == 0; } bool DrmOutput::showCursor(DrmDumbBuffer *c) { const QSize &s = c->size(); return drmModeSetCursor(m_backend->fd(), m_crtc->id(), c->handle(), s.width(), s.height()) == 0; } bool DrmOutput::showCursor() { const bool ret = showCursor(m_cursor[m_cursorIndex]); if (!ret) { return ret; } if (m_hasNewCursor) { m_cursorIndex = (m_cursorIndex + 1) % 2; m_hasNewCursor = false; } return ret; } int orientationToRotation(Qt::ScreenOrientation orientation) { switch (orientation) { case Qt::PrimaryOrientation: case Qt::LandscapeOrientation: return 0; case Qt::InvertedPortraitOrientation: return 90; case Qt::InvertedLandscapeOrientation: return 180; case Qt::PortraitOrientation: return 270; } Q_UNREACHABLE(); return 0; } QMatrix4x4 DrmOutput::matrixDisplay(const QSize &s) const { QMatrix4x4 matrix; const int angle = orientationToRotation(orientation()); if (angle) { const QSize center = s / 2; matrix.translate(center.width(), center.height()); matrix.rotate(angle, 0, 0, 1); matrix.translate(-center.width(), -center.height()); } matrix.scale(scale()); return matrix; } void DrmOutput::updateCursor() { QImage cursorImage = m_backend->softwareCursor(); if (cursorImage.isNull()) { return; } m_hasNewCursor = true; QImage *c = m_cursor[m_cursorIndex]->image(); c->fill(Qt::transparent); QPainter p; p.begin(c); p.setWorldTransform(matrixDisplay(QSize(cursorImage.width(), cursorImage.height())).toTransform()); p.drawImage(QPoint(0, 0), cursorImage); p.end(); } void DrmOutput::moveCursor(const QPoint &globalPos) { const QMatrix4x4 hotspotMatrix = matrixDisplay(m_backend->softwareCursor().size()); QPoint p = globalPos - AbstractWaylandOutput::globalPos(); switch (orientation()) { case Qt::PrimaryOrientation: case Qt::LandscapeOrientation: break; case Qt::PortraitOrientation: p = QPoint(p.y(), pixelSize().height() - p.x()); break; case Qt::InvertedPortraitOrientation: p = QPoint(pixelSize().width() - p.y(), p.x()); break; case Qt::InvertedLandscapeOrientation: p = QPoint(pixelSize().width() - p.x(), pixelSize().height() - p.y()); break; } p *= scale(); p -= hotspotMatrix.map(m_backend->softwareCursorHotspot()); drmModeMoveCursor(m_backend->fd(), m_crtc->id(), p.x(), p.y()); } static QHash s_connectorNames = { {DRM_MODE_CONNECTOR_Unknown, QByteArrayLiteral("Unknown")}, {DRM_MODE_CONNECTOR_VGA, QByteArrayLiteral("VGA")}, {DRM_MODE_CONNECTOR_DVII, QByteArrayLiteral("DVI-I")}, {DRM_MODE_CONNECTOR_DVID, QByteArrayLiteral("DVI-D")}, {DRM_MODE_CONNECTOR_DVIA, QByteArrayLiteral("DVI-A")}, {DRM_MODE_CONNECTOR_Composite, QByteArrayLiteral("Composite")}, {DRM_MODE_CONNECTOR_SVIDEO, QByteArrayLiteral("SVIDEO")}, {DRM_MODE_CONNECTOR_LVDS, QByteArrayLiteral("LVDS")}, {DRM_MODE_CONNECTOR_Component, QByteArrayLiteral("Component")}, {DRM_MODE_CONNECTOR_9PinDIN, QByteArrayLiteral("DIN")}, {DRM_MODE_CONNECTOR_DisplayPort, QByteArrayLiteral("DP")}, {DRM_MODE_CONNECTOR_HDMIA, QByteArrayLiteral("HDMI-A")}, {DRM_MODE_CONNECTOR_HDMIB, QByteArrayLiteral("HDMI-B")}, {DRM_MODE_CONNECTOR_TV, QByteArrayLiteral("TV")}, {DRM_MODE_CONNECTOR_eDP, QByteArrayLiteral("eDP")}, {DRM_MODE_CONNECTOR_VIRTUAL, QByteArrayLiteral("Virtual")}, {DRM_MODE_CONNECTOR_DSI, QByteArrayLiteral("DSI")}, #ifdef DRM_MODE_CONNECTOR_DPI {DRM_MODE_CONNECTOR_DPI, QByteArrayLiteral("DPI")}, #endif }; namespace { quint64 refreshRateForMode(_drmModeModeInfo *m) { // Calculate higher precision (mHz) refresh rate // logic based on Weston, see compositor-drm.c quint64 refreshRate = (m->clock * 1000000LL / m->htotal + m->vtotal / 2) / m->vtotal; if (m->flags & DRM_MODE_FLAG_INTERLACE) { refreshRate *= 2; } if (m->flags & DRM_MODE_FLAG_DBLSCAN) { refreshRate /= 2; } if (m->vscan > 1) { refreshRate /= m->vscan; } return refreshRate; } } bool DrmOutput::init(drmModeConnector *connector) { initEdid(connector); initDpms(connector); initUuid(); if (m_backend->atomicModeSetting()) { if (!initPrimaryPlane()) { return false; } } else if (!m_crtc->blank()) { return false; } setInternal(connector->connector_type == DRM_MODE_CONNECTOR_LVDS || connector->connector_type == DRM_MODE_CONNECTOR_eDP); setDpmsSupported(true); if (isInternal()) { connect(kwinApp(), &Application::screensCreated, this, [this] { connect(screens()->orientationSensor(), &OrientationSensor::orientationChanged, this, &DrmOutput::automaticRotation); } ); } - QSize physicalSize = !m_edid.physicalSize().isEmpty() ? m_edid.physicalSize() : QSize(connector->mmWidth, connector->mmHeight); - // the size might be completely borked. E.g. Samsung SyncMaster 2494HS reports 160x90 while in truth it's 520x292 - // as this information is used to calculate DPI info, it's going to result in everything being huge - const QByteArray unknown = QByteArrayLiteral("unknown"); - KConfigGroup group = kwinApp()->config()->group("EdidOverwrite").group(m_edid.eisaId().isEmpty() ? unknown : m_edid.eisaId()) - .group(m_edid.monitorName().isEmpty() ? unknown : m_edid.monitorName()) - .group(m_edid.serialNumber().isEmpty() ? unknown : m_edid.serialNumber()); - if (group.hasKey("PhysicalSize")) { - const QSize overwriteSize = group.readEntry("PhysicalSize", physicalSize); - qCWarning(KWIN_DRM) << "Overwriting monitor physical size for" << m_edid.eisaId() << "/" << m_edid.monitorName() << "/" << m_edid.serialNumber() << " from " << physicalSize << "to " << overwriteSize; - physicalSize = overwriteSize; - } - setRawPhysicalSize(physicalSize); - initOutputDevice(connector); setEnabled(true); return true; } void DrmOutput::initUuid() { QCryptographicHash hash(QCryptographicHash::Md5); hash.addData(QByteArray::number(m_conn->id())); hash.addData(m_edid.eisaId()); hash.addData(m_edid.monitorName()); hash.addData(m_edid.serialNumber()); m_uuid = hash.result().toHex().left(10); } void DrmOutput::initOutputDevice(drmModeConnector *connector) { QString manufacturer; if (!m_edid.eisaId().isEmpty()) { manufacturer = QString::fromLatin1(m_edid.eisaId()); } QString connectorName = s_connectorNames.value(connector->connector_type, QByteArrayLiteral("Unknown")); QString modelName; if (!m_edid.monitorName().isEmpty()) { QString m = QString::fromLatin1(m_edid.monitorName()); if (!m_edid.serialNumber().isEmpty()) { m.append('/'); m.append(QString::fromLatin1(m_edid.serialNumber())); } modelName = m; } else if (!m_edid.serialNumber().isEmpty()) { modelName = QString::fromLatin1(m_edid.serialNumber()); } else { modelName = i18n("unknown"); } const QString model = connectorName + QStringLiteral("-") + QString::number(connector->connector_type_id) + QStringLiteral("-") + modelName; // read in mode information QVector modes; for (int i = 0; i < connector->count_modes; ++i) { // TODO: in AMS here we could read and store for later every mode's blob_id // would simplify isCurrentMode(..) and presentAtomically(..) in case of mode set auto *m = &connector->modes[i]; KWayland::Server::OutputDeviceInterface::ModeFlags deviceflags; if (isCurrentMode(m)) { deviceflags |= KWayland::Server::OutputDeviceInterface::ModeFlag::Current; } if (m->type & DRM_MODE_TYPE_PREFERRED) { deviceflags |= KWayland::Server::OutputDeviceInterface::ModeFlag::Preferred; } KWayland::Server::OutputDeviceInterface::Mode mode; mode.id = i; mode.size = QSize(m->hdisplay, m->vdisplay); mode.flags = deviceflags; mode.refreshRate = refreshRateForMode(m); modes << mode; } - AbstractWaylandOutput::initWaylandOutputDevice(model, manufacturer, m_uuid, modes); + QSize physicalSize = !m_edid.physicalSize().isEmpty() ? m_edid.physicalSize() : QSize(connector->mmWidth, connector->mmHeight); + // the size might be completely borked. E.g. Samsung SyncMaster 2494HS reports 160x90 while in truth it's 520x292 + // as this information is used to calculate DPI info, it's going to result in everything being huge + const QByteArray unknown = QByteArrayLiteral("unknown"); + KConfigGroup group = kwinApp()->config()->group("EdidOverwrite").group(m_edid.eisaId().isEmpty() ? unknown : m_edid.eisaId()) + .group(m_edid.monitorName().isEmpty() ? unknown : m_edid.monitorName()) + .group(m_edid.serialNumber().isEmpty() ? unknown : m_edid.serialNumber()); + if (group.hasKey("PhysicalSize")) { + const QSize overwriteSize = group.readEntry("PhysicalSize", physicalSize); + qCWarning(KWIN_DRM) << "Overwriting monitor physical size for" << m_edid.eisaId() << "/" << m_edid.monitorName() << "/" << m_edid.serialNumber() << " from " << physicalSize << "to " << overwriteSize; + physicalSize = overwriteSize; + } + + initWaylandOutputDevice(model, manufacturer, m_uuid, physicalSize, modes); } bool DrmOutput::isCurrentMode(const drmModeModeInfo *mode) const { return mode->clock == m_mode.clock && mode->hdisplay == m_mode.hdisplay && mode->hsync_start == m_mode.hsync_start && mode->hsync_end == m_mode.hsync_end && mode->htotal == m_mode.htotal && mode->hskew == m_mode.hskew && mode->vdisplay == m_mode.vdisplay && mode->vsync_start == m_mode.vsync_start && mode->vsync_end == m_mode.vsync_end && mode->vtotal == m_mode.vtotal && mode->vscan == m_mode.vscan && mode->vrefresh == m_mode.vrefresh && mode->flags == m_mode.flags && mode->type == m_mode.type && qstrcmp(mode->name, m_mode.name) == 0; } void DrmOutput::initEdid(drmModeConnector *connector) { DrmScopedPointer edid; for (int i = 0; i < connector->count_props; ++i) { DrmScopedPointer property(drmModeGetProperty(m_backend->fd(), connector->props[i])); if (!property) { continue; } if ((property->flags & DRM_MODE_PROP_BLOB) && qstrcmp(property->name, "EDID") == 0) { edid.reset(drmModeGetPropertyBlob(m_backend->fd(), connector->prop_values[i])); } } if (!edid) { return; } m_edid = Edid(edid->data, edid->length); if (!m_edid.isValid()) { qCWarning(KWIN_DRM, "Couldn't parse EDID for connector with id %d", m_conn->id()); } } bool DrmOutput::initPrimaryPlane() { for (int i = 0; i < m_backend->planes().size(); ++i) { DrmPlane* p = m_backend->planes()[i]; if (!p) { continue; } if (p->type() != DrmPlane::TypeIndex::Primary) { continue; } if (p->output()) { // Plane already has an output continue; } if (m_primaryPlane) { // Output already has a primary plane continue; } if (!p->isCrtcSupported(m_crtc->resIndex())) { continue; } p->setOutput(this); m_primaryPlane = p; qCDebug(KWIN_DRM) << "Initialized primary plane" << p->id() << "on CRTC" << m_crtc->id(); return true; } qCCritical(KWIN_DRM) << "Failed to initialize primary plane."; return false; } bool DrmOutput::initCursorPlane() // TODO: Add call in init (but needs layer support in general first) { for (int i = 0; i < m_backend->planes().size(); ++i) { DrmPlane* p = m_backend->planes()[i]; if (!p) { continue; } if (p->type() != DrmPlane::TypeIndex::Cursor) { continue; } if (p->output()) { // Plane already has an output continue; } if (m_cursorPlane) { // Output already has a cursor plane continue; } if (!p->isCrtcSupported(m_crtc->resIndex())) { continue; } p->setOutput(this); m_cursorPlane = p; qCDebug(KWIN_DRM) << "Initialized cursor plane" << p->id() << "on CRTC" << m_crtc->id(); return true; } return false; } bool DrmOutput::initCursor(const QSize &cursorSize) { auto createCursor = [this, cursorSize] (int index) { m_cursor[index] = m_backend->createBuffer(cursorSize); if (!m_cursor[index]->map(QImage::Format_ARGB32_Premultiplied)) { return false; } return true; }; if (!createCursor(0) || !createCursor(1)) { return false; } return true; } void DrmOutput::initDpms(drmModeConnector *connector) { for (int i = 0; i < connector->count_props; ++i) { DrmScopedPointer property(drmModeGetProperty(m_backend->fd(), connector->props[i])); if (!property) { continue; } if (qstrcmp(property->name, "DPMS") == 0) { m_dpms.swap(property); break; } } } static DrmOutput::DpmsMode fromWaylandDpmsMode(KWayland::Server::OutputInterface::DpmsMode wlMode) { using namespace KWayland::Server; switch (wlMode) { case OutputInterface::DpmsMode::On: return DrmOutput::DpmsMode::On; case OutputInterface::DpmsMode::Standby: return DrmOutput::DpmsMode::Standby; case OutputInterface::DpmsMode::Suspend: return DrmOutput::DpmsMode::Suspend; case OutputInterface::DpmsMode::Off: return DrmOutput::DpmsMode::Off; default: Q_UNREACHABLE(); } } static KWayland::Server::OutputInterface::DpmsMode toWaylandDpmsMode(DrmOutput::DpmsMode mode) { using namespace KWayland::Server; switch (mode) { case DrmOutput::DpmsMode::On: return OutputInterface::DpmsMode::On; case DrmOutput::DpmsMode::Standby: return OutputInterface::DpmsMode::Standby; case DrmOutput::DpmsMode::Suspend: return OutputInterface::DpmsMode::Suspend; case DrmOutput::DpmsMode::Off: return OutputInterface::DpmsMode::Off; default: Q_UNREACHABLE(); } } void DrmOutput::updateDpms(KWayland::Server::OutputInterface::DpmsMode mode) { if (m_dpms.isNull()) { return; } const auto drmMode = fromWaylandDpmsMode(mode); if (drmMode == m_dpmsModePending) { qCDebug(KWIN_DRM) << "New DPMS mode equals old mode. DPMS unchanged."; return; } m_dpmsModePending = drmMode; if (m_backend->atomicModeSetting()) { m_modesetRequested = true; if (drmMode == DpmsMode::On) { if (m_pageFlipPending) { m_pageFlipPending = false; Compositor::self()->bufferSwapComplete(); } dpmsOnHandler(); } else { m_dpmsAtomicOffPending = true; if (!m_pageFlipPending) { dpmsAtomicOff(); } } } else { if (drmModeConnectorSetProperty(m_backend->fd(), m_conn->id(), m_dpms->prop_id, uint64_t(drmMode)) < 0) { m_dpmsModePending = m_dpmsMode; qCWarning(KWIN_DRM) << "Setting DPMS failed"; return; } if (drmMode == DpmsMode::On) { dpmsOnHandler(); } else { dpmsOffHandler(); } m_dpmsMode = m_dpmsModePending; } } void DrmOutput::dpmsOnHandler() { qCDebug(KWIN_DRM) << "DPMS mode set for output" << m_crtc->id() << "to On."; auto wlOutput = waylandOutput(); if (wlOutput) { wlOutput->setDpmsMode(toWaylandDpmsMode(m_dpmsModePending)); } emit dpmsChanged(); m_backend->checkOutputsAreOn(); if (!m_backend->atomicModeSetting()) { m_crtc->blank(); } if (Compositor *compositor = Compositor::self()) { compositor->addRepaintFull(); } } void DrmOutput::dpmsOffHandler() { qCDebug(KWIN_DRM) << "DPMS mode set for output" << m_crtc->id() << "to Off."; auto wlOutput = waylandOutput(); if (wlOutput) { wlOutput->setDpmsMode(toWaylandDpmsMode(m_dpmsModePending)); } emit dpmsChanged(); m_backend->outputWentOff(); } void DrmOutput::transform(KWayland::Server::OutputDeviceInterface::Transform transform) { waylandOutputDevice()->setTransform(transform); using KWayland::Server::OutputDeviceInterface; using KWayland::Server::OutputInterface; auto wlOutput = waylandOutput(); switch (transform) { case OutputDeviceInterface::Transform::Normal: if (m_primaryPlane) { m_primaryPlane->setTransformation(DrmPlane::Transformation::Rotate0); } if (wlOutput) { wlOutput->setTransform(OutputInterface::Transform::Normal); } setOrientation(Qt::PrimaryOrientation); break; case OutputDeviceInterface::Transform::Rotated90: if (m_primaryPlane) { m_primaryPlane->setTransformation(DrmPlane::Transformation::Rotate90); } if (wlOutput) { wlOutput->setTransform(OutputInterface::Transform::Rotated90); } setOrientation(Qt::PortraitOrientation); break; case OutputDeviceInterface::Transform::Rotated180: if (m_primaryPlane) { m_primaryPlane->setTransformation(DrmPlane::Transformation::Rotate180); } if (wlOutput) { wlOutput->setTransform(OutputInterface::Transform::Rotated180); } setOrientation(Qt::InvertedLandscapeOrientation); break; case OutputDeviceInterface::Transform::Rotated270: if (m_primaryPlane) { m_primaryPlane->setTransformation(DrmPlane::Transformation::Rotate270); } if (wlOutput) { wlOutput->setTransform(OutputInterface::Transform::Rotated270); } setOrientation(Qt::InvertedPortraitOrientation); break; case OutputDeviceInterface::Transform::Flipped: // TODO: what is this exactly? if (wlOutput) { wlOutput->setTransform(OutputInterface::Transform::Flipped); } break; case OutputDeviceInterface::Transform::Flipped90: // TODO: what is this exactly? if (wlOutput) { wlOutput->setTransform(OutputInterface::Transform::Flipped90); } break; case OutputDeviceInterface::Transform::Flipped180: // TODO: what is this exactly? if (wlOutput) { wlOutput->setTransform(OutputInterface::Transform::Flipped180); } break; case OutputDeviceInterface::Transform::Flipped270: // TODO: what is this exactly? if (wlOutput) { wlOutput->setTransform(OutputInterface::Transform::Flipped270); } break; } m_modesetRequested = true; // the cursor might need to get rotated updateCursor(); showCursor(); // TODO: are these calls not enough in updateMode already? setWaylandMode(); } void DrmOutput::updateMode(int modeIndex) { // get all modes on the connector DrmScopedPointer connector(drmModeGetConnector(m_backend->fd(), m_conn->id())); if (connector->count_modes <= modeIndex) { // TODO: error? return; } if (isCurrentMode(&connector->modes[modeIndex])) { // nothing to do return; } m_mode = connector->modes[modeIndex]; m_modesetRequested = true; setWaylandMode(); } void DrmOutput::setWaylandMode() { AbstractWaylandOutput::setWaylandMode(QSize(m_mode.hdisplay, m_mode.vdisplay), refreshRateForMode(&m_mode)); } void DrmOutput::pageFlipped() { m_pageFlipPending = false; if (m_deleted) { deleteLater(); return; } if (!m_crtc) { return; } // Egl based surface buffers get destroyed, QPainter based dumb buffers not // TODO: split up DrmOutput in two for dumb and egl/gbm surface buffer compatible subclasses completely? if (m_backend->deleteBufferAfterPageFlip()) { if (m_backend->atomicModeSetting()) { if (!m_primaryPlane->next()) { // on manual vt switch // TODO: when we later use overlay planes it might happen, that we have a page flip with only // damage on one of these, and therefore the primary plane has no next buffer // -> Then we don't want to return here! if (m_primaryPlane->current()) { m_primaryPlane->current()->releaseGbm(); } return; } for (DrmPlane *p : m_nextPlanesFlipList) { p->flipBufferWithDelete(); } m_nextPlanesFlipList.clear(); } else { if (!m_crtc->next()) { // on manual vt switch if (DrmBuffer *b = m_crtc->current()) { b->releaseGbm(); } } m_crtc->flipBuffer(); } } else { if (m_backend->atomicModeSetting()){ for (DrmPlane *p : m_nextPlanesFlipList) { p->flipBuffer(); } m_nextPlanesFlipList.clear(); } else { m_crtc->flipBuffer(); } m_crtc->flipBuffer(); } } bool DrmOutput::present(DrmBuffer *buffer) { if (m_backend->atomicModeSetting()) { return presentAtomically(buffer); } else { return presentLegacy(buffer); } } bool DrmOutput::dpmsAtomicOff() { m_dpmsAtomicOffPending = false; // TODO: With multiple planes: deactivate all of them here delete m_primaryPlane->next(); m_primaryPlane->setNext(nullptr); m_nextPlanesFlipList << m_primaryPlane; if (!doAtomicCommit(AtomicCommitMode::Test)) { qCDebug(KWIN_DRM) << "Atomic test commit to Dpms Off failed. Aborting."; return false; } if (!doAtomicCommit(AtomicCommitMode::Real)) { qCDebug(KWIN_DRM) << "Atomic commit to Dpms Off failed. This should have never happened! Aborting."; return false; } m_nextPlanesFlipList.clear(); dpmsOffHandler(); return true; } bool DrmOutput::presentAtomically(DrmBuffer *buffer) { if (!LogindIntegration::self()->isActiveSession()) { qCWarning(KWIN_DRM) << "Logind session not active."; return false; } if (m_pageFlipPending) { qCWarning(KWIN_DRM) << "Page not yet flipped."; return false; } #if HAVE_EGL_STREAMS if (m_backend->useEglStreams() && !m_modesetRequested) { // EglStreamBackend queues normal page flips through EGL, // modesets are still performed through DRM-KMS m_pageFlipPending = true; return true; } #endif m_primaryPlane->setNext(buffer); m_nextPlanesFlipList << m_primaryPlane; if (!doAtomicCommit(AtomicCommitMode::Test)) { //TODO: When we use planes for layered rendering, fallback to renderer instead. Also for direct scanout? //TODO: Probably should undo setNext and reset the flip list qCDebug(KWIN_DRM) << "Atomic test commit failed. Aborting present."; // go back to previous state if (m_lastWorkingState.valid) { m_mode = m_lastWorkingState.mode; setOrientation(m_lastWorkingState.orientation); setGlobalPos(m_lastWorkingState.globalPos); if (m_primaryPlane) { m_primaryPlane->setTransformation(m_lastWorkingState.planeTransformations); } m_modesetRequested = true; // the cursor might need to get rotated updateCursor(); showCursor(); // TODO: forward to OutputInterface and OutputDeviceInterface setWaylandMode(); emit screens()->changed(); } return false; } const bool wasModeset = m_modesetRequested; if (!doAtomicCommit(AtomicCommitMode::Real)) { qCDebug(KWIN_DRM) << "Atomic commit failed. This should have never happened! Aborting present."; //TODO: Probably should undo setNext and reset the flip list return false; } if (wasModeset) { // store current mode set as new good state m_lastWorkingState.mode = m_mode; m_lastWorkingState.orientation = orientation(); m_lastWorkingState.globalPos = globalPos(); if (m_primaryPlane) { m_lastWorkingState.planeTransformations = m_primaryPlane->transformation(); } m_lastWorkingState.valid = true; } m_pageFlipPending = true; return true; } bool DrmOutput::presentLegacy(DrmBuffer *buffer) { if (m_crtc->next()) { return false; } if (!LogindIntegration::self()->isActiveSession()) { m_crtc->setNext(buffer); return false; } if (m_dpmsMode != DpmsMode::On) { return false; } // Do we need to set a new mode first? if (!m_crtc->current() || m_crtc->current()->needsModeChange(buffer)) { if (!setModeLegacy(buffer)) { return false; } } const bool ok = drmModePageFlip(m_backend->fd(), m_crtc->id(), buffer->bufferId(), DRM_MODE_PAGE_FLIP_EVENT, this) == 0; if (ok) { m_crtc->setNext(buffer); } else { qCWarning(KWIN_DRM) << "Page flip failed:" << strerror(errno); } return ok; } bool DrmOutput::setModeLegacy(DrmBuffer *buffer) { uint32_t connId = m_conn->id(); if (drmModeSetCrtc(m_backend->fd(), m_crtc->id(), buffer->bufferId(), 0, 0, &connId, 1, &m_mode) == 0) { return true; } else { qCWarning(KWIN_DRM) << "Mode setting failed"; return false; } } bool DrmOutput::doAtomicCommit(AtomicCommitMode mode) { drmModeAtomicReq *req = drmModeAtomicAlloc(); auto errorHandler = [this, mode, req] () { if (mode == AtomicCommitMode::Test) { // TODO: when we later test overlay planes, make sure we change only the right stuff back } if (req) { drmModeAtomicFree(req); } if (m_dpmsMode != m_dpmsModePending) { qCWarning(KWIN_DRM) << "Setting DPMS failed"; m_dpmsModePending = m_dpmsMode; if (m_dpmsMode != DpmsMode::On) { dpmsOffHandler(); } } // TODO: see above, rework later for overlay planes! for (DrmPlane *p : m_nextPlanesFlipList) { p->setNext(nullptr); } m_nextPlanesFlipList.clear(); }; if (!req) { qCWarning(KWIN_DRM) << "DRM: couldn't allocate atomic request"; errorHandler(); return false; } uint32_t flags = 0; // Do we need to set a new mode? if (m_modesetRequested) { if (m_dpmsModePending == DpmsMode::On) { if (drmModeCreatePropertyBlob(m_backend->fd(), &m_mode, sizeof(m_mode), &m_blobId) != 0) { qCWarning(KWIN_DRM) << "Failed to create property blob"; errorHandler(); return false; } } if (!atomicReqModesetPopulate(req, m_dpmsModePending == DpmsMode::On)){ qCWarning(KWIN_DRM) << "Failed to populate Atomic Modeset"; errorHandler(); return false; } flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; } if (mode == AtomicCommitMode::Real) { if (m_dpmsModePending == DpmsMode::On) { if (!(flags & DRM_MODE_ATOMIC_ALLOW_MODESET)) { // TODO: Evaluating this condition should only be necessary, as long as we expect older kernels than 4.10. flags |= DRM_MODE_ATOMIC_NONBLOCK; } #if HAVE_EGL_STREAMS if (!m_backend->useEglStreams()) // EglStreamBackend uses the NV_output_drm_flip_event EGL extension // to register the flip event through eglStreamConsumerAcquireAttribNV #endif flags |= DRM_MODE_PAGE_FLIP_EVENT; } } else { flags |= DRM_MODE_ATOMIC_TEST_ONLY; } bool ret = true; // TODO: Make sure when we use more than one plane at a time, that we go through this list in the right order. for (int i = m_nextPlanesFlipList.size() - 1; 0 <= i; i-- ) { DrmPlane *p = m_nextPlanesFlipList[i]; ret &= p->atomicPopulate(req); } if (!ret) { qCWarning(KWIN_DRM) << "Failed to populate atomic planes. Abort atomic commit!"; errorHandler(); return false; } if (drmModeAtomicCommit(m_backend->fd(), req, flags, this)) { qCWarning(KWIN_DRM) << "Atomic request failed to commit:" << strerror(errno); errorHandler(); return false; } if (mode == AtomicCommitMode::Real && (flags & DRM_MODE_ATOMIC_ALLOW_MODESET)) { qCDebug(KWIN_DRM) << "Atomic Modeset successful."; m_modesetRequested = false; m_dpmsMode = m_dpmsModePending; } drmModeAtomicFree(req); return true; } bool DrmOutput::atomicReqModesetPopulate(drmModeAtomicReq *req, bool enable) { if (enable) { m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcX), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcY), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcW), m_mode.hdisplay << 16); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcH), m_mode.vdisplay << 16); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcW), m_mode.hdisplay); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcH), m_mode.vdisplay); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcId), m_crtc->id()); } else { if (m_backend->deleteBufferAfterPageFlip()) { delete m_primaryPlane->current(); delete m_primaryPlane->next(); } m_primaryPlane->setCurrent(nullptr); m_primaryPlane->setNext(nullptr); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcX), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcY), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcW), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcH), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcW), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcH), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcId), 0); } m_conn->setValue(int(DrmConnector::PropertyIndex::CrtcId), enable ? m_crtc->id() : 0); m_crtc->setValue(int(DrmCrtc::PropertyIndex::ModeId), enable ? m_blobId : 0); m_crtc->setValue(int(DrmCrtc::PropertyIndex::Active), enable); bool ret = true; ret &= m_conn->atomicPopulate(req); ret &= m_crtc->atomicPopulate(req); return ret; } bool DrmOutput::supportsTransformations() const { if (!m_primaryPlane) { return false; } const auto transformations = m_primaryPlane->supportedTransformations(); return transformations.testFlag(DrmPlane::Transformation::Rotate90) || transformations.testFlag(DrmPlane::Transformation::Rotate180) || transformations.testFlag(DrmPlane::Transformation::Rotate270); } void DrmOutput::automaticRotation() { if (!m_primaryPlane) { return; } const auto supportedTransformations = m_primaryPlane->supportedTransformations(); const auto requestedTransformation = screens()->orientationSensor()->orientation(); using KWayland::Server::OutputDeviceInterface; OutputDeviceInterface::Transform newTransformation = OutputDeviceInterface::Transform::Normal; switch (requestedTransformation) { case OrientationSensor::Orientation::TopUp: newTransformation = OutputDeviceInterface::Transform::Normal; break; case OrientationSensor::Orientation::TopDown: if (!supportedTransformations.testFlag(DrmPlane::Transformation::Rotate180)) { return; } newTransformation = OutputDeviceInterface::Transform::Rotated180; break; case OrientationSensor::Orientation::LeftUp: if (!supportedTransformations.testFlag(DrmPlane::Transformation::Rotate90)) { return; } newTransformation = OutputDeviceInterface::Transform::Rotated90; break; case OrientationSensor::Orientation::RightUp: if (!supportedTransformations.testFlag(DrmPlane::Transformation::Rotate270)) { return; } newTransformation = OutputDeviceInterface::Transform::Rotated270; break; case OrientationSensor::Orientation::FaceUp: case OrientationSensor::Orientation::FaceDown: case OrientationSensor::Orientation::Undefined: // unsupported return; } transform(newTransformation); emit screens()->changed(); } int DrmOutput::gammaRampSize() const { return m_crtc->gammaRampSize(); } bool DrmOutput::setGammaRamp(const GammaRamp &gamma) { return m_crtc->setGammaRamp(gamma); } } diff --git a/plugins/platforms/fbdev/fb_backend.cpp b/plugins/platforms/fbdev/fb_backend.cpp index 01244f9ca..2b8c5c5ad 100644 --- a/plugins/platforms/fbdev/fb_backend.cpp +++ b/plugins/platforms/fbdev/fb_backend.cpp @@ -1,283 +1,282 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "fb_backend.h" #include "composite.h" #include "logging.h" #include "logind.h" #include "scene_qpainter_fb_backend.h" #include "outputscreens.h" #include "virtual_terminal.h" #include "udev.h" // system #include #include #include #include // Linux #include namespace KWin { -void FramebufferOutput::init(const QSize &size) +void FramebufferOutput::init(const QSize &pixelSize, const QSize &physicalSize) { KWayland::Server::OutputDeviceInterface::Mode mode; mode.id = 0; - mode.size = size; + mode.size = pixelSize; mode.flags = KWayland::Server::OutputDeviceInterface::ModeFlag::Current; mode.refreshRate = 60000; // TODO: get actual refresh rate of fb device? AbstractWaylandOutput::initWaylandOutputDevice("model_TODO", "manufacturer_TODO", - "UUID_TODO", { mode }); + "UUID_TODO", physicalSize, { mode }); } FramebufferBackend::FramebufferBackend(QObject *parent) : Platform(parent) { handleOutputs(); } FramebufferBackend::~FramebufferBackend() { unmap(); if (m_fd >= 0) { close(m_fd); } } Screens *FramebufferBackend::createScreens(QObject *parent) { return new OutputScreens(this, parent); } QPainterBackend *FramebufferBackend::createQPainterBackend() { return new FramebufferQPainterBackend(this); } void FramebufferBackend::init() { setSoftWareCursor(true); LogindIntegration *logind = LogindIntegration::self(); auto takeControl = [logind, this]() { if (logind->hasSessionControl()) { openFrameBuffer(); } else { logind->takeControl(); connect(logind, &LogindIntegration::hasSessionControlChanged, this, &FramebufferBackend::openFrameBuffer); } }; if (logind->isConnected()) { takeControl(); } else { connect(logind, &LogindIntegration::connectedChanged, this, takeControl); } VirtualTerminal::create(this); } void FramebufferBackend::openFrameBuffer() { VirtualTerminal::self()->init(); QString framebufferDevice = deviceIdentifier().constData(); if (framebufferDevice.isEmpty()) { framebufferDevice = QString(Udev().primaryFramebuffer()->devNode()); } int fd = LogindIntegration::self()->takeDevice(framebufferDevice.toUtf8().constData()); qCDebug(KWIN_FB) << "Using frame buffer device:" << framebufferDevice; if (fd < 0) { qCWarning(KWIN_FB) << "Failed to open frame buffer device:" << framebufferDevice << "through logind, trying without"; } fd = open(framebufferDevice.toUtf8().constData(), O_RDWR | O_CLOEXEC); if (fd < 0) { qCWarning(KWIN_FB) << "failed to open frame buffer device:" << framebufferDevice; emit initFailed(); return; } m_fd = fd; if (!handleScreenInfo()) { qCWarning(KWIN_FB) << "failed to handle framebuffer information"; emit initFailed(); return; } initImageFormat(); if (m_imageFormat == QImage::Format_Invalid) { emit initFailed(); return; } setReady(true); emit screensQueried(); } bool FramebufferBackend::handleScreenInfo() { if (m_fd < 0) { return false; } fb_var_screeninfo varinfo; fb_fix_screeninfo fixinfo; // Probe the device for screen information. if (ioctl(m_fd, FBIOGET_FSCREENINFO, &fixinfo) < 0 || ioctl(m_fd, FBIOGET_VSCREENINFO, &varinfo) < 0) { return false; } // Activate the framebuffer device, assuming this is a non-primary framebuffer device varinfo.activate = FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE; ioctl(m_fd, FBIOPUT_VSCREENINFO, &varinfo); // Probe the device for new screen information. if (ioctl(m_fd, FBIOGET_VSCREENINFO, &varinfo) < 0) { return false; } auto *output = new FramebufferOutput(this); - output->init(QSize(varinfo.xres, varinfo.yres)); - output->setRawPhysicalSize(QSize(varinfo.width, varinfo.height)); + output->init(QSize(varinfo.xres, varinfo.yres), QSize(varinfo.width, varinfo.height)); output->setEnabled(true); m_outputs << output; m_id = QByteArray(fixinfo.id); m_red = {varinfo.red.offset, varinfo.red.length}; m_green = {varinfo.green.offset, varinfo.green.length}; m_blue = {varinfo.blue.offset, varinfo.blue.length}; m_alpha = {varinfo.transp.offset, varinfo.transp.length}; m_bitsPerPixel = varinfo.bits_per_pixel; m_bufferLength = fixinfo.smem_len; m_bytesPerLine = fixinfo.line_length; return true; } void FramebufferBackend::map() { if (m_memory) { // already mapped; return; } if (m_fd < 0) { // not valid return; } void *mem = mmap(nullptr, m_bufferLength, PROT_WRITE, MAP_SHARED, m_fd, 0); if (mem == MAP_FAILED) { qCWarning(KWIN_FB) << "Failed to mmap frame buffer"; return; } m_memory = mem; } void FramebufferBackend::unmap() { if (!m_memory) { return; } if (munmap(m_memory, m_bufferLength) < 0) { qCWarning(KWIN_FB) << "Failed to munmap frame buffer"; } m_memory = nullptr; } QSize FramebufferBackend::screenSize() const { if (m_outputs.isEmpty()) { return QSize(); } return m_outputs[0]->pixelSize(); } QImage::Format FramebufferBackend::imageFormat() const { return m_imageFormat; } void FramebufferBackend::initImageFormat() { if (m_fd < 0) { return; } qCDebug(KWIN_FB) << "Bits Per Pixel: " << m_bitsPerPixel; qCDebug(KWIN_FB) << "Buffer Length: " << m_bufferLength; qCDebug(KWIN_FB) << "Bytes Per Line: " << m_bytesPerLine; qCDebug(KWIN_FB) << "Alpha Length: " << m_alpha.length; qCDebug(KWIN_FB) << "Red Length: " << m_red.length; qCDebug(KWIN_FB) << "Green Length: " << m_green.length; qCDebug(KWIN_FB) << "Blue Length: " << m_blue.length; qCDebug(KWIN_FB) << "Blue Offset: " << m_blue.offset; qCDebug(KWIN_FB) << "Green Offset: " << m_green.offset; qCDebug(KWIN_FB) << "Red Offset: " << m_red.offset; qCDebug(KWIN_FB) << "Alpha Offset: " << m_alpha.offset; if (m_bitsPerPixel == 32 && m_red.length == 8 && m_green.length == 8 && m_blue.length == 8 && m_blue.offset == 0 && m_green.offset == 8 && m_red.offset == 16) { qCDebug(KWIN_FB) << "Framebuffer format is RGB32"; m_imageFormat = QImage::Format_RGB32; } else if (m_bitsPerPixel == 32 && m_red.length == 8 && m_green.length == 8 && m_blue.length == 8 && m_alpha.length == 8 && m_red.offset == 0 && m_green.offset == 8 && m_blue.offset == 16 && m_alpha.offset == 24) { qCDebug(KWIN_FB) << "Framebuffer format is RGBA8888"; m_imageFormat = QImage::Format_RGBA8888; } else if (m_bitsPerPixel == 24 && m_red.length == 8 && m_green.length == 8 && m_blue.length == 8 && m_blue.offset == 0 && m_green.offset == 8 && m_red.offset == 16) { qCDebug(KWIN_FB) << "Framebuffer Format is RGB888"; m_bgr = true; m_imageFormat = QImage::Format_RGB888; } else if (m_bitsPerPixel == 16 && m_red.length == 5 && m_green.length == 6 && m_blue.length == 5 && m_blue.offset == 0 && m_green.offset == 5 && m_red.offset == 11) { qCDebug(KWIN_FB) << "Framebuffer Format is RGB16"; m_imageFormat = QImage::Format_RGB16; } else { qCWarning(KWIN_FB) << "Framebuffer format is unknown"; } } Outputs FramebufferBackend::outputs() const { return m_outputs; } Outputs FramebufferBackend::enabledOutputs() const { return m_outputs; } } diff --git a/plugins/platforms/fbdev/fb_backend.h b/plugins/platforms/fbdev/fb_backend.h index 9b366bd28..c71c37721 100644 --- a/plugins/platforms/fbdev/fb_backend.h +++ b/plugins/platforms/fbdev/fb_backend.h @@ -1,123 +1,119 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright 2015 Martin Gräßlin Copyright 2019 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_FB_BACKEND_H #define KWIN_FB_BACKEND_H #include "abstract_wayland_output.h" #include "platform.h" #include #include namespace KWin { class FramebufferOutput : public AbstractWaylandOutput { Q_OBJECT public: FramebufferOutput(QObject *parent = nullptr) : AbstractWaylandOutput(parent) {} ~FramebufferOutput() override = default; - void init(const QSize &size); - - void setRawPhysicalSize(const QSize &set) { - AbstractWaylandOutput::setRawPhysicalSize(set); - } + void init(const QSize &pixelSize, const QSize &physicalSize); }; class KWIN_EXPORT FramebufferBackend : public Platform { Q_OBJECT Q_INTERFACES(KWin::Platform) Q_PLUGIN_METADATA(IID "org.kde.kwin.Platform" FILE "fbdev.json") public: explicit FramebufferBackend(QObject *parent = nullptr); ~FramebufferBackend() override; Screens *createScreens(QObject *parent = nullptr) override; QPainterBackend *createQPainterBackend() override; QSize screenSize() const override; void init() override; bool isValid() const { return m_fd >= 0; } void map(); void unmap(); void *mappedMemory() const { return m_memory; } int bytesPerLine() const { return m_bytesPerLine; } int bufferSize() const { return m_bufferLength; } quint32 bitsPerPixel() const { return m_bitsPerPixel; } QImage::Format imageFormat() const; /** * @returns whether the imageFormat is BGR instead of RGB. */ bool isBGR() const { return m_bgr; } Outputs outputs() const override; Outputs enabledOutputs() const override; QVector supportedCompositors() const override { return QVector{QPainterCompositing}; } private: void openFrameBuffer(); bool handleScreenInfo(); void initImageFormat(); QVector m_outputs; QByteArray m_id; struct Color { quint32 offset; quint32 length; }; Color m_red; Color m_green; Color m_blue; Color m_alpha; quint32 m_bitsPerPixel = 0; int m_fd = -1; quint32 m_bufferLength = 0; int m_bytesPerLine = 0; void *m_memory = nullptr; QImage::Format m_imageFormat = QImage::Format_Invalid; bool m_bgr = false; }; } #endif diff --git a/plugins/platforms/hwcomposer/hwcomposer_backend.cpp b/plugins/platforms/hwcomposer/hwcomposer_backend.cpp index 17b5092ae..b74bc9f2c 100644 --- a/plugins/platforms/hwcomposer/hwcomposer_backend.cpp +++ b/plugins/platforms/hwcomposer/hwcomposer_backend.cpp @@ -1,551 +1,551 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "egl_hwcomposer_backend.h" #include "hwcomposer_backend.h" #include "logging.h" #include "screens_hwcomposer.h" #include "composite.h" #include "input.h" #include "main.h" #include "wayland_server.h" // KWayland #include // KDE #include // Qt #include #include // hybris/android #include #include // linux #include // based on test_hwcomposer.c from libhybris project (Apache 2 licensed) using namespace KWayland::Server; namespace KWin { BacklightInputEventFilter::BacklightInputEventFilter(HwcomposerBackend *backend) : InputEventFilter() , m_backend(backend) { } BacklightInputEventFilter::~BacklightInputEventFilter() = default; bool BacklightInputEventFilter::pointerEvent(QMouseEvent *event, quint32 nativeButton) { Q_UNUSED(event) Q_UNUSED(nativeButton) if (!m_backend->isBacklightOff()) { return false; } toggleBacklight(); return true; } bool BacklightInputEventFilter::wheelEvent(QWheelEvent *event) { Q_UNUSED(event) if (!m_backend->isBacklightOff()) { return false; } toggleBacklight(); return true; } bool BacklightInputEventFilter::keyEvent(QKeyEvent *event) { if (event->key() == Qt::Key_PowerOff && event->type() == QEvent::KeyRelease) { toggleBacklight(); return true; } return m_backend->isBacklightOff(); } bool BacklightInputEventFilter::touchDown(qint32 id, const QPointF &pos, quint32 time) { Q_UNUSED(pos) Q_UNUSED(time) if (!m_backend->isBacklightOff()) { return false; } if (m_touchPoints.isEmpty()) { if (!m_doubleTapTimer.isValid()) { // this is the first tap m_doubleTapTimer.start(); } else { if (m_doubleTapTimer.elapsed() < qApp->doubleClickInterval()) { m_secondTap = true; } else { // took too long. Let's consider it a new click m_doubleTapTimer.restart(); } } } else { // not a double tap m_doubleTapTimer.invalidate(); m_secondTap = false; } m_touchPoints << id; return true; } bool BacklightInputEventFilter::touchUp(qint32 id, quint32 time) { Q_UNUSED(time) m_touchPoints.removeAll(id); if (!m_backend->isBacklightOff()) { return false; } if (m_touchPoints.isEmpty() && m_doubleTapTimer.isValid() && m_secondTap) { if (m_doubleTapTimer.elapsed() < qApp->doubleClickInterval()) { toggleBacklight(); } m_doubleTapTimer.invalidate(); m_secondTap = false; } return true; } bool BacklightInputEventFilter::touchMotion(qint32 id, const QPointF &pos, quint32 time) { Q_UNUSED(id) Q_UNUSED(pos) Q_UNUSED(time) return m_backend->isBacklightOff(); } void BacklightInputEventFilter::toggleBacklight() { // queued to not modify the list of event filters while filtering QMetaObject::invokeMethod(m_backend, "toggleBlankOutput", Qt::QueuedConnection); } HwcomposerBackend::HwcomposerBackend(QObject *parent) : Platform(parent) { if (!QDBusConnection::sessionBus().connect(QStringLiteral("org.kde.Solid.PowerManagement"), QStringLiteral("/org/kde/Solid/PowerManagement/Actions/BrightnessControl"), QStringLiteral("org.kde.Solid.PowerManagement.Actions.BrightnessControl"), QStringLiteral("brightnessChanged"), this, SLOT(screenBrightnessChanged(int)))) { qCWarning(KWIN_HWCOMPOSER) << "Failed to connect to brightness control"; } handleOutputs(); } HwcomposerBackend::~HwcomposerBackend() { if (!m_outputBlank) { toggleBlankOutput(); } } void HwcomposerBackend::init() { hw_module_t *hwcModule = nullptr; if (hw_get_module(HWC_HARDWARE_MODULE_ID, (const hw_module_t **)&hwcModule) != 0) { qCWarning(KWIN_HWCOMPOSER) << "Failed to get hwcomposer module"; emit initFailed(); return; } hwc_composer_device_1_t *hwcDevice = nullptr; if (hwc_open_1(hwcModule, &hwcDevice) != 0) { qCWarning(KWIN_HWCOMPOSER) << "Failed to open hwcomposer device"; emit initFailed(); return; } // unblank, setPowerMode? m_device = hwcDevice; m_hwcVersion = m_device->common.version; if ((m_hwcVersion & 0xffff0000) == 0) { // Assume header version is always 1 uint32_t header_version = 1; // Legacy version encoding m_hwcVersion = (m_hwcVersion << 16) | header_version; } // register callbacks hwc_procs_t *procs = new hwc_procs_t; procs->invalidate = [] (const struct hwc_procs* procs) { Q_UNUSED(procs) }; procs->vsync = [] (const struct hwc_procs* procs, int disp, int64_t timestamp) { Q_UNUSED(procs) if (disp != 0) { return; } dynamic_cast(kwinApp()->platform())->wakeVSync(); }; procs->hotplug = [] (const struct hwc_procs* procs, int disp, int connected) { Q_UNUSED(procs) Q_UNUSED(disp) Q_UNUSED(connected) }; m_device->registerProcs(m_device, procs); //move to HwcomposerOutput + signal initLights(); toggleBlankOutput(); m_filter.reset(new BacklightInputEventFilter(this)); input()->prependInputEventFilter(m_filter.data()); // get display configuration m_output.reset(new HwcomposerOutput(hwcDevice)); if (!m_output->isValid()) { emit initFailed(); return; } if (m_output->refreshRate() != 0) { m_vsyncInterval = 1000000/m_output->refreshRate(); } if (m_lights) { using namespace KWayland::Server; auto updateDpms = [this] { if (!m_output || !m_output->waylandOutput()) { m_output->waylandOutput()->setDpmsMode(m_outputBlank ? OutputInterface::DpmsMode::Off : OutputInterface::DpmsMode::On); } }; connect(this, &HwcomposerBackend::outputBlankChanged, this, updateDpms); connect(m_output.data(), &HwcomposerOutput::dpmsModeRequested, this, [this] (KWayland::Server::OutputInterface::DpmsMode mode) { if (mode == OutputInterface::DpmsMode::On) { if (m_outputBlank) { toggleBlankOutput(); } } else { if (!m_outputBlank) { toggleBlankOutput(); } } } ); } emit screensQueried(); setReady(true); } QSize HwcomposerBackend::size() const { if (m_output) { return m_output->pixelSize(); } return QSize(); } QSize HwcomposerBackend::screenSize() const { if (m_output) { return m_output->pixelSize() / m_output->scale(); } return QSize(); } int HwcomposerBackend::scale() const { if (m_output) { return m_output->scale(); } return 1; } void HwcomposerBackend::initLights() { hw_module_t *lightsModule = nullptr; if (hw_get_module(LIGHTS_HARDWARE_MODULE_ID, (const hw_module_t **)&lightsModule) != 0) { qCWarning(KWIN_HWCOMPOSER) << "Failed to get lights module"; return; } light_device_t *lightsDevice = nullptr; if (lightsModule->methods->open(lightsModule, LIGHT_ID_BACKLIGHT, (hw_device_t **)&lightsDevice) != 0) { qCWarning(KWIN_HWCOMPOSER) << "Failed to create lights device"; return; } m_lights = lightsDevice; } void HwcomposerBackend::toggleBlankOutput() { if (!m_device) { return; } m_outputBlank = !m_outputBlank; toggleScreenBrightness(); #if defined(HWC_DEVICE_API_VERSION_1_4) || defined(HWC_DEVICE_API_VERSION_1_5) if (m_hwcVersion > HWC_DEVICE_API_VERSION_1_3) m_device->setPowerMode(m_device, 0, m_outputBlank ? HWC_POWER_MODE_OFF : HWC_POWER_MODE_NORMAL); else #endif m_device->blank(m_device, 0, m_outputBlank ? 1 : 0); // only disable Vsync, enable happens after next frame rendered if (m_outputBlank) { enableVSync(false); } // enable/disable compositor repainting when blanked setOutputsEnabled(!m_outputBlank); if (Compositor *compositor = Compositor::self()) { if (!m_outputBlank) { compositor->addRepaintFull(); } } emit outputBlankChanged(); } void HwcomposerBackend::toggleScreenBrightness() { if (!m_lights) { return; } const int brightness = m_outputBlank ? 0 : m_oldScreenBrightness; struct light_state_t state; state.flashMode = LIGHT_FLASH_NONE; state.brightnessMode = BRIGHTNESS_MODE_USER; state.color = (int)((0xffU << 24) | (brightness << 16) | (brightness << 8) | brightness); m_lights->set_light(m_lights, &state); } void HwcomposerBackend::enableVSync(bool enable) { if (m_hasVsync == enable) { return; } const int result = m_device->eventControl(m_device, 0, HWC_EVENT_VSYNC, enable ? 1: 0); m_hasVsync = enable && (result == 0); } HwcomposerWindow *HwcomposerBackend::createSurface() { return new HwcomposerWindow(this); } Screens *HwcomposerBackend::createScreens(QObject *parent) { return new HwcomposerScreens(this, parent); } Outputs HwcomposerBackend::outputs() const { if (!m_output.isNull()) { return QVector({m_output.data()}); } return {}; } Outputs HwcomposerBackend::enabledOutputs() const { return outputs(); } OpenGLBackend *HwcomposerBackend::createOpenGLBackend() { return new EglHwcomposerBackend(this); } void HwcomposerBackend::waitVSync() { if (!m_hasVsync) { return; } m_vsyncMutex.lock(); m_vsyncWaitCondition.wait(&m_vsyncMutex, m_vsyncInterval); m_vsyncMutex.unlock(); } void HwcomposerBackend::wakeVSync() { m_vsyncMutex.lock(); m_vsyncWaitCondition.wakeAll(); m_vsyncMutex.unlock(); } static void initLayer(hwc_layer_1_t *layer, const hwc_rect_t &rect, int layerCompositionType) { memset(layer, 0, sizeof(hwc_layer_1_t)); layer->compositionType = layerCompositionType; layer->hints = 0; layer->flags = 0; layer->handle = 0; layer->transform = 0; layer->blending = HWC_BLENDING_NONE; #ifdef HWC_DEVICE_API_VERSION_1_3 layer->sourceCropf.top = 0.0f; layer->sourceCropf.left = 0.0f; layer->sourceCropf.bottom = (float) rect.bottom; layer->sourceCropf.right = (float) rect.right; #else layer->sourceCrop = rect; #endif layer->displayFrame = rect; layer->visibleRegionScreen.numRects = 1; layer->visibleRegionScreen.rects = &layer->displayFrame; layer->acquireFenceFd = -1; layer->releaseFenceFd = -1; layer->planeAlpha = 0xFF; #ifdef HWC_DEVICE_API_VERSION_1_5 layer->surfaceDamage.numRects = 0; #endif } HwcomposerWindow::HwcomposerWindow(HwcomposerBackend *backend) : HWComposerNativeWindow(backend->size().width(), backend->size().height(), HAL_PIXEL_FORMAT_RGBA_8888) , m_backend(backend) { setBufferCount(3); size_t size = sizeof(hwc_display_contents_1_t) + 2 * sizeof(hwc_layer_1_t); hwc_display_contents_1_t *list = (hwc_display_contents_1_t*)malloc(size); m_list = (hwc_display_contents_1_t**)malloc(HWC_NUM_DISPLAY_TYPES * sizeof(hwc_display_contents_1_t *)); for (int i = 0; i < HWC_NUM_DISPLAY_TYPES; ++i) { m_list[i] = nullptr; } // Assign buffer only to the first item, otherwise you get tearing // if passed the same to multiple places // see https://github.com/mer-hybris/qt5-qpa-hwcomposer-plugin/commit/f1d802151e8a4f5d10d60eb8de8e07552b93a34a m_list[0] = list; const hwc_rect_t rect = { 0, 0, m_backend->size().width(), m_backend->size().height() }; initLayer(&list->hwLayers[0], rect, HWC_FRAMEBUFFER); initLayer(&list->hwLayers[1], rect, HWC_FRAMEBUFFER_TARGET); list->retireFenceFd = -1; list->flags = HWC_GEOMETRY_CHANGED; list->numHwLayers = 2; } HwcomposerWindow::~HwcomposerWindow() { // TODO: cleanup } void HwcomposerWindow::present(HWComposerNativeWindowBuffer *buffer) { m_backend->waitVSync(); hwc_composer_device_1_t *device = m_backend->device(); auto fblayer = &m_list[0]->hwLayers[1]; fblayer->handle = buffer->handle; fblayer->acquireFenceFd = getFenceBufferFd(buffer); fblayer->releaseFenceFd = -1; int err = device->prepare(device, 1, m_list); assert(err == 0); err = device->set(device, 1, m_list); assert(err == 0); m_backend->enableVSync(true); setFenceBufferFd(buffer, fblayer->releaseFenceFd); if (m_list[0]->retireFenceFd != -1) { close(m_list[0]->retireFenceFd); m_list[0]->retireFenceFd = -1; } m_list[0]->flags = 0; } HwcomposerOutput::HwcomposerOutput(hwc_composer_device_1_t *device) : AbstractWaylandOutput() , m_device(device) { uint32_t configs[5]; size_t numConfigs = 5; if (device->getDisplayConfigs(device, 0, configs, &numConfigs) != 0) { qCWarning(KWIN_HWCOMPOSER) << "Failed to get hwcomposer display configurations"; return; } int32_t attr_values[5]; uint32_t attributes[] = { HWC_DISPLAY_WIDTH, HWC_DISPLAY_HEIGHT, HWC_DISPLAY_DPI_X, HWC_DISPLAY_DPI_Y, HWC_DISPLAY_VSYNC_PERIOD , HWC_DISPLAY_NO_ATTRIBUTE }; device->getDisplayAttributes(device, 0, configs[0], attributes, attr_values); - QSize pixel(attr_values[0], attr_values[1]); - if (pixel.isEmpty()) { + QSize pixelSize(attr_values[0], attr_values[1]); + if (pixelSize.isEmpty()) { return; } + QSizeF physicalSize; if (attr_values[2] != 0 && attr_values[3] != 0) { static const qreal factor = 25.4; - auto physicalSize = QSizeF(qreal(pixel.width() * 1000) / qreal(attr_values[2]) * factor, - qreal(pixel.height() * 1000) / qreal(attr_values[3]) * factor); - setRawPhysicalSize(physicalSize.toSize()); + physicalSize = QSizeF(qreal(pixelSize.width() * 1000) / qreal(attr_values[2]) * factor, + qreal(pixelSize.height() * 1000) / qreal(attr_values[3]) * factor); } else { // couldn't read physical size, assume 96 dpi - setRawPhysicalSize(pixel / 3.8); + physicalSize = pixelSize / 3.8; } OutputDeviceInterface::Mode mode; mode.id = 0; - mode.size = pixel; + mode.size = pixelSize; mode.flags = OutputDeviceInterface::ModeFlag::Current | OutputDeviceInterface::ModeFlag::Preferred; mode.refreshRate = (attr_values[4] == 0) ? 60000 : 10E11/attr_values[4]; - initWaylandOutputDevice(QString(), QString(), QByteArray(), {mode}); + initWaylandOutputDevice(QString(), QString(), QByteArray(), physicalSize.toSize(), {mode}); setInternal(true); setEnabled(true); setDpmsSupported(true); const auto outputGroup = kwinApp()->config()->group("HWComposerOutputs").group("0"); setScale(outputGroup.readEntry("Scale", 1)); - setWaylandMode(pixel, mode.refreshRate); + setWaylandMode(pixelSize, mode.refreshRate); } HwcomposerOutput::~HwcomposerOutput() { hwc_close_1(m_device); } bool HwcomposerOutput::isValid() const { return isEnabled(); } void HwcomposerOutput::updateDpms(KWayland::Server::OutputInterface::DpmsMode mode) { emit dpmsModeRequested(mode); } } diff --git a/plugins/platforms/virtual/virtual_output.cpp b/plugins/platforms/virtual/virtual_output.cpp index 4f55df8f0..1047d78ed 100644 --- a/plugins/platforms/virtual/virtual_output.cpp +++ b/plugins/platforms/virtual/virtual_output.cpp @@ -1,56 +1,55 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2018 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "virtual_output.h" namespace KWin { VirtualOutput::VirtualOutput(QObject *parent) : AbstractWaylandOutput() { Q_UNUSED(parent); } VirtualOutput::~VirtualOutput() { } void VirtualOutput::init(const QPoint &logicalPosition, const QSize &pixelSize) { KWayland::Server::OutputDeviceInterface::Mode mode; mode.id = 0; mode.size = pixelSize; mode.flags = KWayland::Server::OutputDeviceInterface::ModeFlag::Current; mode.refreshRate = 60000; // TODO AbstractWaylandOutput::initWaylandOutputDevice("model_TODO", "manufacturer_TODO", - "UUID_TODO", { mode }); + "UUID_TODO", pixelSize, { mode }); setEnabled(true); setGeometry(QRect(logicalPosition, pixelSize)); setScale(1.); } void VirtualOutput::setGeometry(const QRect &geo) { // TODO: set mode to have updated pixelSize - setRawPhysicalSize(geo.size()); setGlobalPos(geo.topLeft()); } } diff --git a/plugins/platforms/wayland/wayland_output.cpp b/plugins/platforms/wayland/wayland_output.cpp index 37d80b857..f447ac94b 100644 --- a/plugins/platforms/wayland/wayland_output.cpp +++ b/plugins/platforms/wayland/wayland_output.cpp @@ -1,194 +1,194 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright 2019 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "wayland_output.h" #include "wayland_backend.h" #include "wayland_server.h" #include #include #include #include #include namespace KWin { namespace Wayland { using namespace KWayland::Client; WaylandOutput::WaylandOutput(Surface *surface, WaylandBackend *backend) : AbstractWaylandOutput(backend) , m_surface(surface) , m_backend(backend) { connect(surface, &Surface::frameRendered, [this] { m_rendered = true; emit frameRendered(); }); } WaylandOutput::~WaylandOutput() { m_surface->destroy(); delete m_surface; } void WaylandOutput::init(const QPoint &logicalPosition, const QSize &pixelSize) { KWayland::Server::OutputDeviceInterface::Mode mode; mode.id = 0; mode.size = pixelSize; mode.flags = KWayland::Server::OutputDeviceInterface::ModeFlag::Current; mode.refreshRate = 60000; // TODO: can we get refresh rate data from Wayland host? AbstractWaylandOutput::initWaylandOutputDevice("model_TODO", "manufacturer_TODO", - "UUID_TODO", { mode }); - setRawPhysicalSize(pixelSize); + "UUID_TODO", pixelSize, { mode }); setEnabled(true); setGeometry(logicalPosition, pixelSize); setScale(backend()->initialOutputScale()); } void WaylandOutput::setGeometry(const QPoint &logicalPosition, const QSize &pixelSize) { // TODO: set mode to have updated pixelSize - setRawPhysicalSize(pixelSize); + Q_UNUSED(pixelSize) + setGlobalPos(logicalPosition); } ShellOutput::ShellOutput(Surface *surface, Shell *shell, WaylandBackend *backend) : WaylandOutput(surface, backend) { auto shellSurface = shell->createSurface(surface, this); shellSurface->setToplevel(); } ShellOutput::~ShellOutput() { m_shellSurface->destroy(); delete m_shellSurface; } XdgShellOutput::XdgShellOutput(Surface *surface, XdgShell *xdgShell, WaylandBackend *backend, int number) : WaylandOutput(surface, backend) , m_number(number) { m_xdgShellSurface = xdgShell->createSurface(surface, this); updateWindowTitle(); connect(m_xdgShellSurface, &XdgShellSurface::configureRequested, this, &XdgShellOutput::handleConfigure); connect(m_xdgShellSurface, &XdgShellSurface::closeRequested, qApp, &QCoreApplication::quit); connect(backend, &WaylandBackend::pointerLockSupportedChanged, this, &XdgShellOutput::updateWindowTitle); connect(backend, &WaylandBackend::pointerLockChanged, this, [this](bool locked) { if (locked) { if (!m_hasPointerLock) { // some other output has locked the pointer // this surface can stop trying to lock the pointer lockPointer(nullptr, false); // set it true for the other surface m_hasPointerLock = true; } } else { // just try unlocking lockPointer(nullptr, false); } updateWindowTitle(); }); } XdgShellOutput::~XdgShellOutput() { m_xdgShellSurface->destroy(); delete m_xdgShellSurface; } void XdgShellOutput::handleConfigure(const QSize &size, XdgShellSurface::States states, quint32 serial) { Q_UNUSED(states); if (size.width() == 0 || size.height() == 0) { return; } setGeometry(geometry().topLeft(), size); m_xdgShellSurface->ackConfigure(serial); emit sizeChanged(size); } void XdgShellOutput::updateWindowTitle() { QString grab; if (m_hasPointerLock) { grab = i18n("Press right control to ungrab pointer"); } else if (backend()->pointerConstraints()) { grab = i18n("Press right control key to grab pointer"); } const QString title = i18nc("Title of nested KWin Wayland with Wayland socket identifier as argument", "KDE Wayland Compositor #%1 (%2)", m_number, waylandServer()->display()->socketName()); if (grab.isEmpty()) { m_xdgShellSurface->setTitle(title); } else { m_xdgShellSurface->setTitle(title + QStringLiteral(" — ") + grab); } } void XdgShellOutput::lockPointer(Pointer *pointer, bool lock) { if (!lock) { const bool surfaceWasLocked = m_pointerLock && m_hasPointerLock; delete m_pointerLock; m_pointerLock = nullptr; m_hasPointerLock = false; if (surfaceWasLocked) { emit backend()->pointerLockChanged(false); } return; } Q_ASSERT(!m_pointerLock); m_pointerLock = backend()->pointerConstraints()->lockPointer(surface(), pointer, nullptr, PointerConstraints::LifeTime::OneShot, this); if (!m_pointerLock->isValid()) { delete m_pointerLock; m_pointerLock = nullptr; return; } connect(m_pointerLock, &LockedPointer::locked, this, [this] { m_hasPointerLock = true; emit backend()->pointerLockChanged(true); } ); connect(m_pointerLock, &LockedPointer::unlocked, this, [this] { delete m_pointerLock; m_pointerLock = nullptr; m_hasPointerLock = false; emit backend()->pointerLockChanged(false); } ); } } } diff --git a/plugins/platforms/x11/windowed/x11windowed_output.cpp b/plugins/platforms/x11/windowed/x11windowed_output.cpp index 344be9ed6..8950e69a8 100644 --- a/plugins/platforms/x11/windowed/x11windowed_output.cpp +++ b/plugins/platforms/x11/windowed/x11windowed_output.cpp @@ -1,163 +1,162 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright 2019 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "x11windowed_output.h" #include "x11windowed_backend.h" #include #if HAVE_X11_XINPUT #include #endif #include namespace KWin { X11WindowedOutput::X11WindowedOutput(X11WindowedBackend *backend) : AbstractWaylandOutput(backend) , m_backend(backend) { m_window = xcb_generate_id(m_backend->connection()); } X11WindowedOutput::~X11WindowedOutput() { xcb_unmap_window(m_backend->connection(), m_window); xcb_destroy_window(m_backend->connection(), m_window); delete m_winInfo; xcb_flush(m_backend->connection()); } void X11WindowedOutput::init(const QPoint &logicalPosition, const QSize &pixelSize) { KWayland::Server::OutputDeviceInterface::Mode mode; mode.id = 0; mode.size = pixelSize; mode.flags = KWayland::Server::OutputDeviceInterface::ModeFlag::Current; mode.refreshRate = 60000; // TODO: get refresh rate via randr + + // Physicial size must be adjusted, such that QPA calculates correct sizes of + // internal elements. + const QSize physicalSize = pixelSize / 96.0 * 25.4 / scale(); AbstractWaylandOutput::initWaylandOutputDevice("model_TODO", "manufacturer_TODO", - "UUID_TODO", { mode }); + "UUID_TODO", physicalSize, { mode }); setEnabled(true); setGeometry(logicalPosition, pixelSize); setScale(m_backend->initialOutputScale()); uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK; const uint32_t values[] = { m_backend->screen()->black_pixel, XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE | XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_EXPOSURE }; xcb_create_window(m_backend->connection(), XCB_COPY_FROM_PARENT, m_window, m_backend->screen()->root, 0, 0, pixelSize.width(), pixelSize.height(), 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, mask, values); // select xinput 2 events initXInputForWindow(); m_winInfo = new NETWinInfo(m_backend->connection(), m_window, m_backend->screen()->root, NET::WMWindowType, NET::Properties2()); m_winInfo->setWindowType(NET::Normal); m_winInfo->setPid(QCoreApplication::applicationPid()); QIcon windowIcon = QIcon::fromTheme(QStringLiteral("kwin")); auto addIcon = [&windowIcon, this] (const QSize &size) { if (windowIcon.actualSize(size) != size) { return; } NETIcon icon; icon.data = windowIcon.pixmap(size).toImage().bits(); icon.size.width = size.width(); icon.size.height = size.height(); m_winInfo->setIcon(icon, false); }; addIcon(QSize(16, 16)); addIcon(QSize(32, 32)); addIcon(QSize(48, 48)); xcb_map_window(m_backend->connection(), m_window); } void X11WindowedOutput::initXInputForWindow() { if (!m_backend->hasXInput()) { return; } #if HAVE_X11_XINPUT XIEventMask evmasks[1]; unsigned char mask1[XIMaskLen(XI_LASTEVENT)]; memset(mask1, 0, sizeof(mask1)); XISetMask(mask1, XI_TouchBegin); XISetMask(mask1, XI_TouchUpdate); XISetMask(mask1, XI_TouchOwnership); XISetMask(mask1, XI_TouchEnd); evmasks[0].deviceid = XIAllMasterDevices; evmasks[0].mask_len = sizeof(mask1); evmasks[0].mask = mask1; XISelectEvents(m_backend->display(), m_window, evmasks, 1); #else Q_UNUSED(window) #endif } void X11WindowedOutput::setGeometry(const QPoint &logicalPosition, const QSize &pixelSize) { // TODO: set mode to have updated pixelSize - - // Physicial size must be adjusted, such that QPA calculates correct sizes of - // internal elements. - setRawPhysicalSize(pixelSize / 96.0 * 25.4 / scale()); - setGlobalPos(logicalPosition); } void X11WindowedOutput::setWindowTitle(const QString &title) { m_winInfo->setName(title.toUtf8().constData()); } QPoint X11WindowedOutput::internalPosition() const { return geometry().topLeft(); } void X11WindowedOutput::setHostPosition(const QPoint &pos) { m_hostPosition = pos; } }