diff --git a/backends/drm/CMakeLists.txt b/backends/drm/CMakeLists.txt index 1fda8a0f7..3f6a99a35 100644 --- a/backends/drm/CMakeLists.txt +++ b/backends/drm/CMakeLists.txt @@ -1,24 +1,27 @@ set(DRM_SOURCES drm_backend.cpp + drm_output.cpp + drm_buffer.cpp + drm_inputeventfilter.cpp logging.cpp scene_qpainter_drm_backend.cpp screens_drm.cpp ) if(HAVE_GBM) set(DRM_SOURCES ${DRM_SOURCES} egl_gbm_backend.cpp) endif() add_library(KWinWaylandDrmBackend MODULE ${DRM_SOURCES}) target_link_libraries(KWinWaylandDrmBackend kwin Libdrm::Libdrm) if(HAVE_GBM) target_link_libraries(KWinWaylandDrmBackend gbm::gbm) endif() install( TARGETS KWinWaylandDrmBackend DESTINATION ${PLUGIN_INSTALL_DIR}/org.kde.kwin.waylandbackends/ ) diff --git a/backends/drm/drm_backend.cpp b/backends/drm/drm_backend.cpp index 08cf29d87..01addb3a7 100644 --- a/backends/drm/drm_backend.cpp +++ b/backends/drm/drm_backend.cpp @@ -1,1360 +1,602 @@ /******************************************************************** 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_backend.h" +#include "drm_output.h" #include "composite.h" #include "cursor.h" #include "logging.h" #include "logind.h" #include "scene_qpainter_drm_backend.h" #include "screens_drm.h" #include "udev.h" #include "virtual_terminal.h" #include "wayland_server.h" #if HAVE_GBM #include "egl_gbm_backend.h" #endif // KWayland -#include -#include -#include -#include -#include #include // KF5 #include #include #include // Qt #include #include #include // system #include -#include // drm #include #include #include -#if HAVE_GBM -#include -#endif #ifndef DRM_CAP_CURSOR_WIDTH #define DRM_CAP_CURSOR_WIDTH 0x8 #endif #ifndef DRM_CAP_CURSOR_HEIGHT #define DRM_CAP_CURSOR_HEIGHT 0x9 #endif namespace KWin { -DpmsInputEventFilter::DpmsInputEventFilter(DrmBackend *backend) - : InputEventFilter() - , m_backend(backend) -{ -} - -DpmsInputEventFilter::~DpmsInputEventFilter() = default; - -bool DpmsInputEventFilter::pointerEvent(QMouseEvent *event, quint32 nativeButton) -{ - Q_UNUSED(event) - Q_UNUSED(nativeButton) - notify(); - return true; -} - -bool DpmsInputEventFilter::wheelEvent(QWheelEvent *event) -{ - Q_UNUSED(event) - notify(); - return true; -} - -bool DpmsInputEventFilter::keyEvent(QKeyEvent *event) -{ - Q_UNUSED(event) - notify(); - return true; -} - -bool DpmsInputEventFilter::touchDown(quint32 id, const QPointF &pos, quint32 time) -{ - Q_UNUSED(pos) - Q_UNUSED(time) - 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 DpmsInputEventFilter::touchUp(quint32 id, quint32 time) -{ - Q_UNUSED(time) - m_touchPoints.removeAll(id); - if (m_touchPoints.isEmpty() && m_doubleTapTimer.isValid() && m_secondTap) { - if (m_doubleTapTimer.elapsed() < qApp->doubleClickInterval()) { - notify(); - } - m_doubleTapTimer.invalidate(); - m_secondTap = false; - } - return true; -} - -bool DpmsInputEventFilter::touchMotion(quint32 id, const QPointF &pos, quint32 time) -{ - Q_UNUSED(id) - Q_UNUSED(pos) - Q_UNUSED(time) - // ignore the event - return true; -} - -void DpmsInputEventFilter::notify() -{ - // queued to not modify the list of event filters while filtering - QMetaObject::invokeMethod(m_backend, "turnOutputsOn", Qt::QueuedConnection); -} - DrmBackend::DrmBackend(QObject *parent) : AbstractBackend(parent) , m_udev(new Udev) , m_udevMonitor(m_udev->monitor()) , m_dpmsFilter() { handleOutputs(); m_cursor[0] = nullptr; m_cursor[1] = nullptr; } DrmBackend::~DrmBackend() { if (m_fd >= 0) { // wait for pageflips while (m_pageFlipsPending != 0) { QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents); } qDeleteAll(m_outputs); delete m_cursor[0]; delete m_cursor[1]; close(m_fd); } } void DrmBackend::init() { LogindIntegration *logind = LogindIntegration::self(); auto takeControl = [logind, this]() { if (logind->hasSessionControl()) { openDrm(); } else { logind->takeControl(); connect(logind, &LogindIntegration::hasSessionControlChanged, this, &DrmBackend::openDrm); } }; if (logind->isConnected()) { takeControl(); } else { connect(logind, &LogindIntegration::connectedChanged, this, takeControl); } auto v = VirtualTerminal::create(this); connect(v, &VirtualTerminal::activeChanged, this, &DrmBackend::activate); } void DrmBackend::outputWentOff() { if (!m_dpmsFilter.isNull()) { // already another output is off return; } m_dpmsFilter.reset(new DpmsInputEventFilter(this)); input()->prepandInputEventFilter(m_dpmsFilter.data()); } void DrmBackend::turnOutputsOn() { m_dpmsFilter.reset(); for (auto it = m_outputs.constBegin(), end = m_outputs.constEnd(); it != end; it++) { (*it)->setDpms(DrmOutput::DpmsMode::On); } } void DrmBackend::checkOutputsAreOn() { if (m_dpmsFilter.isNull()) { // already disabled, all outputs are on return; } for (auto it = m_outputs.constBegin(), end = m_outputs.constEnd(); it != end; it++) { if (!(*it)->isDpmsEnabled()) { // dpms still disabled, need to keep the filter return; } } // all outputs are on, disable the filter m_dpmsFilter.reset(); } void DrmBackend::activate(bool active) { if (active) { reactivate(); } else { deactivate(); } } void DrmBackend::reactivate() { if (m_active) { return; } m_active = true; DrmBuffer *c = m_cursor[(m_cursorIndex + 1) % 2]; const QPoint cp = Cursor::pos() - softwareCursorHotspot(); for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { DrmOutput *o = *it; o->pageFlipped(); o->blank(); o->showCursor(c); o->moveCursor(cp); } // restart compositor m_pageFlipsPending = 0; if (Compositor *compositor = Compositor::self()) { compositor->bufferSwapComplete(); compositor->addRepaintFull(); } } void DrmBackend::deactivate() { if (!m_active) { return; } // block compositor if (m_pageFlipsPending == 0 && Compositor::self()) { Compositor::self()->aboutToSwapBuffers(); } // hide cursor and disable for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { DrmOutput *o = *it; o->hideCursor(); o->restoreSaved(); } m_active = false; } void DrmBackend::pageFlipHandler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *data) { Q_UNUSED(fd) Q_UNUSED(frame) Q_UNUSED(sec) Q_UNUSED(usec) auto output = reinterpret_cast(data); output->pageFlipped(); output->m_backend->m_pageFlipsPending--; if (output->m_backend->m_pageFlipsPending == 0) { // TODO: improve, this currently means we wait for all page flips or all outputs. // It would be better to driver the repaint per output if (Compositor::self()) { Compositor::self()->bufferSwapComplete(); } } } void DrmBackend::openDrm() { connect(LogindIntegration::self(), &LogindIntegration::sessionActiveChanged, this, &DrmBackend::activate); VirtualTerminal::self()->init(); UdevDevice::Ptr device = m_udev->primaryGpu(); if (!device) { qCWarning(KWIN_DRM) << "Did not find a GPU"; return; } int fd = LogindIntegration::self()->takeDevice(device->devNode()); if (fd < 0) { qCWarning(KWIN_DRM) << "failed to open drm device at" << device->devNode(); return; } m_fd = fd; m_active = true; QSocketNotifier *notifier = new QSocketNotifier(m_fd, QSocketNotifier::Read, this); connect(notifier, &QSocketNotifier::activated, this, [this] { if (!VirtualTerminal::self()->isActive()) { return; } drmEventContext e; memset(&e, 0, sizeof e); e.version = DRM_EVENT_CONTEXT_VERSION; e.page_flip_handler = pageFlipHandler; drmHandleEvent(m_fd, &e); } ); m_drmId = device->sysNum(); queryResources(); // setup udevMonitor if (m_udevMonitor) { m_udevMonitor->filterSubsystemDevType("drm"); const int fd = m_udevMonitor->fd(); if (fd != -1) { QSocketNotifier *notifier = new QSocketNotifier(fd, QSocketNotifier::Read, this); connect(notifier, &QSocketNotifier::activated, this, [this] { auto device = m_udevMonitor->getDevice(); if (!device) { return; } if (device->sysNum() != m_drmId) { return; } if (device->hasProperty("HOTPLUG", "1")) { qCDebug(KWIN_DRM) << "Received hot plug event for monitored drm device"; queryResources(); m_cursorIndex = (m_cursorIndex + 1) % 2; updateCursor(); } } ); m_udevMonitor->enable(); } } setReady(true); initCursor(); } void DrmBackend::queryResources() { if (m_fd < 0) { return; } ScopedDrmPointer<_drmModeRes, &drmModeFreeResources> resources(drmModeGetResources(m_fd)); if (!resources) { qCWarning(KWIN_DRM) << "drmModeGetResources failed"; return; } QVector connectedOutputs; for (int i = 0; i < resources->count_connectors; ++i) { const auto id = resources->connectors[i]; ScopedDrmPointer<_drmModeConnector, &drmModeFreeConnector> connector(drmModeGetConnector(m_fd, id)); if (!connector) { continue; } if (connector->connection != DRM_MODE_CONNECTED) { continue; } if (connector->count_modes == 0) { continue; } if (DrmOutput *o = findOutput(connector->connector_id)) { connectedOutputs << o; continue; } bool crtcFound = false; const quint32 crtcId = findCrtc(resources.data(), connector.data(), &crtcFound); if (!crtcFound) { continue; } ScopedDrmPointer<_drmModeCrtc, &drmModeFreeCrtc> crtc(drmModeGetCrtc(m_fd, crtcId)); if (!crtc) { continue; } DrmOutput *drmOutput = new DrmOutput(this); connect(drmOutput, &DrmOutput::dpmsChanged, this, &DrmBackend::outputDpmsChanged); drmOutput->m_crtcId = crtcId; if (crtc->mode_valid) { drmOutput->m_mode = crtc->mode; } else { drmOutput->m_mode = connector->modes[0]; } drmOutput->m_connector = connector->connector_id; drmOutput->init(connector.data()); qCDebug(KWIN_DRM) << "Found new output with uuid" << drmOutput->uuid(); connectedOutputs << drmOutput; } std::sort(connectedOutputs.begin(), connectedOutputs.end(), [] (DrmOutput *a, DrmOutput *b) { return a->m_connector < b->m_connector; }); // check for outputs which got removed auto it = m_outputs.begin(); while (it != m_outputs.end()) { if (connectedOutputs.contains(*it)) { it++; continue; } DrmOutput *removed = *it; it = m_outputs.erase(it); emit outputRemoved(removed); delete removed; } for (auto it = connectedOutputs.constBegin(); it != connectedOutputs.constEnd(); ++it) { if (!m_outputs.contains(*it)) { emit outputAdded(*it); } } m_outputs = connectedOutputs; readOutputsConfiguration(); emit screensQueried(); } void DrmBackend::readOutputsConfiguration() { if (m_outputs.isEmpty()) { return; } const QByteArray uuid = generateOutputConfigurationUuid(); const auto outputGroup = kwinApp()->config()->group("DrmOutputs"); const auto configGroup = outputGroup.group(uuid); qCDebug(KWIN_DRM) << "Reading output configuration for" << uuid; // default position goes from left to right QPoint pos(0, 0); for (auto it = m_outputs.begin(); it != m_outputs.end(); ++it) { const auto outputConfig = configGroup.group((*it)->uuid()); (*it)->setGlobalPos(outputConfig.readEntry("Position", pos)); // TODO: add mode pos.setX(pos.x() + (*it)->size().width()); } } QByteArray DrmBackend::generateOutputConfigurationUuid() const { auto it = m_outputs.constBegin(); if (m_outputs.size() == 1) { // special case: one output return (*it)->uuid(); } QCryptographicHash hash(QCryptographicHash::Md5); for (; it != m_outputs.constEnd(); ++it) { hash.addData((*it)->uuid()); } return hash.result().toHex().left(10); } void DrmBackend::configurationChangeRequested(KWayland::Server::OutputConfigurationInterface *config) { const auto changes = config->changes(); for (auto it = changes.begin(); it != changes.end(); it++) { KWayland::Server::OutputChangeSet *changeset = it.value(); auto drmoutput = findOutput(it.key()->uuid()); if (drmoutput == nullptr) { qCWarning(KWIN_DRM) << "Could NOT find DrmOutput matching " << it.key()->uuid(); return; } drmoutput->setChanges(changeset); } } DrmOutput *DrmBackend::findOutput(quint32 connector) { auto it = std::find_if(m_outputs.constBegin(), m_outputs.constEnd(), [connector] (DrmOutput *o) { return o->m_connector == connector; }); if (it != m_outputs.constEnd()) { return *it; } return nullptr; } DrmOutput *DrmBackend::findOutput(const QByteArray &uuid) { auto it = std::find_if(m_outputs.constBegin(), m_outputs.constEnd(), [uuid] (DrmOutput *o) { return o->m_uuid == uuid; }); if (it != m_outputs.constEnd()) { return *it; } return nullptr; } quint32 DrmBackend::findCrtc(drmModeRes *res, drmModeConnector *connector, bool *ok) { if (ok) { *ok = false; } ScopedDrmPointer<_drmModeEncoder, &drmModeFreeEncoder> encoder(drmModeGetEncoder(m_fd, connector->encoder_id)); if (encoder) { if (!crtcIsUsed(encoder->crtc_id)) { if (ok) { *ok = true; } return encoder->crtc_id; } } // let's iterate over all encoders to find a suitable crtc for (int i = 0; i < connector->count_encoders; ++i) { ScopedDrmPointer<_drmModeEncoder, &drmModeFreeEncoder> encoder(drmModeGetEncoder(m_fd, connector->encoders[i])); if (!encoder) { continue; } for (int j = 0; j < res->count_crtcs; ++j) { if (!(encoder->possible_crtcs & (1 << j))) { continue; } if (!crtcIsUsed(res->crtcs[j])) { if (ok) { *ok = true; } return res->crtcs[j]; } } } return 0; } bool DrmBackend::crtcIsUsed(quint32 crtc) { auto it = std::find_if(m_outputs.constBegin(), m_outputs.constEnd(), [crtc] (DrmOutput *o) { return o->m_crtcId == crtc; } ); return it != m_outputs.constEnd(); } void DrmBackend::present(DrmBuffer *buffer, DrmOutput *output) { if (output->present(buffer)) { m_pageFlipsPending++; if (m_pageFlipsPending == 1 && Compositor::self()) { Compositor::self()->aboutToSwapBuffers(); } } } void DrmBackend::initCursor() { uint64_t capability = 0; QSize cursorSize; if (drmGetCap(m_fd, DRM_CAP_CURSOR_WIDTH, &capability) == 0) { cursorSize.setWidth(capability); } else { cursorSize.setWidth(64); } if (drmGetCap(m_fd, DRM_CAP_CURSOR_HEIGHT, &capability) == 0) { cursorSize.setHeight(capability); } else { cursorSize.setHeight(64); } m_cursor[0] = createBuffer(cursorSize); m_cursor[0]->map(QImage::Format_ARGB32_Premultiplied); m_cursor[0]->image()->fill(Qt::transparent); m_cursor[1] = createBuffer(cursorSize); m_cursor[1]->map(QImage::Format_ARGB32_Premultiplied); m_cursor[0]->image()->fill(Qt::transparent); // now we have screens and can set cursors, so start tracking connect(this, &DrmBackend::cursorChanged, this, &DrmBackend::updateCursor); connect(Cursor::self(), &Cursor::posChanged, this, &DrmBackend::moveCursor); } void DrmBackend::setCursor() { DrmBuffer *c = m_cursor[m_cursorIndex]; m_cursorIndex = (m_cursorIndex + 1) % 2; for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { (*it)->showCursor(c); } markCursorAsRendered(); } void DrmBackend::updateCursor() { const QImage &cursorImage = softwareCursor(); if (cursorImage.isNull()) { hideCursor(); return; } QImage *c = m_cursor[m_cursorIndex]->image(); c->fill(Qt::transparent); QPainter p; p.begin(c); p.drawImage(QPoint(0, 0), cursorImage); p.end(); setCursor(); moveCursor(); } void DrmBackend::hideCursor() { for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { (*it)->hideCursor(); } } void DrmBackend::moveCursor() { const QPoint p = Cursor::pos() - softwareCursorHotspot(); for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { (*it)->moveCursor(p); } } QSize DrmBackend::size() const { if (m_outputs.isEmpty()) { return QSize(); } return m_outputs.first()->size(); } Screens *DrmBackend::createScreens(QObject *parent) { return new DrmScreens(this, parent); } QPainterBackend *DrmBackend::createQPainterBackend() { return new DrmQPainterBackend(this); } OpenGLBackend *DrmBackend::createOpenGLBackend() { #if HAVE_GBM return new EglGbmBackend(this); #else return AbstractBackend::createOpenGLBackend(); #endif } DrmBuffer *DrmBackend::createBuffer(const QSize &size) { DrmBuffer *b = new DrmBuffer(this, size); m_buffers << b; return b; } DrmBuffer *DrmBackend::createBuffer(gbm_surface *surface) { #if HAVE_GBM DrmBuffer *b = new DrmBuffer(this, surface); m_buffers << b; return b; #else return nullptr; #endif } void DrmBackend::bufferDestroyed(DrmBuffer *b) { m_buffers.removeAll(b); } void DrmBackend::outputDpmsChanged() { if (m_outputs.isEmpty()) { return; } bool enabled = false; for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { enabled = enabled || (*it)->isDpmsEnabled(); } setOutputsEnabled(enabled); } -DrmOutput::DrmOutput(DrmBackend *backend) - : QObject() - , m_backend(backend) -{ -} - -DrmOutput::~DrmOutput() -{ - hideCursor(); - cleanupBlackBuffer(); - delete m_waylandOutput.data(); - delete m_waylandOutputDevice.data(); -} - -void DrmOutput::hideCursor() -{ - drmModeSetCursor(m_backend->fd(), m_crtcId, 0, 0, 0); -} - -void DrmOutput::restoreSaved() -{ - if (!m_savedCrtc.isNull()) { - drmModeSetCrtc(m_backend->fd(), m_savedCrtc->crtc_id, m_savedCrtc->buffer_id, - m_savedCrtc->x, m_savedCrtc->y, &m_connector, 1, &m_savedCrtc->mode); - } -} - -void DrmOutput::showCursor(DrmBuffer *c) -{ - const QSize &s = c->size(); - drmModeSetCursor(m_backend->fd(), m_crtcId, c->handle(), s.width(), s.height()); -} - -void DrmOutput::moveCursor(const QPoint &globalPos) -{ - const QPoint p = globalPos - m_globalPos; - drmModeMoveCursor(m_backend->fd(), m_crtcId, p.x(), p.y()); -} - -QSize DrmOutput::size() const -{ - return QSize(m_mode.hdisplay, m_mode.vdisplay); -} - -QRect DrmOutput::geometry() const -{ - return QRect(m_globalPos, size()); -} - -bool DrmOutput::present(DrmBuffer *buffer) -{ - if (!buffer || buffer->bufferId() == 0) { - return false; - } - if (!VirtualTerminal::self()->isActive()) { - m_currentBuffer = buffer; - return false; - } - if (m_dpmsMode != DpmsMode::On) { - return false; - } - if (m_currentBuffer) { - return false; - } - if (m_lastStride != buffer->stride() || m_lastGbm != buffer->isGbm()) { - // need to set a new mode first - if (!setMode(buffer)) { - return false; - } - } - const bool ok = drmModePageFlip(m_backend->fd(), m_crtcId, buffer->bufferId(), DRM_MODE_PAGE_FLIP_EVENT, this) == 0; - if (ok) { - m_currentBuffer = buffer; - } else { - qCWarning(KWIN_DRM) << "Page flip failed"; - buffer->releaseGbm(); - } - return ok; -} - -void DrmOutput::pageFlipped() -{ - if (!m_currentBuffer) { - return; - } - m_currentBuffer->releaseGbm(); - m_currentBuffer = nullptr; - cleanupBlackBuffer(); -} - -void DrmOutput::cleanupBlackBuffer() -{ - if (m_blackBuffer) { - delete m_blackBuffer; - m_blackBuffer = nullptr; - } -} - -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(); - } -} - -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(); - } -} - -void DrmOutput::init(drmModeConnector *connector) -{ - initEdid(connector); - initDpms(connector); - initUuid(); - m_savedCrtc.reset(drmModeGetCrtc(m_backend->fd(), m_crtcId)); - blank(); - setDpms(DpmsMode::On); - if (!m_waylandOutput.isNull()) { - delete m_waylandOutput.data(); - m_waylandOutput.clear(); - } - m_waylandOutput = waylandServer()->display()->createOutput(); - if (!m_waylandOutputDevice.isNull()) { - delete m_waylandOutputDevice.data(); - m_waylandOutputDevice.clear(); - } - m_waylandOutputDevice = waylandServer()->display()->createOutputDevice(); - m_waylandOutputDevice->setUuid(m_uuid); - - if (!m_edid.eisaId.isEmpty()) { - m_waylandOutput->setManufacturer(QString::fromLatin1(m_edid.eisaId)); - } else { - m_waylandOutput->setManufacturer(i18n("unknown")); - } - m_waylandOutputDevice->setManufacturer(m_waylandOutput->manufacturer()); - - if (!m_edid.monitorName.isEmpty()) { - QString model = QString::fromLatin1(m_edid.monitorName); - if (!m_edid.serialNumber.isEmpty()) { - model.append('/'); - model.append(QString::fromLatin1(m_edid.serialNumber)); - } - m_waylandOutput->setModel(model); - } else if (!m_edid.serialNumber.isEmpty()) { - m_waylandOutput->setModel(QString::fromLatin1(m_edid.serialNumber)); - } else { - m_waylandOutput->setModel(i18n("unknown")); - } - m_waylandOutputDevice->setModel(m_waylandOutput->model()); - - 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("unkown"); - 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; - } - m_waylandOutput->setPhysicalSize(physicalSize); - m_waylandOutputDevice->setPhysicalSize(physicalSize); - - // read in mode information - for (int i = 0; i < connector->count_modes; ++i) { - auto *m = &connector->modes[i]; - KWayland::Server::OutputInterface::ModeFlags flags; - KWayland::Server::OutputDeviceInterface::ModeFlags deviceflags; - if (isCurrentMode(m)) { - flags |= KWayland::Server::OutputInterface::ModeFlag::Current; - deviceflags |= KWayland::Server::OutputDeviceInterface::ModeFlag::Current; - } - if (m->type & DRM_MODE_TYPE_PREFERRED) { - flags |= KWayland::Server::OutputInterface::ModeFlag::Preferred; - deviceflags |= KWayland::Server::OutputDeviceInterface::ModeFlag::Preferred; - } - - // 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; - } - m_waylandOutput->addMode(QSize(m->hdisplay, m->vdisplay), flags, refreshRate); - - KWayland::Server::OutputDeviceInterface::Mode mode; - mode.id = i; - mode.size = QSize(m->hdisplay, m->vdisplay); - mode.flags = deviceflags; - mode.refreshRate = refreshRate; - qCDebug(KWIN_DRM) << "Adding mode: " << i << mode.size; - m_waylandOutputDevice->addMode(mode); - } - - // set dpms - if (!m_dpms.isNull()) { - m_waylandOutput->setDpmsSupported(true); - m_waylandOutput->setDpmsMode(toWaylandDpmsMode(m_dpmsMode)); - connect(m_waylandOutput.data(), &KWayland::Server::OutputInterface::dpmsModeRequested, this, - [this] (KWayland::Server::OutputInterface::DpmsMode mode) { - setDpms(fromWaylandDpmsMode(mode)); - }, Qt::QueuedConnection - ); - } - - m_waylandOutput->create(); - qCDebug(KWIN_DRM) << "Created OutputDevice"; - m_waylandOutputDevice->create(); -} - -void DrmOutput::initUuid() -{ - QCryptographicHash hash(QCryptographicHash::Md5); - hash.addData(QByteArray::number(m_connector)); - hash.addData(m_edid.eisaId); - hash.addData(m_edid.monitorName); - hash.addData(m_edid.serialNumber); - m_uuid = hash.result().toHex().left(10); -} - -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::blank() -{ - if (!m_blackBuffer) { - m_blackBuffer = m_backend->createBuffer(size()); - m_blackBuffer->map(); - m_blackBuffer->image()->fill(Qt::black); - } - setMode(m_blackBuffer); -} - -bool DrmOutput::setMode(DrmBuffer *buffer) -{ - if (drmModeSetCrtc(m_backend->fd(), m_crtcId, buffer->bufferId(), 0, 0, &m_connector, 1, &m_mode) == 0) { - m_lastStride = buffer->stride(); - m_lastGbm = buffer->isGbm(); - return true; - } else { - qCWarning(KWIN_DRM) << "Mode setting failed"; - return false; - } -} - -static bool verifyEdidHeader(drmModePropertyBlobPtr edid) -{ - const uint8_t *data = reinterpret_cast(edid->data); - if (data[0] != 0x00) { - return false; - } - for (int i = 1; i < 7; ++i) { - if (data[i] != 0xFF) { - return false; - } - } - if (data[7] != 0x00) { - return false; - } - return true; -} - -static QByteArray extractEisaId(drmModePropertyBlobPtr edid) -{ - /* - * From EDID standard section 3.4: - * The ID Manufacturer Name field, shown in Table 3.5, contains a 2-byte representation of the monitor's - * manufacturer. This is the same as the EISA ID. It is based on compressed ASCII, “0001=A” ... “11010=Z”. - * - * The table: - * | Byte | Bit | - * | | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | - * ---------------------------------------- - * | 1 | 0)| (4| 3 | 2 | 1 | 0)| (4| 3 | - * | | * | Character 1 | Char 2| - * ---------------------------------------- - * | 2 | 2 | 1 | 0)| (4| 3 | 2 | 1 | 0)| - * | | Character2| Character 3 | - * ---------------------------------------- - **/ - const uint8_t *data = reinterpret_cast(edid->data); - static const uint offset = 0x8; - char id[4]; - if (data[offset] >> 7) { - // bit at position 7 is not a 0 - return QByteArray(); - } - // shift two bits to right, and with 7 right most bits - id[0] = 'A' + ((data[offset] >> 2) & 0x1f) -1; - // for first byte: take last two bits and shift them 3 to left (000xx000) - // for second byte: shift 5 bits to right and take 3 right most bits (00000xxx) - // or both together - id[1] = 'A' + (((data[offset] & 0x3) << 3) | ((data[offset + 1] >> 5) & 0x7)) - 1; - // take five right most bits - id[2] = 'A' + (data[offset + 1] & 0x1f) - 1; - id[3] = '\0'; - return QByteArray(id); -} - -static void extractMonitorDescriptorDescription(drmModePropertyBlobPtr blob, DrmOutput::Edid &edid) -{ - // see section 3.10.3 - const uint8_t *data = reinterpret_cast(blob->data); - static const uint offset = 0x36; - static const uint blockLength = 18; - for (int i = 0; i < 5; ++i) { - const uint co = offset + i * blockLength; - // Flag = 0000h when block used as descriptor - if (data[co] != 0) { - continue; - } - if (data[co + 1] != 0) { - continue; - } - // Reserved = 00h when block used as descriptor - if (data[co + 2] != 0) { - continue; - } - /* - * FFh: Monitor Serial Number - Stored as ASCII, code page # 437, ≤ 13 bytes. - * FEh: ASCII String - Stored as ASCII, code page # 437, ≤ 13 bytes. - * FDh: Monitor range limits, binary coded - * FCh: Monitor name, stored as ASCII, code page # 437 - * FBh: Descriptor contains additional color point data - * FAh: Descriptor contains additional Standard Timing Identifications - * F9h - 11h: Currently undefined - * 10h: Dummy descriptor, used to indicate that the descriptor space is unused - * 0Fh - 00h: Descriptor defined by manufacturer. - */ - if (data[co + 3] == 0xfc && edid.monitorName.isEmpty()) { - edid.monitorName = QByteArray((const char *)(&data[co + 5]), 12).trimmed(); - } - if (data[co + 3] == 0xfe) { - const QByteArray id = QByteArray((const char *)(&data[co + 5]), 12).trimmed(); - if (!id.isEmpty()) { - edid.eisaId = id; - } - } - if (data[co + 3] == 0xff) { - edid.serialNumber = QByteArray((const char *)(&data[co + 5]), 12).trimmed(); - } - } -} - -static QByteArray extractSerialNumber(drmModePropertyBlobPtr edid) -{ - // see section 3.4 - const uint8_t *data = reinterpret_cast(edid->data); - static const uint offset = 0x0C; - /* - * The ID serial number is a 32-bit serial number used to differentiate between individual instances of the same model - * of monitor. Its use is optional. When used, the bit order for this field follows that shown in Table 3.6. The EDID - * structure Version 1 Revision 1 and later offer a way to represent the serial number of the monitor as an ASCII string - * in a separate descriptor block. - */ - uint32_t serialNumber = 0; - serialNumber = (uint32_t) data[offset + 0]; - serialNumber |= (uint32_t) data[offset + 1] << 8; - serialNumber |= (uint32_t) data[offset + 2] << 16; - serialNumber |= (uint32_t) data[offset + 3] << 24; - if (serialNumber == 0) { - return QByteArray(); - } - return QByteArray::number(serialNumber); -} - -static QSize extractPhysicalSize(drmModePropertyBlobPtr edid) -{ - const uint8_t *data = reinterpret_cast(edid->data); - return QSize(data[0x15], data[0x16]) * 10; -} - -void DrmOutput::initEdid(drmModeConnector *connector) -{ - ScopedDrmPointer<_drmModePropertyBlob, &drmModeFreePropertyBlob> edid; - for (int i = 0; i < connector->count_props; ++i) { - ScopedDrmPointer<_drmModeProperty, &drmModeFreeProperty> 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; - } - - // for documentation see: http://read.pudn.com/downloads110/ebook/456020/E-EDID%20Standard.pdf - if (edid->length < 128) { - return; - } - if (!verifyEdidHeader(edid.data())) { - return; - } - m_edid.eisaId = extractEisaId(edid.data()); - m_edid.serialNumber = extractSerialNumber(edid.data()); - - // parse monitor descriptor description - extractMonitorDescriptorDescription(edid.data(), m_edid); - - m_edid.physicalSize = extractPhysicalSize(edid.data()); -} - -void DrmOutput::initDpms(drmModeConnector *connector) -{ - for (int i = 0; i < connector->count_props; ++i) { - ScopedDrmPointer<_drmModeProperty, &drmModeFreeProperty> property(drmModeGetProperty(m_backend->fd(), connector->props[i])); - if (!property) { - continue; - } - if (qstrcmp(property->name, "DPMS") == 0) { - m_dpms.swap(property); - break; - } - } -} - -void DrmOutput::setDpms(DrmOutput::DpmsMode mode) -{ - if (m_dpms.isNull()) { - return; - } - if (mode == m_dpmsMode) { - return; - } - if (drmModeConnectorSetProperty(m_backend->fd(), m_connector, m_dpms->prop_id, uint64_t(mode)) != 0) { - qCWarning(KWIN_DRM) << "Setting DPMS failed"; - return; - } - m_dpmsMode = mode; - if (m_waylandOutput) { - m_waylandOutput->setDpmsMode(toWaylandDpmsMode(m_dpmsMode)); - } - emit dpmsChanged(); - if (m_dpmsMode != DpmsMode::On) { - m_backend->outputWentOff(); - } else { - m_backend->checkOutputsAreOn(); - blank(); - if (Compositor *compositor = Compositor::self()) { - compositor->addRepaintFull(); - } - } -} - -QString DrmOutput::name() const -{ - if (!m_waylandOutput) { - return i18n("unknown"); - } - return QStringLiteral("%1 %2").arg(m_waylandOutput->manufacturer()).arg(m_waylandOutput->model()); -} - -int DrmOutput::currentRefreshRate() const -{ - if (!m_waylandOutput) { - return 60000; - } - return m_waylandOutput->refreshRate(); -} - -void DrmOutput::setGlobalPos(const QPoint &pos) -{ - m_globalPos = pos; - if (m_waylandOutput) { - m_waylandOutput->setGlobalPosition(pos); - } - if (m_waylandOutputDevice) { - m_waylandOutputDevice->setGlobalPosition(pos); - } -} - -void DrmOutput::setChanges(KWayland::Server::OutputChangeSet *changes) -{ - m_changeset = changes; - qCDebug(KWIN_DRM) << "set changes in DrmOutput"; - commitChanges(); -} - -bool DrmOutput::commitChanges() -{ - Q_ASSERT(!m_waylandOutputDevice.isNull()); - Q_ASSERT(!m_waylandOutput.isNull()); - - if (m_changeset.isNull()) { - qCDebug(KWIN_DRM) << "no changes"; - // No changes to an output is an entirely valid thing - return true; - } - - if (m_changeset->enabledChanged()) { - qCDebug(KWIN_DRM) << "Setting enabled:"; - m_waylandOutputDevice->setEnabled(m_changeset->enabled()); - // FIXME: implement - } - if (m_changeset->modeChanged()) { - qCDebug(KWIN_DRM) << "Setting new mode:" << m_changeset->mode(); - m_waylandOutputDevice->setCurrentMode(m_changeset->mode()); - // FIXME: implement for wl_output - } - if (m_changeset->transformChanged()) { - qCDebug(KWIN_DRM) << "Server setting transform: " << (int)(m_changeset->transform()); - m_waylandOutputDevice->setTransform(m_changeset->transform()); - // FIXME: implement for wl_output - } - if (m_changeset->positionChanged()) { - qCDebug(KWIN_DRM) << "Server setting position: " << m_changeset->position(); - m_waylandOutput->setGlobalPosition(m_changeset->position()); - m_waylandOutputDevice->setGlobalPosition(m_changeset->position()); - } - if (m_changeset->scaleChanged()) { - qCDebug(KWIN_DRM) << "Setting scale:" << m_changeset->scale(); - m_waylandOutputDevice->setScale(m_changeset->scale()); - // FIXME: implement for wl_output - } - return true; -} - -DrmBuffer::DrmBuffer(DrmBackend *backend, const QSize &size) - : m_backend(backend) - , m_size(size) -{ - drm_mode_create_dumb createArgs; - memset(&createArgs, 0, sizeof createArgs); - createArgs.bpp = 32; - createArgs.width = size.width(); - createArgs.height = size.height(); - if (drmIoctl(m_backend->fd(), DRM_IOCTL_MODE_CREATE_DUMB, &createArgs) != 0) { - return; - } - m_handle = createArgs.handle; - m_bufferSize = createArgs.size; - m_stride = createArgs.pitch; - drmModeAddFB(m_backend->fd(), size.width(), size.height(), 24, 32, - m_stride, createArgs.handle, &m_bufferId); -} - - -#if HAVE_GBM -static void gbmCallback(gbm_bo *bo, void *data) -{ - DrmBackend *backend = reinterpret_cast(data); - const auto &buffers = backend->buffers(); - for (auto buffer: buffers) { - if (buffer->gbm() == bo) { - delete buffer; - return; - } - } -} -#endif - -DrmBuffer::DrmBuffer(DrmBackend *backend, gbm_surface *surface) - : m_backend(backend) - , m_surface(surface) -{ -#if HAVE_GBM - m_bo = gbm_surface_lock_front_buffer(surface); - if (!m_bo) { - qCWarning(KWIN_DRM) << "Locking front buffer failed"; - return; - } - m_size = QSize(gbm_bo_get_width(m_bo), gbm_bo_get_height(m_bo)); - m_stride = gbm_bo_get_stride(m_bo); - if (drmModeAddFB(m_backend->fd(), m_size.width(), m_size.height(), 24, 32, m_stride, gbm_bo_get_handle(m_bo).u32, &m_bufferId) != 0) { - qCWarning(KWIN_DRM) << "drmModeAddFB failed"; - } - gbm_bo_set_user_data(m_bo, m_backend, gbmCallback); -#endif -} - -DrmBuffer::~DrmBuffer() -{ - m_backend->bufferDestroyed(this); - delete m_image; - if (m_memory) { - munmap(m_memory, m_bufferSize); - } - if (m_bufferId) { - drmModeRmFB(m_backend->fd(), m_bufferId); - } - if (m_handle) { - drm_mode_destroy_dumb destroyArgs; - destroyArgs.handle = m_handle; - drmIoctl(m_backend->fd(), DRM_IOCTL_MODE_DESTROY_DUMB, &destroyArgs); - } - releaseGbm(); -} - -bool DrmBuffer::map(QImage::Format format) -{ - if (!m_handle || !m_bufferId) { - return false; - } - drm_mode_map_dumb mapArgs; - memset(&mapArgs, 0, sizeof mapArgs); - mapArgs.handle = m_handle; - if (drmIoctl(m_backend->fd(), DRM_IOCTL_MODE_MAP_DUMB, &mapArgs) != 0) { - return false; - } - void *address = mmap(nullptr, m_bufferSize, PROT_WRITE, MAP_SHARED, m_backend->fd(), mapArgs.offset); - if (address == MAP_FAILED) { - return false; - } - m_memory = address; - m_image = new QImage((uchar*)m_memory, m_size.width(), m_size.height(), m_stride, format); - return !m_image->isNull(); -} - -void DrmBuffer::releaseGbm() -{ -#if HAVE_GBM - if (m_bo) { - gbm_surface_release_buffer(m_surface, m_bo); - m_bo = nullptr; - } -#endif -} } diff --git a/backends/drm/drm_backend.h b/backends/drm/drm_backend.h index 421356ac0..3aec6137e 100644 --- a/backends/drm/drm_backend.h +++ b/backends/drm/drm_backend.h @@ -1,294 +1,137 @@ /******************************************************************** 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 . *********************************************************************/ #ifndef KWIN_DRM_BACKEND_H #define KWIN_DRM_BACKEND_H #include "abstract_backend.h" #include "input.h" +#include "drm_buffer.h" +#include "drm_inputeventfilter.h" +#include "drm_pointer.h" + #include #include #include #include #include struct gbm_bo; struct gbm_surface; namespace KWayland { namespace Server { class OutputInterface; class OutputDeviceInterface; class OutputChangeSet; class OutputManagementInterface; } } namespace KWin { class Udev; class UdevMonitor; -class DrmBuffer; class DrmOutput; -class DpmsInputEventFilter; -template -struct DrmCleanup -{ - static inline void cleanup(Pointer *ptr) - { - cleanupFunc(ptr); - } -}; -template using ScopedDrmPointer = QScopedPointer>; class KWIN_EXPORT DrmBackend : public AbstractBackend { Q_OBJECT Q_INTERFACES(KWin::AbstractBackend) Q_PLUGIN_METADATA(IID "org.kde.kwin.AbstractBackend" FILE "drm.json") public: explicit DrmBackend(QObject *parent = nullptr); virtual ~DrmBackend(); void configurationChangeRequested(KWayland::Server::OutputConfigurationInterface *config) override; Screens *createScreens(QObject *parent = nullptr) override; QPainterBackend *createQPainterBackend() override; OpenGLBackend* createOpenGLBackend() override; void init() override; DrmBuffer *createBuffer(const QSize &size); DrmBuffer *createBuffer(gbm_surface *surface); void present(DrmBuffer *buffer, DrmOutput *output); QSize size() const; int fd() const { return m_fd; } QVector outputs() const { return m_outputs; } QVector buffers() const { return m_buffers; } void bufferDestroyed(DrmBuffer *b); void outputWentOff(); void checkOutputsAreOn(); public Q_SLOTS: void turnOutputsOn(); Q_SIGNALS: void outputRemoved(KWin::DrmOutput *output); void outputAdded(KWin::DrmOutput *output); private: static void pageFlipHandler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *data); void openDrm(); void activate(bool active); void reactivate(); void deactivate(); void queryResources(); void setCursor(); void updateCursor(); void hideCursor(); void moveCursor(); void initCursor(); quint32 findCrtc(drmModeRes *res, drmModeConnector *connector, bool *ok = nullptr); bool crtcIsUsed(quint32 crtc); void outputDpmsChanged(); void readOutputsConfiguration(); QByteArray generateOutputConfigurationUuid() const; DrmOutput *findOutput(quint32 connector); DrmOutput *findOutput(const QByteArray &uuid); QScopedPointer m_udev; QScopedPointer m_udevMonitor; int m_fd = -1; int m_drmId = 0; QVector m_outputs; DrmBuffer *m_cursor[2]; int m_cursorIndex = 0; int m_pageFlipsPending = 0; bool m_active = false; QVector m_buffers; QScopedPointer m_dpmsFilter; KWayland::Server::OutputManagementInterface *m_outputManagement = nullptr; }; -class DrmOutput : public QObject -{ - Q_OBJECT -public: - struct Edid { - QByteArray eisaId; - QByteArray monitorName; - QByteArray serialNumber; - QSize physicalSize; - }; - virtual ~DrmOutput(); - void showCursor(DrmBuffer *buffer); - void hideCursor(); - void moveCursor(const QPoint &globalPos); - bool present(DrmBuffer *buffer); - void pageFlipped(); - void init(drmModeConnector *connector); - void restoreSaved(); - void blank(); - - /** - * This sets the changes and tests them against the DRM output - */ - void setChanges(KWayland::Server::OutputChangeSet *changeset); - bool commitChanges(); - - QSize size() const; - QRect geometry() const; - QString name() const; - int currentRefreshRate() const; - enum class DpmsMode { - On = DRM_MODE_DPMS_ON, - Standby = DRM_MODE_DPMS_STANDBY, - Suspend = DRM_MODE_DPMS_SUSPEND, - Off = DRM_MODE_DPMS_OFF - }; - void setDpms(DpmsMode mode); - bool isDpmsEnabled() const { - return m_dpmsMode == DpmsMode::On; - } - - QByteArray uuid() const { - return m_uuid; - } - -Q_SIGNALS: - void dpmsChanged(); - -private: - friend class DrmBackend; - DrmOutput(DrmBackend *backend); - void cleanupBlackBuffer(); - bool setMode(DrmBuffer *buffer); - void initEdid(drmModeConnector *connector); - void initDpms(drmModeConnector *connector); - bool isCurrentMode(const drmModeModeInfo *mode) const; - void initUuid(); - void setGlobalPos(const QPoint &pos); - - DrmBackend *m_backend; - QPoint m_globalPos; - quint32 m_crtcId = 0; - quint32 m_connector = 0; - quint32 m_lastStride = 0; - bool m_lastGbm = false; - drmModeModeInfo m_mode; - DrmBuffer *m_currentBuffer = nullptr; - DrmBuffer *m_blackBuffer = nullptr; - struct CrtcCleanup { - static void inline cleanup(_drmModeCrtc *ptr) { - drmModeFreeCrtc(ptr); - } - }; - Edid m_edid; - QScopedPointer<_drmModeCrtc, CrtcCleanup> m_savedCrtc; - QPointer m_waylandOutput; - QPointer m_waylandOutputDevice; - QPointer m_changeset; - ScopedDrmPointer<_drmModeProperty, &drmModeFreeProperty> m_dpms; - DpmsMode m_dpmsMode = DpmsMode::On; - QByteArray m_uuid; -}; - -class DrmBuffer -{ -public: - ~DrmBuffer(); - - bool map(QImage::Format format = QImage::Format_RGB32); - QImage *image() const { - return m_image; - } - quint32 handle() const { - return m_handle; - } - const QSize &size() const { - return m_size; - } - quint32 bufferId() const { - return m_bufferId; - } - quint32 stride() const { - return m_stride; - } - gbm_bo *gbm() const { - return m_bo; - } - bool isGbm() const { - return m_bo != nullptr; - } - void releaseGbm(); - -private: - friend class DrmBackend; - DrmBuffer(DrmBackend *backend, const QSize &size); - DrmBuffer(DrmBackend *backend, gbm_surface *surface); - DrmBackend *m_backend; - gbm_surface *m_surface = nullptr; - gbm_bo *m_bo = nullptr; - QSize m_size; - quint32 m_handle = 0; - quint32 m_bufferId = 0; - quint32 m_stride = 0; - quint64 m_bufferSize = 0; - void *m_memory = nullptr; - QImage *m_image = nullptr; -}; - -class DpmsInputEventFilter : public InputEventFilter -{ -public: - DpmsInputEventFilter(DrmBackend *backend); - ~DpmsInputEventFilter(); - - bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override; - bool wheelEvent(QWheelEvent *event) override; - bool keyEvent(QKeyEvent *event) override; - bool touchDown(quint32 id, const QPointF &pos, quint32 time) override; - bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override; - bool touchUp(quint32 id, quint32 time) override; - -private: - void notify(); - DrmBackend *m_backend; - QElapsedTimer m_doubleTapTimer; - QVector m_touchPoints; - bool m_secondTap = false; -}; } -Q_DECLARE_METATYPE(KWin::DrmOutput*) - #endif diff --git a/backends/drm/drm_buffer.cpp b/backends/drm/drm_buffer.cpp new file mode 100644 index 000000000..cbcb46ff2 --- /dev/null +++ b/backends/drm/drm_buffer.cpp @@ -0,0 +1,138 @@ +/******************************************************************** + 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_buffer.h" +#include "drm_backend.h" + +#include "logging.h" + +// system +#include +// drm +#include +#if HAVE_GBM +#include +#endif + +namespace KWin +{ + + +DrmBuffer::DrmBuffer(DrmBackend *backend, const QSize &size) + : m_backend(backend) + , m_size(size) +{ + drm_mode_create_dumb createArgs; + memset(&createArgs, 0, sizeof createArgs); + createArgs.bpp = 32; + createArgs.width = size.width(); + createArgs.height = size.height(); + if (drmIoctl(m_backend->fd(), DRM_IOCTL_MODE_CREATE_DUMB, &createArgs) != 0) { + return; + } + m_handle = createArgs.handle; + m_bufferSize = createArgs.size; + m_stride = createArgs.pitch; + drmModeAddFB(m_backend->fd(), size.width(), size.height(), 24, 32, + m_stride, createArgs.handle, &m_bufferId); +} + + +#if HAVE_GBM +static void gbmCallback(gbm_bo *bo, void *data) +{ + DrmBackend *backend = reinterpret_cast(data); + const auto &buffers = backend->buffers(); + for (auto buffer: buffers) { + if (buffer->gbm() == bo) { + delete buffer; + return; + } + } +} +#endif + +DrmBuffer::DrmBuffer(DrmBackend *backend, gbm_surface *surface) + : m_backend(backend) + , m_surface(surface) +{ +#if HAVE_GBM + m_bo = gbm_surface_lock_front_buffer(surface); + if (!m_bo) { + qCWarning(KWIN_DRM) << "Locking front buffer failed"; + return; + } + m_size = QSize(gbm_bo_get_width(m_bo), gbm_bo_get_height(m_bo)); + m_stride = gbm_bo_get_stride(m_bo); + if (drmModeAddFB(m_backend->fd(), m_size.width(), m_size.height(), 24, 32, m_stride, gbm_bo_get_handle(m_bo).u32, &m_bufferId) != 0) { + qCWarning(KWIN_DRM) << "drmModeAddFB failed"; + } + gbm_bo_set_user_data(m_bo, m_backend, gbmCallback); +#endif +} + +DrmBuffer::~DrmBuffer() +{ + m_backend->bufferDestroyed(this); + delete m_image; + if (m_memory) { + munmap(m_memory, m_bufferSize); + } + if (m_bufferId) { + drmModeRmFB(m_backend->fd(), m_bufferId); + } + if (m_handle) { + drm_mode_destroy_dumb destroyArgs; + destroyArgs.handle = m_handle; + drmIoctl(m_backend->fd(), DRM_IOCTL_MODE_DESTROY_DUMB, &destroyArgs); + } + releaseGbm(); +} + +bool DrmBuffer::map(QImage::Format format) +{ + if (!m_handle || !m_bufferId) { + return false; + } + drm_mode_map_dumb mapArgs; + memset(&mapArgs, 0, sizeof mapArgs); + mapArgs.handle = m_handle; + if (drmIoctl(m_backend->fd(), DRM_IOCTL_MODE_MAP_DUMB, &mapArgs) != 0) { + return false; + } + void *address = mmap(nullptr, m_bufferSize, PROT_WRITE, MAP_SHARED, m_backend->fd(), mapArgs.offset); + if (address == MAP_FAILED) { + return false; + } + m_memory = address; + m_image = new QImage((uchar*)m_memory, m_size.width(), m_size.height(), m_stride, format); + return !m_image->isNull(); +} + +void DrmBuffer::releaseGbm() +{ +#if HAVE_GBM + if (m_bo) { + gbm_surface_release_buffer(m_surface, m_bo); + m_bo = nullptr; + } +#endif +} + +} diff --git a/backends/drm/drm_buffer.h b/backends/drm/drm_buffer.h new file mode 100644 index 000000000..47db4f8d6 --- /dev/null +++ b/backends/drm/drm_buffer.h @@ -0,0 +1,82 @@ +/******************************************************************** + 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 . +*********************************************************************/ +#ifndef KWIN_DRM_BUFFER_H +#define KWIN_DRM_BUFFER_H + +#include +#include + +struct gbm_bo; +struct gbm_surface; + +namespace KWin +{ + +class DrmBackend; + +class DrmBuffer +{ +public: + ~DrmBuffer(); + + bool map(QImage::Format format = QImage::Format_RGB32); + QImage *image() const { + return m_image; + } + quint32 handle() const { + return m_handle; + } + const QSize &size() const { + return m_size; + } + quint32 bufferId() const { + return m_bufferId; + } + quint32 stride() const { + return m_stride; + } + gbm_bo *gbm() const { + return m_bo; + } + bool isGbm() const { + return m_bo != nullptr; + } + void releaseGbm(); + +private: + friend class DrmBackend; + DrmBuffer(DrmBackend *backend, const QSize &size); + DrmBuffer(DrmBackend *backend, gbm_surface *surface); + DrmBackend *m_backend; + gbm_surface *m_surface = nullptr; + gbm_bo *m_bo = nullptr; + QSize m_size; + quint32 m_handle = 0; + quint32 m_bufferId = 0; + quint32 m_stride = 0; + quint64 m_bufferSize = 0; + void *m_memory = nullptr; + QImage *m_image = nullptr; +}; + +} + +#endif + diff --git a/backends/drm/drm_inputeventfilter.cpp b/backends/drm/drm_inputeventfilter.cpp new file mode 100644 index 000000000..e4ef49804 --- /dev/null +++ b/backends/drm/drm_inputeventfilter.cpp @@ -0,0 +1,112 @@ +/******************************************************************** + 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_inputeventfilter.h" +#include "drm_backend.h" + +#include + +namespace KWin +{ + +DpmsInputEventFilter::DpmsInputEventFilter(DrmBackend *backend) + : InputEventFilter() + , m_backend(backend) +{ +} + +DpmsInputEventFilter::~DpmsInputEventFilter() = default; + +bool DpmsInputEventFilter::pointerEvent(QMouseEvent *event, quint32 nativeButton) +{ + Q_UNUSED(event) + Q_UNUSED(nativeButton) + notify(); + return true; +} + +bool DpmsInputEventFilter::wheelEvent(QWheelEvent *event) +{ + Q_UNUSED(event) + notify(); + return true; +} + +bool DpmsInputEventFilter::keyEvent(QKeyEvent *event) +{ + Q_UNUSED(event) + notify(); + return true; +} + +bool DpmsInputEventFilter::touchDown(quint32 id, const QPointF &pos, quint32 time) +{ + Q_UNUSED(pos) + Q_UNUSED(time) + 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 DpmsInputEventFilter::touchUp(quint32 id, quint32 time) +{ + Q_UNUSED(time) + m_touchPoints.removeAll(id); + if (m_touchPoints.isEmpty() && m_doubleTapTimer.isValid() && m_secondTap) { + if (m_doubleTapTimer.elapsed() < qApp->doubleClickInterval()) { + notify(); + } + m_doubleTapTimer.invalidate(); + m_secondTap = false; + } + return true; +} + +bool DpmsInputEventFilter::touchMotion(quint32 id, const QPointF &pos, quint32 time) +{ + Q_UNUSED(id) + Q_UNUSED(pos) + Q_UNUSED(time) + // ignore the event + return true; +} + +void DpmsInputEventFilter::notify() +{ + // queued to not modify the list of event filters while filtering + QMetaObject::invokeMethod(m_backend, "turnOutputsOn", Qt::QueuedConnection); +} + +} diff --git a/backends/drm/drm_inputeventfilter.h b/backends/drm/drm_inputeventfilter.h new file mode 100644 index 000000000..056f7792b --- /dev/null +++ b/backends/drm/drm_inputeventfilter.h @@ -0,0 +1,56 @@ +/******************************************************************** + 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 . +*********************************************************************/ +#ifndef KWIN_DRM_INPUTEVENTFILTER_H +#define KWIN_DRM_INPUTEVENTFILTER_H +#include "input.h" + +#include + +namespace KWin +{ + +class DrmBackend; + +class DpmsInputEventFilter : public InputEventFilter +{ +public: + DpmsInputEventFilter(DrmBackend *backend); + ~DpmsInputEventFilter(); + + bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override; + bool wheelEvent(QWheelEvent *event) override; + bool keyEvent(QKeyEvent *event) override; + bool touchDown(quint32 id, const QPointF &pos, quint32 time) override; + bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override; + bool touchUp(quint32 id, quint32 time) override; + +private: + void notify(); + DrmBackend *m_backend; + QElapsedTimer m_doubleTapTimer; + QVector m_touchPoints; + bool m_secondTap = false; +}; + +} + + +#endif + diff --git a/backends/drm/drm_output.cpp b/backends/drm/drm_output.cpp new file mode 100644 index 000000000..57a19b8cf --- /dev/null +++ b/backends/drm/drm_output.cpp @@ -0,0 +1,620 @@ +/******************************************************************** + 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 "composite.h" +#include "logging.h" +#include "screens_drm.h" +#include "virtual_terminal.h" +#include "wayland_server.h" +#if HAVE_GBM +#include "egl_gbm_backend.h" +#endif +// KWayland +#include +#include +#include +#include +#include +#include +// KF5 +#include +#include +#include +// Qt +#include +// drm +#include +#include +#include + + +namespace KWin +{ + +DrmOutput::DrmOutput(DrmBackend *backend) + : QObject() + , m_backend(backend) +{ +} + +DrmOutput::~DrmOutput() +{ + hideCursor(); + cleanupBlackBuffer(); + delete m_waylandOutput.data(); + delete m_waylandOutputDevice.data(); +} + +void DrmOutput::hideCursor() +{ + drmModeSetCursor(m_backend->fd(), m_crtcId, 0, 0, 0); +} + +void DrmOutput::restoreSaved() +{ + if (!m_savedCrtc.isNull()) { + drmModeSetCrtc(m_backend->fd(), m_savedCrtc->crtc_id, m_savedCrtc->buffer_id, + m_savedCrtc->x, m_savedCrtc->y, &m_connector, 1, &m_savedCrtc->mode); + } +} + +void DrmOutput::showCursor(DrmBuffer *c) +{ + const QSize &s = c->size(); + drmModeSetCursor(m_backend->fd(), m_crtcId, c->handle(), s.width(), s.height()); +} + +void DrmOutput::moveCursor(const QPoint &globalPos) +{ + const QPoint p = globalPos - m_globalPos; + drmModeMoveCursor(m_backend->fd(), m_crtcId, p.x(), p.y()); +} + +QSize DrmOutput::size() const +{ + return QSize(m_mode.hdisplay, m_mode.vdisplay); +} + +QRect DrmOutput::geometry() const +{ + return QRect(m_globalPos, size()); +} + +bool DrmOutput::present(DrmBuffer *buffer) +{ + if (!buffer || buffer->bufferId() == 0) { + return false; + } + if (!VirtualTerminal::self()->isActive()) { + m_currentBuffer = buffer; + return false; + } + if (m_dpmsMode != DpmsMode::On) { + return false; + } + if (m_currentBuffer) { + return false; + } + if (m_lastStride != buffer->stride() || m_lastGbm != buffer->isGbm()) { + // need to set a new mode first + if (!setMode(buffer)) { + return false; + } + } + const bool ok = drmModePageFlip(m_backend->fd(), m_crtcId, buffer->bufferId(), DRM_MODE_PAGE_FLIP_EVENT, this) == 0; + if (ok) { + m_currentBuffer = buffer; + } else { + qCWarning(KWIN_DRM) << "Page flip failed"; + buffer->releaseGbm(); + } + return ok; +} + +void DrmOutput::pageFlipped() +{ + if (!m_currentBuffer) { + return; + } + m_currentBuffer->releaseGbm(); + m_currentBuffer = nullptr; + cleanupBlackBuffer(); +} + +void DrmOutput::cleanupBlackBuffer() +{ + if (m_blackBuffer) { + delete m_blackBuffer; + m_blackBuffer = nullptr; + } +} + +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(); + } +} + +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(); + } +} + +void DrmOutput::init(drmModeConnector *connector) +{ + initEdid(connector); + initDpms(connector); + initUuid(); + m_savedCrtc.reset(drmModeGetCrtc(m_backend->fd(), m_crtcId)); + blank(); + setDpms(DpmsMode::On); + if (!m_waylandOutput.isNull()) { + delete m_waylandOutput.data(); + m_waylandOutput.clear(); + } + m_waylandOutput = waylandServer()->display()->createOutput(); + if (!m_waylandOutputDevice.isNull()) { + delete m_waylandOutputDevice.data(); + m_waylandOutputDevice.clear(); + } + m_waylandOutputDevice = waylandServer()->display()->createOutputDevice(); + m_waylandOutputDevice->setUuid(m_uuid); + + if (!m_edid.eisaId.isEmpty()) { + m_waylandOutput->setManufacturer(QString::fromLatin1(m_edid.eisaId)); + } else { + m_waylandOutput->setManufacturer(i18n("unknown")); + } + m_waylandOutputDevice->setManufacturer(m_waylandOutput->manufacturer()); + + if (!m_edid.monitorName.isEmpty()) { + QString model = QString::fromLatin1(m_edid.monitorName); + if (!m_edid.serialNumber.isEmpty()) { + model.append('/'); + model.append(QString::fromLatin1(m_edid.serialNumber)); + } + m_waylandOutput->setModel(model); + } else if (!m_edid.serialNumber.isEmpty()) { + m_waylandOutput->setModel(QString::fromLatin1(m_edid.serialNumber)); + } else { + m_waylandOutput->setModel(i18n("unknown")); + } + m_waylandOutputDevice->setModel(m_waylandOutput->model()); + + 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("unkown"); + 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; + } + m_waylandOutput->setPhysicalSize(physicalSize); + m_waylandOutputDevice->setPhysicalSize(physicalSize); + + // read in mode information + for (int i = 0; i < connector->count_modes; ++i) { + auto *m = &connector->modes[i]; + KWayland::Server::OutputInterface::ModeFlags flags; + KWayland::Server::OutputDeviceInterface::ModeFlags deviceflags; + if (isCurrentMode(m)) { + flags |= KWayland::Server::OutputInterface::ModeFlag::Current; + deviceflags |= KWayland::Server::OutputDeviceInterface::ModeFlag::Current; + } + if (m->type & DRM_MODE_TYPE_PREFERRED) { + flags |= KWayland::Server::OutputInterface::ModeFlag::Preferred; + deviceflags |= KWayland::Server::OutputDeviceInterface::ModeFlag::Preferred; + } + + // 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; + } + m_waylandOutput->addMode(QSize(m->hdisplay, m->vdisplay), flags, refreshRate); + + KWayland::Server::OutputDeviceInterface::Mode mode; + mode.id = i; + mode.size = QSize(m->hdisplay, m->vdisplay); + mode.flags = deviceflags; + mode.refreshRate = refreshRate; + qCDebug(KWIN_DRM) << "Adding mode: " << i << mode.size; + m_waylandOutputDevice->addMode(mode); + } + + // set dpms + if (!m_dpms.isNull()) { + m_waylandOutput->setDpmsSupported(true); + m_waylandOutput->setDpmsMode(toWaylandDpmsMode(m_dpmsMode)); + connect(m_waylandOutput.data(), &KWayland::Server::OutputInterface::dpmsModeRequested, this, + [this] (KWayland::Server::OutputInterface::DpmsMode mode) { + setDpms(fromWaylandDpmsMode(mode)); + }, Qt::QueuedConnection + ); + } + + m_waylandOutput->create(); + qCDebug(KWIN_DRM) << "Created OutputDevice"; + m_waylandOutputDevice->create(); +} + +void DrmOutput::initUuid() +{ + QCryptographicHash hash(QCryptographicHash::Md5); + hash.addData(QByteArray::number(m_connector)); + hash.addData(m_edid.eisaId); + hash.addData(m_edid.monitorName); + hash.addData(m_edid.serialNumber); + m_uuid = hash.result().toHex().left(10); +} + +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::blank() +{ + if (!m_blackBuffer) { + m_blackBuffer = m_backend->createBuffer(size()); + m_blackBuffer->map(); + m_blackBuffer->image()->fill(Qt::black); + } + setMode(m_blackBuffer); +} + +bool DrmOutput::setMode(DrmBuffer *buffer) +{ + if (drmModeSetCrtc(m_backend->fd(), m_crtcId, buffer->bufferId(), 0, 0, &m_connector, 1, &m_mode) == 0) { + m_lastStride = buffer->stride(); + m_lastGbm = buffer->isGbm(); + return true; + } else { + qCWarning(KWIN_DRM) << "Mode setting failed"; + return false; + } +} + +static bool verifyEdidHeader(drmModePropertyBlobPtr edid) +{ + const uint8_t *data = reinterpret_cast(edid->data); + if (data[0] != 0x00) { + return false; + } + for (int i = 1; i < 7; ++i) { + if (data[i] != 0xFF) { + return false; + } + } + if (data[7] != 0x00) { + return false; + } + return true; +} + +static QByteArray extractEisaId(drmModePropertyBlobPtr edid) +{ + /* + * From EDID standard section 3.4: + * The ID Manufacturer Name field, shown in Table 3.5, contains a 2-byte representation of the monitor's + * manufacturer. This is the same as the EISA ID. It is based on compressed ASCII, “0001=A” ... “11010=Z”. + * + * The table: + * | Byte | Bit | + * | | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | + * ---------------------------------------- + * | 1 | 0)| (4| 3 | 2 | 1 | 0)| (4| 3 | + * | | * | Character 1 | Char 2| + * ---------------------------------------- + * | 2 | 2 | 1 | 0)| (4| 3 | 2 | 1 | 0)| + * | | Character2| Character 3 | + * ---------------------------------------- + **/ + const uint8_t *data = reinterpret_cast(edid->data); + static const uint offset = 0x8; + char id[4]; + if (data[offset] >> 7) { + // bit at position 7 is not a 0 + return QByteArray(); + } + // shift two bits to right, and with 7 right most bits + id[0] = 'A' + ((data[offset] >> 2) & 0x1f) -1; + // for first byte: take last two bits and shift them 3 to left (000xx000) + // for second byte: shift 5 bits to right and take 3 right most bits (00000xxx) + // or both together + id[1] = 'A' + (((data[offset] & 0x3) << 3) | ((data[offset + 1] >> 5) & 0x7)) - 1; + // take five right most bits + id[2] = 'A' + (data[offset + 1] & 0x1f) - 1; + id[3] = '\0'; + return QByteArray(id); +} + +static void extractMonitorDescriptorDescription(drmModePropertyBlobPtr blob, DrmOutput::Edid &edid) +{ + // see section 3.10.3 + const uint8_t *data = reinterpret_cast(blob->data); + static const uint offset = 0x36; + static const uint blockLength = 18; + for (int i = 0; i < 5; ++i) { + const uint co = offset + i * blockLength; + // Flag = 0000h when block used as descriptor + if (data[co] != 0) { + continue; + } + if (data[co + 1] != 0) { + continue; + } + // Reserved = 00h when block used as descriptor + if (data[co + 2] != 0) { + continue; + } + /* + * FFh: Monitor Serial Number - Stored as ASCII, code page # 437, ≤ 13 bytes. + * FEh: ASCII String - Stored as ASCII, code page # 437, ≤ 13 bytes. + * FDh: Monitor range limits, binary coded + * FCh: Monitor name, stored as ASCII, code page # 437 + * FBh: Descriptor contains additional color point data + * FAh: Descriptor contains additional Standard Timing Identifications + * F9h - 11h: Currently undefined + * 10h: Dummy descriptor, used to indicate that the descriptor space is unused + * 0Fh - 00h: Descriptor defined by manufacturer. + */ + if (data[co + 3] == 0xfc && edid.monitorName.isEmpty()) { + edid.monitorName = QByteArray((const char *)(&data[co + 5]), 12).trimmed(); + } + if (data[co + 3] == 0xfe) { + const QByteArray id = QByteArray((const char *)(&data[co + 5]), 12).trimmed(); + if (!id.isEmpty()) { + edid.eisaId = id; + } + } + if (data[co + 3] == 0xff) { + edid.serialNumber = QByteArray((const char *)(&data[co + 5]), 12).trimmed(); + } + } +} + +static QByteArray extractSerialNumber(drmModePropertyBlobPtr edid) +{ + // see section 3.4 + const uint8_t *data = reinterpret_cast(edid->data); + static const uint offset = 0x0C; + /* + * The ID serial number is a 32-bit serial number used to differentiate between individual instances of the same model + * of monitor. Its use is optional. When used, the bit order for this field follows that shown in Table 3.6. The EDID + * structure Version 1 Revision 1 and later offer a way to represent the serial number of the monitor as an ASCII string + * in a separate descriptor block. + */ + uint32_t serialNumber = 0; + serialNumber = (uint32_t) data[offset + 0]; + serialNumber |= (uint32_t) data[offset + 1] << 8; + serialNumber |= (uint32_t) data[offset + 2] << 16; + serialNumber |= (uint32_t) data[offset + 3] << 24; + if (serialNumber == 0) { + return QByteArray(); + } + return QByteArray::number(serialNumber); +} + +static QSize extractPhysicalSize(drmModePropertyBlobPtr edid) +{ + const uint8_t *data = reinterpret_cast(edid->data); + return QSize(data[0x15], data[0x16]) * 10; +} + +void DrmOutput::initEdid(drmModeConnector *connector) +{ + ScopedDrmPointer<_drmModePropertyBlob, &drmModeFreePropertyBlob> edid; + for (int i = 0; i < connector->count_props; ++i) { + ScopedDrmPointer<_drmModeProperty, &drmModeFreeProperty> 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; + } + + // for documentation see: http://read.pudn.com/downloads110/ebook/456020/E-EDID%20Standard.pdf + if (edid->length < 128) { + return; + } + if (!verifyEdidHeader(edid.data())) { + return; + } + m_edid.eisaId = extractEisaId(edid.data()); + m_edid.serialNumber = extractSerialNumber(edid.data()); + + // parse monitor descriptor description + extractMonitorDescriptorDescription(edid.data(), m_edid); + + m_edid.physicalSize = extractPhysicalSize(edid.data()); +} + +void DrmOutput::initDpms(drmModeConnector *connector) +{ + for (int i = 0; i < connector->count_props; ++i) { + ScopedDrmPointer<_drmModeProperty, &drmModeFreeProperty> property(drmModeGetProperty(m_backend->fd(), connector->props[i])); + if (!property) { + continue; + } + if (qstrcmp(property->name, "DPMS") == 0) { + m_dpms.swap(property); + break; + } + } +} + +void DrmOutput::setDpms(DrmOutput::DpmsMode mode) +{ + if (m_dpms.isNull()) { + return; + } + if (mode == m_dpmsMode) { + return; + } + if (drmModeConnectorSetProperty(m_backend->fd(), m_connector, m_dpms->prop_id, uint64_t(mode)) != 0) { + qCWarning(KWIN_DRM) << "Setting DPMS failed"; + return; + } + m_dpmsMode = mode; + if (m_waylandOutput) { + m_waylandOutput->setDpmsMode(toWaylandDpmsMode(m_dpmsMode)); + } + emit dpmsChanged(); + if (m_dpmsMode != DpmsMode::On) { + m_backend->outputWentOff(); + } else { + m_backend->checkOutputsAreOn(); + blank(); + if (Compositor *compositor = Compositor::self()) { + compositor->addRepaintFull(); + } + } +} + +QString DrmOutput::name() const +{ + if (!m_waylandOutput) { + return i18n("unknown"); + } + return QStringLiteral("%1 %2").arg(m_waylandOutput->manufacturer()).arg(m_waylandOutput->model()); +} + +int DrmOutput::currentRefreshRate() const +{ + if (!m_waylandOutput) { + return 60000; + } + return m_waylandOutput->refreshRate(); +} + +void DrmOutput::setGlobalPos(const QPoint &pos) +{ + m_globalPos = pos; + if (m_waylandOutput) { + m_waylandOutput->setGlobalPosition(pos); + } + if (m_waylandOutputDevice) { + m_waylandOutputDevice->setGlobalPosition(pos); + } +} + +void DrmOutput::setChanges(KWayland::Server::OutputChangeSet *changes) +{ + m_changeset = changes; + qCDebug(KWIN_DRM) << "set changes in DrmOutput"; + commitChanges(); +} + +bool DrmOutput::commitChanges() +{ + Q_ASSERT(!m_waylandOutputDevice.isNull()); + Q_ASSERT(!m_waylandOutput.isNull()); + + if (m_changeset.isNull()) { + qCDebug(KWIN_DRM) << "no changes"; + // No changes to an output is an entirely valid thing + return true; + } + + if (m_changeset->enabledChanged()) { + qCDebug(KWIN_DRM) << "Setting enabled:"; + m_waylandOutputDevice->setEnabled(m_changeset->enabled()); + } + if (m_changeset->modeChanged()) { + qCDebug(KWIN_DRM) << "Setting new mode:" << m_changeset->mode(); + m_waylandOutputDevice->setCurrentMode(m_changeset->mode()); + // FIXME: implement for wl_output + } + if (m_changeset->transformChanged()) { + qCDebug(KWIN_DRM) << "Server setting transform: " << (int)(m_changeset->transform()); + m_waylandOutputDevice->setTransform(m_changeset->transform()); + // FIXME: implement for wl_output + } + if (m_changeset->positionChanged()) { + qCDebug(KWIN_DRM) << "Server setting position: " << m_changeset->position(); + m_waylandOutput->setGlobalPosition(m_changeset->position()); + m_waylandOutputDevice->setGlobalPosition(m_changeset->position()); + // may just work already! + } + if (m_changeset->scaleChanged()) { + qCDebug(KWIN_DRM) << "Setting scale:" << m_changeset->scale(); + m_waylandOutputDevice->setScale(m_changeset->scale()); + // FIXME: implement for wl_output + } + return true; +} + + +} diff --git a/backends/drm/drm_output.h b/backends/drm/drm_output.h new file mode 100644 index 000000000..e021f6001 --- /dev/null +++ b/backends/drm/drm_output.h @@ -0,0 +1,137 @@ +/******************************************************************** + 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 . +*********************************************************************/ +#ifndef KWIN_DRM_OUTPUT_H +#define KWIN_DRM_OUTPUT_H + +#include "drm_pointer.h" + +#include +#include +#include +#include +#include + +namespace KWayland +{ +namespace Server +{ +class OutputInterface; +class OutputDeviceInterface; +class OutputChangeSet; +class OutputManagementInterface; +} +} + +namespace KWin +{ + +class DrmBackend; +class DrmBuffer; + +class DrmOutput : public QObject +{ + Q_OBJECT +public: + struct Edid { + QByteArray eisaId; + QByteArray monitorName; + QByteArray serialNumber; + QSize physicalSize; + }; + virtual ~DrmOutput(); + void showCursor(DrmBuffer *buffer); + void hideCursor(); + void moveCursor(const QPoint &globalPos); + bool present(DrmBuffer *buffer); + void pageFlipped(); + void init(drmModeConnector *connector); + void restoreSaved(); + void blank(); + + /** + * This sets the changes and tests them against the DRM output + */ + void setChanges(KWayland::Server::OutputChangeSet *changeset); + bool commitChanges(); + + QSize size() const; + QRect geometry() const; + QString name() const; + int currentRefreshRate() const; + enum class DpmsMode { + On = DRM_MODE_DPMS_ON, + Standby = DRM_MODE_DPMS_STANDBY, + Suspend = DRM_MODE_DPMS_SUSPEND, + Off = DRM_MODE_DPMS_OFF + }; + void setDpms(DpmsMode mode); + bool isDpmsEnabled() const { + return m_dpmsMode == DpmsMode::On; + } + + QByteArray uuid() const { + return m_uuid; + } + +Q_SIGNALS: + void dpmsChanged(); + +private: + friend class DrmBackend; + DrmOutput(DrmBackend *backend); + void cleanupBlackBuffer(); + bool setMode(DrmBuffer *buffer); + void initEdid(drmModeConnector *connector); + void initDpms(drmModeConnector *connector); + bool isCurrentMode(const drmModeModeInfo *mode) const; + void initUuid(); + void setGlobalPos(const QPoint &pos); + + DrmBackend *m_backend; + QPoint m_globalPos; + quint32 m_crtcId = 0; + quint32 m_connector = 0; + quint32 m_lastStride = 0; + bool m_lastGbm = false; + drmModeModeInfo m_mode; + DrmBuffer *m_currentBuffer = nullptr; + DrmBuffer *m_blackBuffer = nullptr; + struct CrtcCleanup { + static void inline cleanup(_drmModeCrtc *ptr) { + drmModeFreeCrtc(ptr); + } + }; + Edid m_edid; + QScopedPointer<_drmModeCrtc, CrtcCleanup> m_savedCrtc; + QPointer m_waylandOutput; + QPointer m_waylandOutputDevice; + QPointer m_changeset; + KWin::ScopedDrmPointer<_drmModeProperty, &drmModeFreeProperty> m_dpms; + DpmsMode m_dpmsMode = DpmsMode::On; + QByteArray m_uuid; +}; + + +} + +Q_DECLARE_METATYPE(KWin::DrmOutput*) + +#endif + diff --git a/backends/drm/drm_pointer.h b/backends/drm/drm_pointer.h new file mode 100644 index 000000000..a6ec47751 --- /dev/null +++ b/backends/drm/drm_pointer.h @@ -0,0 +1,41 @@ +/******************************************************************** + 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 . +*********************************************************************/ +#ifndef KWIN_DRM_POINTER_H +#define KWIN_DRM_POINTER_H + +#include + +namespace KWin +{ + +template +struct DrmCleanup +{ + static inline void cleanup(Pointer *ptr) + { + cleanupFunc(ptr); + } +}; +template using ScopedDrmPointer = QScopedPointer>; + +} + +#endif + diff --git a/backends/drm/egl_gbm_backend.cpp b/backends/drm/egl_gbm_backend.cpp index f761aaea1..6203acb13 100644 --- a/backends/drm/egl_gbm_backend.cpp +++ b/backends/drm/egl_gbm_backend.cpp @@ -1,349 +1,350 @@ /******************************************************************** 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 "egl_gbm_backend.h" // kwin #include "composite.h" #include "drm_backend.h" +#include "drm_output.h" #include "logging.h" #include "options.h" #include "screens.h" // kwin libs #include // Qt #include // system #include namespace KWin { EglGbmBackend::EglGbmBackend(DrmBackend *b) : QObject(NULL) , AbstractEglBackend() , m_backend(b) { // Egl is always direct rendering setIsDirectRendering(true); setSyncsToVBlank(true); connect(m_backend, &DrmBackend::outputAdded, this, &EglGbmBackend::createOutput); connect(m_backend, &DrmBackend::outputRemoved, this, [this] (DrmOutput *output) { auto it = std::find_if(m_outputs.begin(), m_outputs.end(), [output] (const Output &o) { return o.output == output; } ); if (it == m_outputs.end()) { return; } cleanupOutput(*it); m_outputs.erase(it); } ); } EglGbmBackend::~EglGbmBackend() { // TODO: cleanup front buffer? cleanup(); if (m_device) { gbm_device_destroy(m_device); } } void EglGbmBackend::cleanupSurfaces() { for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { cleanupOutput(*it); } } void EglGbmBackend::cleanupOutput(const Output &o) { // TODO: cleanup front buffer? if (o.eglSurface != EGL_NO_SURFACE) { eglDestroySurface(eglDisplay(), o.eglSurface); } if (o.gbmSurface) { gbm_surface_destroy(o.gbmSurface); } } bool EglGbmBackend::initializeEgl() { initClientExtensions(); EGLDisplay display = EGL_NO_DISPLAY; // Use eglGetPlatformDisplayEXT() to get the display pointer // if the implementation supports it. if (!hasClientExtension(QByteArrayLiteral("EGL_EXT_platform_base")) || !hasClientExtension(QByteArrayLiteral("EGL_MESA_platform_gbm"))) { setFailed("EGL_EXT_platform_base and/or EGL_MESA_platform_gbm missing"); return false; } m_device = gbm_create_device(m_backend->fd()); if (!m_device) { setFailed("Could not create gbm device"); return false; } display = eglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_MESA, m_device, nullptr); if (display == EGL_NO_DISPLAY) return false; setEglDisplay(display); return initEglAPI(); } void EglGbmBackend::init() { if (!initializeEgl()) { setFailed("Could not initialize egl"); return; } if (!initRenderingContext()) { setFailed("Could not initialize rendering context"); return; } initKWinGL(); initBufferAge(); initWayland(); } bool EglGbmBackend::initRenderingContext() { initBufferConfigs(); if (!createContext()) { return false; } const auto outputs = m_backend->outputs(); for (DrmOutput *drmOutput: outputs) { createOutput(drmOutput); } if (m_outputs.isEmpty()) { qCCritical(KWIN_DRM) << "Create Window Surfaces failed"; return false; } // set our first surface as the one for the abstract backend, just to make it happy setSurface(m_outputs.first().eglSurface); return makeContextCurrent(m_outputs.first()); } void EglGbmBackend::createOutput(DrmOutput *drmOutput) { Output o; o.output = drmOutput; o.gbmSurface = gbm_surface_create(m_device, drmOutput->size().width(), drmOutput->size().height(), GBM_FORMAT_XRGB8888, GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING); if (!o.gbmSurface) { qCCritical(KWIN_DRM) << "Create gbm surface failed"; return; } o.eglSurface = eglCreatePlatformWindowSurfaceEXT(eglDisplay(), config(), (void *)o.gbmSurface, nullptr); if (o.eglSurface == EGL_NO_SURFACE) { qCCritical(KWIN_DRM) << "Create Window Surface failed"; gbm_surface_destroy(o.gbmSurface); return; } m_outputs << o; } bool EglGbmBackend::makeContextCurrent(const Output &output) { const EGLSurface surface = output.eglSurface; if (surface == EGL_NO_SURFACE) { return false; } if (eglMakeCurrent(eglDisplay(), surface, surface, context()) == EGL_FALSE) { qCCritical(KWIN_DRM) << "Make Context Current failed"; return false; } EGLint error = eglGetError(); if (error != EGL_SUCCESS) { qCWarning(KWIN_DRM) << "Error occurred while creating context " << error; return false; } // TODO: ensure the viewport is set correctly each time const QSize &overall = screens()->size(); const QRect &v = output.output->geometry(); // TODO: are the values correct? glViewport(-v.x(), v.height() - overall.height() - v.y(), overall.width(), overall.height()); return true; } bool EglGbmBackend::initBufferConfigs() { const EGLint config_attribs[] = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RED_SIZE, 1, EGL_GREEN_SIZE, 1, EGL_BLUE_SIZE, 1, EGL_ALPHA_SIZE, 0, EGL_RENDERABLE_TYPE, isOpenGLES() ? EGL_OPENGL_ES2_BIT : EGL_OPENGL_BIT, EGL_CONFIG_CAVEAT, EGL_NONE, EGL_NONE, }; EGLint count; EGLConfig configs[1024]; if (eglChooseConfig(eglDisplay(), config_attribs, configs, 1, &count) == EGL_FALSE) { qCCritical(KWIN_DRM) << "choose config failed"; return false; } if (count != 1) { qCCritical(KWIN_DRM) << "choose config did not return a config" << count; return false; } setConfig(configs[0]); return true; } void EglGbmBackend::present() { for (auto &o: m_outputs) { makeContextCurrent(o); presentOnOutput(o); } } void EglGbmBackend::presentOnOutput(EglGbmBackend::Output &o) { eglSwapBuffers(eglDisplay(), o.eglSurface); auto oldBuffer = o.buffer; o.buffer = m_backend->createBuffer(o.gbmSurface); m_backend->present(o.buffer, o.output); delete oldBuffer; if (supportsBufferAge()) { eglQuerySurface(eglDisplay(), o.eglSurface, EGL_BUFFER_AGE_EXT, &o.bufferAge); } } void EglGbmBackend::screenGeometryChanged(const QSize &size) { Q_UNUSED(size) // TODO, create new buffer? } SceneOpenGL::TexturePrivate *EglGbmBackend::createBackendTexture(SceneOpenGL::Texture *texture) { return new EglGbmTexture(texture, this); } QRegion EglGbmBackend::prepareRenderingFrame() { startRenderTimer(); return QRegion(); } QRegion EglGbmBackend::prepareRenderingForScreen(int screenId) { const Output &o = m_outputs.at(screenId); makeContextCurrent(o); if (supportsBufferAge()) { QRegion region; // Note: An age of zero means the buffer contents are undefined if (o.bufferAge > 0 && o.bufferAge <= o.damageHistory.count()) { for (int i = 0; i < o.bufferAge - 1; i++) region |= o.damageHistory[i]; } else { region = o.output->geometry(); } return region; } return QRegion(); } void EglGbmBackend::endRenderingFrame(const QRegion &renderedRegion, const QRegion &damagedRegion) { Q_UNUSED(renderedRegion) Q_UNUSED(damagedRegion) } void EglGbmBackend::endRenderingFrameForScreen(int screenId, const QRegion &renderedRegion, const QRegion &damagedRegion) { Output &o = m_outputs[screenId]; if (damagedRegion.intersected(o.output->geometry()).isEmpty() && screenId == 0) { // If the damaged region of a window is fully occluded, the only // rendering done, if any, will have been to repair a reused back // buffer, making it identical to the front buffer. // // In this case we won't post the back buffer. Instead we'll just // set the buffer age to 1, so the repaired regions won't be // rendered again in the next frame. if (!renderedRegion.intersected(o.output->geometry()).isEmpty()) glFlush(); for (auto &o: m_outputs) { o.bufferAge = 1; } return; } presentOnOutput(o); // Save the damaged region to history // Note: damage history is only collected for the first screen. For any other screen full repaints // are triggered. This is due to a limitation in Scene::paintGenericScreen which resets the Toplevel's // repaint. So multiple calls to Scene::paintScreen as it's done in multi-output rendering only // have correct damage information for the first screen. If we try to track damage nevertheless, // it creates artifacts. So for the time being we work around the problem by only supporting buffer // age on the first output. To properly support buffer age on all outputs the rendering needs to // be refactored in general. if (supportsBufferAge() && screenId == 0) { if (o.damageHistory.count() > 10) { o.damageHistory.removeLast(); } o.damageHistory.prepend(damagedRegion.intersected(o.output->geometry())); } } bool EglGbmBackend::usesOverlayWindow() const { return false; } bool EglGbmBackend::perScreenRendering() const { return true; } /************************************************ * EglTexture ************************************************/ EglGbmTexture::EglGbmTexture(KWin::SceneOpenGL::Texture *texture, EglGbmBackend *backend) : AbstractEglTexture(texture, backend) { } EglGbmTexture::~EglGbmTexture() = default; } // namespace diff --git a/backends/drm/scene_qpainter_drm_backend.cpp b/backends/drm/scene_qpainter_drm_backend.cpp index bb2a38999..40cf92630 100644 --- a/backends/drm/scene_qpainter_drm_backend.cpp +++ b/backends/drm/scene_qpainter_drm_backend.cpp @@ -1,122 +1,123 @@ /******************************************************************** 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 "scene_qpainter_drm_backend.h" #include "drm_backend.h" +#include "drm_output.h" #include "virtual_terminal.h" namespace KWin { DrmQPainterBackend::DrmQPainterBackend(DrmBackend *backend) : QObject() , QPainterBackend() , m_backend(backend) { const auto outputs = m_backend->outputs(); for (auto output: outputs) { initOutput(output); } connect(m_backend, &DrmBackend::outputAdded, this, &DrmQPainterBackend::initOutput); connect(m_backend, &DrmBackend::outputRemoved, this, [this] (DrmOutput *o) { auto it = std::find_if(m_outputs.begin(), m_outputs.end(), [o] (const Output &output) { return output.output == o; } ); if (it == m_outputs.end()) { return; } delete (*it).buffer[0]; delete (*it).buffer[1]; m_outputs.erase(it); } ); } DrmQPainterBackend::~DrmQPainterBackend() { for (auto it = m_outputs.begin(); it != m_outputs.end(); ++it) { delete (*it).buffer[0]; delete (*it).buffer[1]; } } void DrmQPainterBackend::initOutput(DrmOutput *output) { Output o; auto initBuffer = [&o, output, this] (int index) { o.buffer[index] = m_backend->createBuffer(output->size()); o.buffer[index]->map(); o.buffer[index]->image()->fill(Qt::black); }; initBuffer(0); initBuffer(1); o.output = output; m_outputs << o; } QImage *DrmQPainterBackend::buffer() { return bufferForScreen(0); } QImage *DrmQPainterBackend::bufferForScreen(int screenId) { const Output &o = m_outputs.at(screenId); return o.buffer[o.index]->image(); } bool DrmQPainterBackend::needsFullRepaint() const { return true; } void DrmQPainterBackend::prepareRenderingFrame() { for (auto it = m_outputs.begin(); it != m_outputs.end(); ++it) { (*it).index = ((*it).index + 1) % 2; } } void DrmQPainterBackend::present(int mask, const QRegion &damage) { Q_UNUSED(mask) Q_UNUSED(damage) if (!VirtualTerminal::self()->isActive()) { return; } for (auto it = m_outputs.begin(); it != m_outputs.end(); ++it) { const Output &o = *it; m_backend->present(o.buffer[o.index], o.output); } } bool DrmQPainterBackend::usesOverlayWindow() const { return false; } bool DrmQPainterBackend::perScreenRendering() const { return true; } -} \ No newline at end of file +} diff --git a/backends/drm/screens_drm.cpp b/backends/drm/screens_drm.cpp index dc70a40f0..5ab27cdb4 100644 --- a/backends/drm/screens_drm.cpp +++ b/backends/drm/screens_drm.cpp @@ -1,106 +1,107 @@ /******************************************************************** 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 "screens_drm.h" #include "drm_backend.h" +#include "drm_output.h" namespace KWin { DrmScreens::DrmScreens(DrmBackend *backend, QObject *parent) : Screens(parent) , m_backend(backend) { connect(backend, &DrmBackend::screensQueried, this, &DrmScreens::updateCount); connect(backend, &DrmBackend::screensQueried, this, &DrmScreens::changed); } DrmScreens::~DrmScreens() = default; void DrmScreens::init() { KWin::Screens::init(); updateCount(); emit changed(); } QRect DrmScreens::geometry(int screen) const { const auto outputs = m_backend->outputs(); if (screen >= outputs.size()) { return QRect(); } return outputs.at(screen)->geometry(); } QSize DrmScreens::size(int screen) const { const auto outputs = m_backend->outputs(); if (screen >= outputs.size()) { return QSize(); } return outputs.at(screen)->size(); } void DrmScreens::updateCount() { setCount(m_backend->outputs().size()); } int DrmScreens::number(const QPoint &pos) const { int bestScreen = 0; int minDistance = INT_MAX; const auto outputs = m_backend->outputs(); for (int i = 0; i < outputs.size(); ++i) { const QRect &geo = outputs.at(i)->geometry(); if (geo.contains(pos)) { return i; } int distance = QPoint(geo.topLeft() - pos).manhattanLength(); distance = qMin(distance, QPoint(geo.topRight() - pos).manhattanLength()); distance = qMin(distance, QPoint(geo.bottomRight() - pos).manhattanLength()); distance = qMin(distance, QPoint(geo.bottomLeft() - pos).manhattanLength()); if (distance < minDistance) { minDistance = distance; bestScreen = i; } } return bestScreen; } QString DrmScreens::name(int screen) const { const auto outputs = m_backend->outputs(); if (screen >= outputs.size()) { return Screens::name(screen); } return outputs.at(screen)->name(); } float DrmScreens::refreshRate(int screen) const { const auto outputs = m_backend->outputs(); if (screen >= outputs.size()) { return Screens::refreshRate(screen); } return outputs.at(screen)->currentRefreshRate() / 1000.0f; } }