diff --git a/abstract_output.h b/abstract_output.h index 16aaa90e9..bf2741504 100644 --- a/abstract_output.h +++ b/abstract_output.h @@ -1,184 +1,184 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright 2019 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_ABSTRACT_OUTPUT_H #define KWIN_ABSTRACT_OUTPUT_H #include #include #include #include #include namespace KWayland { namespace Server { class OutputChangeSet; } } namespace KWin { class KWIN_EXPORT GammaRamp { public: GammaRamp(uint32_t size); /** * Returns the size of the gamma ramp. */ uint32_t size() const; /** * Returns pointer to the first red component in the gamma ramp. * * The returned pointer can be used for altering the red component * in the gamma ramp. */ uint16_t *red(); /** * Returns pointer to the first red component in the gamma ramp. */ const uint16_t *red() const; /** * Returns pointer to the first green component in the gamma ramp. * * The returned pointer can be used for altering the green component * in the gamma ramp. */ uint16_t *green(); /** * Returns pointer to the first green component in the gamma ramp. */ const uint16_t *green() const; /** * Returns pointer to the first blue component in the gamma ramp. * * The returned pointer can be used for altering the blue component * in the gamma ramp. */ uint16_t *blue(); /** * Returns pointer to the first blue component in the gamma ramp. */ const uint16_t *blue() const; private: QVector m_table; uint32_t m_size; }; /** * Generic output representation. */ class KWIN_EXPORT AbstractOutput : public QObject { Q_OBJECT public: explicit AbstractOutput(QObject *parent = nullptr); ~AbstractOutput() override; /** - * Returns the human readable name of this output. + * Returns a short identifiable name of this output. */ virtual QString name() const = 0; /** * Returns the identifying uuid of this output. * * Default implementation returns an empty byte array. */ virtual QByteArray uuid() const; /** * Enable or disable the output. * * Default implementation does nothing */ virtual void setEnabled(bool enable); /** * This sets the changes and tests them against the specific output. * * Default implementation does nothing */ virtual void applyChanges(const KWayland::Server::OutputChangeSet *changeSet); /** * Returns geometry of this output in device independent pixels. */ virtual QRect geometry() const = 0; /** * Returns the approximate vertical refresh rate of this output, in mHz. */ virtual int refreshRate() const = 0; /** * Returns whether this output is connected through an internal connector, * e.g. LVDS, or eDP. * * Default implementation returns @c false. */ virtual bool isInternal() const; /** * Returns the ratio between physical pixels and logical pixels. * * Default implementation returns 1. */ virtual qreal scale() const; /** * Returns the physical size of this output, in millimeters. * * Default implementation returns an invalid QSize. */ virtual QSize physicalSize() const; /** * Returns the size of the gamma lookup table. * * Default implementation returns 0. */ virtual int gammaRampSize() const; /** * Sets the gamma ramp of this output. * * Returns @c true if the gamma ramp was successfully set. */ virtual bool setGammaRamp(const GammaRamp &gamma); private: Q_DISABLE_COPY(AbstractOutput) }; } // namespace KWin #endif diff --git a/abstract_wayland_output.cpp b/abstract_wayland_output.cpp index 060cb8bd4..4426b6392 100644 --- a/abstract_wayland_output.cpp +++ b/abstract_wayland_output.cpp @@ -1,311 +1,320 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright 2019 Roman Gilg Copyright 2020 David Edmundson This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "abstract_wayland_output.h" #include "screens.h" #include "wayland_server.h" // KWayland #include #include #include // KF5 #include #include namespace KWin { AbstractWaylandOutput::AbstractWaylandOutput(QObject *parent) : AbstractOutput(parent) { m_waylandOutput = waylandServer()->display()->createOutput(this); m_waylandOutputDevice = waylandServer()->display()->createOutputDevice(this); m_xdgOutput = waylandServer()->xdgOutputManager()->createXdgOutput(m_waylandOutput, this); connect(m_waylandOutput, &KWayland::Server::OutputInterface::dpmsModeRequested, this, [this] (KWayland::Server::OutputInterface::DpmsMode mode) { updateDpms(mode); }); } AbstractWaylandOutput::~AbstractWaylandOutput() { } QString AbstractWaylandOutput::name() const { - return QStringLiteral("%1 %2").arg(m_waylandOutputDevice->manufacturer()).arg( - m_waylandOutputDevice->model()); + return m_name; } QByteArray AbstractWaylandOutput::uuid() const { return m_waylandOutputDevice->uuid(); } QRect AbstractWaylandOutput::geometry() const { return QRect(globalPos(), pixelSize() / scale()); } QSize AbstractWaylandOutput::physicalSize() const { return orientateSize(m_waylandOutputDevice->physicalSize()); } int AbstractWaylandOutput::refreshRate() const { return m_waylandOutputDevice->refreshRate(); } QPoint AbstractWaylandOutput::globalPos() const { return m_waylandOutputDevice->globalPosition(); } void AbstractWaylandOutput::setGlobalPos(const QPoint &pos) { m_waylandOutputDevice->setGlobalPosition(pos); m_waylandOutput->setGlobalPosition(pos); m_xdgOutput->setLogicalPosition(pos); m_xdgOutput->done(); } QSize AbstractWaylandOutput::modeSize() const { return m_waylandOutputDevice->pixelSize(); } QSize AbstractWaylandOutput::pixelSize() const { return orientateSize(m_waylandOutputDevice->pixelSize()); } qreal AbstractWaylandOutput::scale() const { return m_waylandOutputDevice->scaleF(); } void AbstractWaylandOutput::setScale(qreal scale) { m_waylandOutputDevice->setScaleF(scale); // this is the scale that clients will ideally use for their buffers // this has to be an int which is fine // I don't know whether we want to round or ceil // or maybe even set this to 3 when we're scaling to 1.5 // don't treat this like it's chosen deliberately m_waylandOutput->setScale(std::ceil(scale)); m_xdgOutput->setLogicalSize(pixelSize() / scale); m_xdgOutput->done(); } using DeviceInterface = KWayland::Server::OutputDeviceInterface; KWayland::Server::OutputInterface::Transform toOutputTransform(DeviceInterface::Transform transform) { using Transform = DeviceInterface::Transform; using OutputTransform = KWayland::Server::OutputInterface::Transform; switch (transform) { case Transform::Rotated90: return OutputTransform::Rotated90; case Transform::Rotated180: return OutputTransform::Rotated180; case Transform::Rotated270: return OutputTransform::Rotated270; case Transform::Flipped: return OutputTransform::Flipped; case Transform::Flipped90: return OutputTransform::Flipped90; case Transform::Flipped180: return OutputTransform::Flipped180; case Transform::Flipped270: return OutputTransform::Flipped270; default: return OutputTransform::Normal; } } void AbstractWaylandOutput::setTransform(DeviceInterface::Transform transform) { m_waylandOutputDevice->setTransform(transform); m_waylandOutput->setTransform(toOutputTransform(transform)); m_xdgOutput->setLogicalSize(pixelSize() / scale()); m_xdgOutput->done(); } inline AbstractWaylandOutput::Transform toTransform(DeviceInterface::Transform deviceTransform) { return static_cast(deviceTransform); } inline DeviceInterface::Transform toDeviceTransform(AbstractWaylandOutput::Transform transform) { return static_cast(transform); } void AbstractWaylandOutput::applyChanges(const KWayland::Server::OutputChangeSet *changeSet) { qCDebug(KWIN_CORE) << "Apply changes to the Wayland output."; bool emitModeChanged = false; bool overallSizeCheckNeeded = false; // Enablement changes are handled by platform. if (changeSet->modeChanged()) { qCDebug(KWIN_CORE) << "Setting new mode:" << changeSet->mode(); m_waylandOutputDevice->setCurrentMode(changeSet->mode()); updateMode(changeSet->mode()); emitModeChanged = true; } if (changeSet->transformChanged()) { qCDebug(KWIN_CORE) << "Server setting transform: " << (int)(changeSet->transform()); setTransform(changeSet->transform()); updateTransform(toTransform(changeSet->transform())); emitModeChanged = true; } if (changeSet->positionChanged()) { qCDebug(KWIN_CORE) << "Server setting position: " << changeSet->position(); setGlobalPos(changeSet->position()); // may just work already! overallSizeCheckNeeded = true; } if (changeSet->scaleChanged()) { qCDebug(KWIN_CORE) << "Setting scale:" << changeSet->scaleF(); setScale(changeSet->scaleF()); emitModeChanged = true; } overallSizeCheckNeeded |= emitModeChanged; if (overallSizeCheckNeeded) { emit screens()->changed(); } if (emitModeChanged) { emit modeChanged(); } } bool AbstractWaylandOutput::isEnabled() const { return m_waylandOutputDevice->enabled() == DeviceInterface::Enablement::Enabled; } void AbstractWaylandOutput::setEnabled(bool enable) { if (enable == isEnabled()) { return; } if (enable) { m_waylandOutputDevice->setEnabled(DeviceInterface::Enablement::Enabled); m_waylandOutput->create(); updateEnablement(true); } else { m_waylandOutputDevice->setEnabled(DeviceInterface::Enablement::Disabled); m_waylandOutput->destroy(); // xdg-output is destroyed in KWayland on wl_output going away. updateEnablement(false); } } +QString AbstractWaylandOutput::description() const +{ + return QStringLiteral("%1 %2").arg(m_waylandOutputDevice->manufacturer()).arg( + m_waylandOutputDevice->model()); +} + void AbstractWaylandOutput::setWaylandMode(const QSize &size, int refreshRate) { m_waylandOutput->setCurrentMode(size, refreshRate); m_xdgOutput->setLogicalSize(pixelSize() / scale()); m_xdgOutput->done(); } void AbstractWaylandOutput::initInterfaces(const QString &model, const QString &manufacturer, const QByteArray &uuid, const QSize &physicalSize, const QVector &modes) { m_waylandOutputDevice->setUuid(uuid); if (!manufacturer.isEmpty()) { m_waylandOutputDevice->setManufacturer(manufacturer); } else { m_waylandOutputDevice->setManufacturer(i18n("unknown")); } m_waylandOutputDevice->setModel(model); m_waylandOutputDevice->setPhysicalSize(physicalSize); m_waylandOutput->setManufacturer(m_waylandOutputDevice->manufacturer()); m_waylandOutput->setModel(m_waylandOutputDevice->model()); m_waylandOutput->setPhysicalSize(m_waylandOutputDevice->physicalSize()); int i = 0; for (auto mode : modes) { qCDebug(KWIN_CORE).nospace() << "Adding mode " << ++i << ": " << mode.size << " [" << mode.refreshRate << "]"; m_waylandOutputDevice->addMode(mode); KWayland::Server::OutputInterface::ModeFlags flags; if (mode.flags & DeviceInterface::ModeFlag::Current) { flags |= KWayland::Server::OutputInterface::ModeFlag::Current; } if (mode.flags & DeviceInterface::ModeFlag::Preferred) { flags |= KWayland::Server::OutputInterface::ModeFlag::Preferred; } m_waylandOutput->addMode(mode.size, flags, mode.refreshRate); } + m_waylandOutputDevice->create(); + // start off enabled + m_waylandOutput->create(); - m_waylandOutputDevice->create(); + m_xdgOutput->setName(name()); + m_xdgOutput->setDescription(description()); m_xdgOutput->setLogicalSize(pixelSize() / scale()); m_xdgOutput->done(); } QSize AbstractWaylandOutput::orientateSize(const QSize &size) const { using Transform = DeviceInterface::Transform; const Transform transform = m_waylandOutputDevice->transform(); if (transform == Transform::Rotated90 || transform == Transform::Rotated270 || transform == Transform::Flipped90 || transform == Transform::Flipped270) { return size.transposed(); } return size; } void AbstractWaylandOutput::setTransform(Transform transform) { const auto deviceTransform = toDeviceTransform(transform); if (deviceTransform == m_waylandOutputDevice->transform()) { return; } setTransform(deviceTransform); emit modeChanged(); } AbstractWaylandOutput::Transform AbstractWaylandOutput::transform() const { return static_cast(m_waylandOutputDevice->transform()); } } diff --git a/abstract_wayland_output.h b/abstract_wayland_output.h index f89458c32..73ef7bd71 100644 --- a/abstract_wayland_output.h +++ b/abstract_wayland_output.h @@ -1,178 +1,184 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright 2019 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_ABSTRACT_WAYLAND_OUTPUT_H #define KWIN_ABSTRACT_WAYLAND_OUTPUT_H #include "abstract_output.h" #include #include #include #include #include #include #include #include #include #include namespace KWayland { namespace Server { class OutputInterface; class OutputDeviceInterface; class OutputChangeSet; class OutputManagementInterface; class XdgOutputInterface; } } namespace KWin { /** * Generic output representation in a Wayland session */ class KWIN_EXPORT AbstractWaylandOutput : public AbstractOutput { Q_OBJECT public: enum class Transform { Normal, Rotated90, Rotated180, Rotated270, Flipped, Flipped90, Flipped180, Flipped270 }; explicit AbstractWaylandOutput(QObject *parent = nullptr); ~AbstractWaylandOutput() override; QString name() const override; QByteArray uuid() const override; QSize modeSize() const; // TODO: The name is ambiguous. Rename this function. QSize pixelSize() const; qreal scale() const override; /** * The geometry of this output in global compositor co-ordinates (i.e scaled) */ QRect geometry() const override; QSize physicalSize() const override; /** * Returns the orientation of this output. * * - Flipped along the vertical axis is landscape + inv. portrait. * - Rotated 90° and flipped along the horizontal axis is portrait + inv. landscape * - Rotated 180° and flipped along the vertical axis is inv. landscape + inv. portrait * - Rotated 270° and flipped along the horizontal axis is inv. portrait + inv. landscape + * portrait */ Transform transform() const; /** * Current refresh rate in 1/ms. */ int refreshRate() const override; bool isInternal() const override { return m_internal; } void setGlobalPos(const QPoint &pos); void setScale(qreal scale); void applyChanges(const KWayland::Server::OutputChangeSet *changeSet) override; QPointer waylandOutput() const { return m_waylandOutput; } bool isEnabled() const; /** * Enable or disable the output. * * This differs from updateDpms as it also removes the wl_output. * The default is on. */ void setEnabled(bool enable) override; + QString description() const; + Q_SIGNALS: void modeChanged(); protected: void initInterfaces(const QString &model, const QString &manufacturer, const QByteArray &uuid, const QSize &physicalSize, const QVector &modes); QPoint globalPos() const; bool internal() const { return m_internal; } + void setName(const QString &name) { + m_name = name; + } void setInternal(bool set) { m_internal = set; } void setDpmsSupported(bool set) { m_waylandOutput->setDpmsSupported(set); } virtual void updateEnablement(bool enable) { Q_UNUSED(enable); } virtual void updateDpms(KWayland::Server::OutputInterface::DpmsMode mode) { Q_UNUSED(mode); } virtual void updateMode(int modeIndex) { Q_UNUSED(modeIndex); } virtual void updateTransform(Transform transform) { Q_UNUSED(transform); } void setWaylandMode(const QSize &size, int refreshRate); void setTransform(Transform transform); QSize orientateSize(const QSize &size) const; private: void setTransform(KWayland::Server::OutputDeviceInterface::Transform transform); KWayland::Server::OutputInterface *m_waylandOutput; KWayland::Server::XdgOutputInterface *m_xdgOutput; KWayland::Server::OutputDeviceInterface *m_waylandOutputDevice; KWayland::Server::OutputInterface::DpmsMode m_dpms = KWayland::Server::OutputInterface::DpmsMode::On; + QString m_name; bool m_internal = false; }; } #endif // KWIN_OUTPUT_H diff --git a/autotests/integration/screen_changes_test.cpp b/autotests/integration/screen_changes_test.cpp index e06afa163..1ee2b4153 100644 --- a/autotests/integration/screen_changes_test.cpp +++ b/autotests/integration/screen_changes_test.cpp @@ -1,195 +1,199 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 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 "kwin_wayland_test.h" #include "cursor.h" #include "platform.h" #include "screens.h" #include "wayland_server.h" #include #include #include using namespace KWin; using namespace KWayland::Client; static const QString s_socketName = QStringLiteral("wayland_test_kwin_screen_changes-0"); class ScreenChangesTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testScreenAddRemove(); }; void ScreenChangesTest::initTestCase() { QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); setenv("QT_QPA_PLATFORM", "wayland", true); waylandServer()->initWorkspace(); } void ScreenChangesTest::init() { QVERIFY(Test::setupWaylandConnection()); screens()->setCurrent(0); KWin::Cursors::self()->mouse()->setPos(QPoint(640, 512)); } void ScreenChangesTest::cleanup() { Test::destroyWaylandConnection(); } void ScreenChangesTest::testScreenAddRemove() { // this test verifies that when a new screen is added it gets synced to Wayland // first create a registry to get signals about Outputs announced/removed Registry registry; QSignalSpy allAnnounced(®istry, &Registry::interfacesAnnounced); QVERIFY(allAnnounced.isValid()); QSignalSpy outputAnnouncedSpy(®istry, &Registry::outputAnnounced); QVERIFY(outputAnnouncedSpy.isValid()); QSignalSpy outputRemovedSpy(®istry, &Registry::outputRemoved); QVERIFY(outputRemovedSpy.isValid()); registry.create(Test::waylandConnection()); QVERIFY(registry.isValid()); registry.setup(); QVERIFY(allAnnounced.wait()); const auto xdgOMData = registry.interface(Registry::Interface::XdgOutputUnstableV1); auto xdgOutputManager = registry.createXdgOutputManager(xdgOMData.name, xdgOMData.version); // should be one output QCOMPARE(screens()->count(), 1); QCOMPARE(outputAnnouncedSpy.count(), 1); const quint32 firstOutputId = outputAnnouncedSpy.first().first().value(); QVERIFY(firstOutputId != 0u); outputAnnouncedSpy.clear(); // let's announce a new output QSignalSpy screensChangedSpy(screens(), &Screens::changed); QVERIFY(screensChangedSpy.isValid()); const QVector geometries{QRect(0, 0, 1280, 1024), QRect(1280, 0, 1280, 1024)}; QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2), Q_ARG(QVector, geometries)); QVERIFY(screensChangedSpy.wait()); QCOMPARE(screensChangedSpy.count(), 1); QCOMPARE(screens()->count(), 2); QCOMPARE(screens()->geometry(0), geometries.at(0)); QCOMPARE(screens()->geometry(1), geometries.at(1)); // this should result in it getting announced, two new outputs are added... QVERIFY(outputAnnouncedSpy.wait()); if (outputAnnouncedSpy.count() < 2) { QVERIFY(outputAnnouncedSpy.wait()); } QCOMPARE(outputAnnouncedSpy.count(), 2); // ... and afterward the previous output gets removed if (outputRemovedSpy.isEmpty()) { QVERIFY(outputRemovedSpy.wait()); } QCOMPARE(outputRemovedSpy.count(), 1); QCOMPARE(outputRemovedSpy.first().first().value(), firstOutputId); // let's wait a little bit to ensure we don't get more events QTest::qWait(100); QCOMPARE(outputAnnouncedSpy.count(), 2); QCOMPARE(outputRemovedSpy.count(), 1); // let's create the output objects to ensure they are correct QScopedPointer o1(registry.createOutput(outputAnnouncedSpy.first().first().value(), outputAnnouncedSpy.first().last().value())); QVERIFY(o1->isValid()); QSignalSpy o1ChangedSpy(o1.data(), &Output::changed); QVERIFY(o1ChangedSpy.isValid()); QVERIFY(o1ChangedSpy.wait()); QCOMPARE(o1->geometry(), geometries.at(0)); QScopedPointer o2(registry.createOutput(outputAnnouncedSpy.last().first().value(), outputAnnouncedSpy.last().last().value())); QVERIFY(o2->isValid()); QSignalSpy o2ChangedSpy(o2.data(), &Output::changed); QVERIFY(o2ChangedSpy.isValid()); QVERIFY(o2ChangedSpy.wait()); QCOMPARE(o2->geometry(), geometries.at(1)); //and check XDGOutput is synced QScopedPointer xdgO1(xdgOutputManager->getXdgOutput(o1.data())); QSignalSpy xdgO1ChangedSpy(xdgO1.data(), &XdgOutput::changed); QVERIFY(xdgO1ChangedSpy.isValid()); QVERIFY(xdgO1ChangedSpy.wait()); QCOMPARE(xdgO1->logicalPosition(), geometries.at(0).topLeft()); QCOMPARE(xdgO1->logicalSize(), geometries.at(0).size()); QScopedPointer xdgO2(xdgOutputManager->getXdgOutput(o2.data())); QSignalSpy xdgO2ChangedSpy(xdgO2.data(), &XdgOutput::changed); QVERIFY(xdgO2ChangedSpy.isValid()); QVERIFY(xdgO2ChangedSpy.wait()); QCOMPARE(xdgO2->logicalPosition(), geometries.at(1).topLeft()); QCOMPARE(xdgO2->logicalSize(), geometries.at(1).size()); + QVERIFY(xdgO1->name().startsWith("Virtual-")); + QVERIFY(xdgO1->name() != xdgO2->name()); + QVERIFY(!xdgO1->description().isEmpty()); + // now let's try to remove one output again outputAnnouncedSpy.clear(); outputRemovedSpy.clear(); screensChangedSpy.clear(); QSignalSpy o1RemovedSpy(o1.data(), &Output::removed); QVERIFY(o1RemovedSpy.isValid()); QSignalSpy o2RemovedSpy(o2.data(), &Output::removed); QVERIFY(o2RemovedSpy.isValid()); const QVector geometries2{QRect(0, 0, 1280, 1024)}; QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 1), Q_ARG(QVector, geometries2)); QVERIFY(screensChangedSpy.wait()); QCOMPARE(screensChangedSpy.count(), 1); QCOMPARE(screens()->count(), 1); QCOMPARE(screens()->geometry(0), geometries2.at(0)); QVERIFY(outputAnnouncedSpy.wait()); QCOMPARE(outputAnnouncedSpy.count(), 1); if (o1RemovedSpy.isEmpty()) { QVERIFY(o1RemovedSpy.wait()); } if (o2RemovedSpy.isEmpty()) { QVERIFY(o2RemovedSpy.wait()); } // now wait a bit to ensure we don't get more events QTest::qWait(100); QCOMPARE(outputAnnouncedSpy.count(), 1); QCOMPARE(o1RemovedSpy.count(), 1); QCOMPARE(o2RemovedSpy.count(), 1); QCOMPARE(outputRemovedSpy.count(), 2); } WAYLANDTEST_MAIN(ScreenChangesTest) #include "screen_changes_test.moc" diff --git a/plugins/platforms/drm/drm_output.cpp b/plugins/platforms/drm/drm_output.cpp index 4626bf0ec..ad91d8aa6 100644 --- a/plugins/platforms/drm/drm_output.cpp +++ b/plugins/platforms/drm/drm_output.cpp @@ -1,1091 +1,1091 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "drm_output.h" #include "drm_backend.h" #include "drm_object_plane.h" #include "drm_object_crtc.h" #include "drm_object_connector.h" #include "composite.h" #include "cursor.h" #include "logind.h" #include "logging.h" #include "main.h" #include "screens_drm.h" #include "wayland_server.h" // KWayland #include // KF5 #include #include #include // Qt #include #include #include // c++ #include // drm #include #include #include namespace KWin { DrmOutput::DrmOutput(DrmBackend *backend) : AbstractWaylandOutput(backend) , m_backend(backend) { } DrmOutput::~DrmOutput() { Q_ASSERT(!m_pageFlipPending); teardown(); } void DrmOutput::teardown() { if (m_deleted) { return; } m_deleted = true; hideCursor(); m_crtc->blank(); if (m_primaryPlane) { // TODO: when having multiple planes, also clean up these m_primaryPlane->setOutput(nullptr); if (m_backend->deleteBufferAfterPageFlip()) { delete m_primaryPlane->current(); } m_primaryPlane->setCurrent(nullptr); } m_crtc->setOutput(nullptr); m_conn->setOutput(nullptr); delete m_cursor[0]; delete m_cursor[1]; if (!m_pageFlipPending) { deleteLater(); } //else will be deleted in the page flip handler //this is needed so that the pageflipcallback handle isn't deleted } void DrmOutput::releaseGbm() { if (DrmBuffer *b = m_crtc->current()) { b->releaseGbm(); } if (m_primaryPlane && m_primaryPlane->current()) { m_primaryPlane->current()->releaseGbm(); } } bool DrmOutput::hideCursor() { return drmModeSetCursor(m_backend->fd(), m_crtc->id(), 0, 0, 0) == 0; } bool DrmOutput::showCursor(DrmDumbBuffer *c) { const QSize &s = c->size(); return drmModeSetCursor(m_backend->fd(), m_crtc->id(), c->handle(), s.width(), s.height()) == 0; } bool DrmOutput::showCursor() { const bool ret = showCursor(m_cursor[m_cursorIndex]); if (!ret) { return ret; } if (m_hasNewCursor) { m_cursorIndex = (m_cursorIndex + 1) % 2; m_hasNewCursor = false; } return ret; } // TODO: Do we need to handle the flipped cases differently? int transformToRotation(DrmOutput::Transform transform) { switch (transform) { case DrmOutput::Transform::Normal: case DrmOutput::Transform::Flipped: return 0; case DrmOutput::Transform::Rotated90: case DrmOutput::Transform::Flipped90: return 90; case DrmOutput::Transform::Rotated180: case DrmOutput::Transform::Flipped180: return 180; case DrmOutput::Transform::Rotated270: case DrmOutput::Transform::Flipped270: return 270; } Q_UNREACHABLE(); return 0; } QMatrix4x4 DrmOutput::matrixDisplay(const QSize &s) const { QMatrix4x4 matrix; const int angle = transformToRotation(transform()); if (angle) { const QSize center = s / 2; matrix.translate(center.width(), center.height()); matrix.rotate(-angle, 0, 0, 1); matrix.translate(-center.width(), -center.height()); } matrix.scale(scale()); return matrix; } void DrmOutput::updateCursor() { QImage cursorImage = Cursors::self()->currentCursor()->image(); if (cursorImage.isNull()) { return; } m_hasNewCursor = true; QImage *c = m_cursor[m_cursorIndex]->image(); c->fill(Qt::transparent); QPainter p; p.begin(c); p.setWorldTransform(matrixDisplay(QSize(cursorImage.width(), cursorImage.height())).toTransform()); p.drawImage(QPoint(0, 0), cursorImage); p.end(); } void DrmOutput::moveCursor(Cursor* cursor, const QPoint &globalPos) { const QMatrix4x4 hotspotMatrix = matrixDisplay(cursor->image().size()); const QPoint localPos = globalPos - AbstractWaylandOutput::globalPos(); QPoint pos = localPos; // TODO: Do we need to handle the flipped cases differently? switch (transform()) { case Transform::Normal: case Transform::Flipped: break; case Transform::Rotated90: case Transform::Flipped90: pos = QPoint(localPos.y(), pixelSize().width() / scale() - localPos.x()); break; case Transform::Rotated270: case Transform::Flipped270: pos = QPoint(pixelSize().height() / scale() - localPos.y(), localPos.x()); break; case Transform::Rotated180: case Transform::Flipped180: pos = QPoint(pixelSize().width() / scale() - localPos.x(), pixelSize().height() / scale() - localPos.y()); break; default: Q_UNREACHABLE(); } pos *= scale(); pos -= hotspotMatrix.map(cursor->hotspot()); drmModeMoveCursor(m_backend->fd(), m_crtc->id(), pos.x(), pos.y()); } static QHash s_connectorNames = { {DRM_MODE_CONNECTOR_Unknown, QByteArrayLiteral("Unknown")}, {DRM_MODE_CONNECTOR_VGA, QByteArrayLiteral("VGA")}, {DRM_MODE_CONNECTOR_DVII, QByteArrayLiteral("DVI-I")}, {DRM_MODE_CONNECTOR_DVID, QByteArrayLiteral("DVI-D")}, {DRM_MODE_CONNECTOR_DVIA, QByteArrayLiteral("DVI-A")}, {DRM_MODE_CONNECTOR_Composite, QByteArrayLiteral("Composite")}, {DRM_MODE_CONNECTOR_SVIDEO, QByteArrayLiteral("SVIDEO")}, {DRM_MODE_CONNECTOR_LVDS, QByteArrayLiteral("LVDS")}, {DRM_MODE_CONNECTOR_Component, QByteArrayLiteral("Component")}, {DRM_MODE_CONNECTOR_9PinDIN, QByteArrayLiteral("DIN")}, {DRM_MODE_CONNECTOR_DisplayPort, QByteArrayLiteral("DP")}, {DRM_MODE_CONNECTOR_HDMIA, QByteArrayLiteral("HDMI-A")}, {DRM_MODE_CONNECTOR_HDMIB, QByteArrayLiteral("HDMI-B")}, {DRM_MODE_CONNECTOR_TV, QByteArrayLiteral("TV")}, {DRM_MODE_CONNECTOR_eDP, QByteArrayLiteral("eDP")}, {DRM_MODE_CONNECTOR_VIRTUAL, QByteArrayLiteral("Virtual")}, {DRM_MODE_CONNECTOR_DSI, QByteArrayLiteral("DSI")}, #ifdef DRM_MODE_CONNECTOR_DPI {DRM_MODE_CONNECTOR_DPI, QByteArrayLiteral("DPI")}, #endif }; namespace { quint64 refreshRateForMode(_drmModeModeInfo *m) { // Calculate higher precision (mHz) refresh rate // logic based on Weston, see compositor-drm.c quint64 refreshRate = (m->clock * 1000000LL / m->htotal + m->vtotal / 2) / m->vtotal; if (m->flags & DRM_MODE_FLAG_INTERLACE) { refreshRate *= 2; } if (m->flags & DRM_MODE_FLAG_DBLSCAN) { refreshRate /= 2; } if (m->vscan > 1) { refreshRate /= m->vscan; } return refreshRate; } } bool DrmOutput::init(drmModeConnector *connector) { initEdid(connector); initDpms(connector); initUuid(); if (m_backend->atomicModeSetting()) { if (!initPrimaryPlane()) { return false; } } setInternal(connector->connector_type == DRM_MODE_CONNECTOR_LVDS || connector->connector_type == DRM_MODE_CONNECTOR_eDP || connector->connector_type == DRM_MODE_CONNECTOR_DSI); setDpmsSupported(true); initOutputDevice(connector); if (!m_backend->atomicModeSetting() && !m_crtc->blank()) { // We use legacy mode and the initial output blank failed. return false; } updateDpms(KWayland::Server::OutputInterface::DpmsMode::On); return true; } void DrmOutput::initUuid() { QCryptographicHash hash(QCryptographicHash::Md5); hash.addData(QByteArray::number(m_conn->id())); hash.addData(m_edid.eisaId()); hash.addData(m_edid.monitorName()); hash.addData(m_edid.serialNumber()); m_uuid = hash.result().toHex().left(10); } void DrmOutput::initOutputDevice(drmModeConnector *connector) { QString manufacturer; if (!m_edid.vendor().isEmpty()) { manufacturer = QString::fromLatin1(m_edid.vendor()); } else if (!m_edid.eisaId().isEmpty()) { manufacturer = QString::fromLatin1(m_edid.eisaId()); } - QString connectorName = s_connectorNames.value(connector->connector_type, QByteArrayLiteral("Unknown")); + QString connectorName = s_connectorNames.value(connector->connector_type, QByteArrayLiteral("Unknown")) + QStringLiteral("-") + QString::number(connector->connector_type_id); QString modelName; if (!m_edid.monitorName().isEmpty()) { QString m = QString::fromLatin1(m_edid.monitorName()); if (!m_edid.serialNumber().isEmpty()) { m.append('/'); m.append(QString::fromLatin1(m_edid.serialNumber())); } modelName = m; } else if (!m_edid.serialNumber().isEmpty()) { modelName = QString::fromLatin1(m_edid.serialNumber()); } else { modelName = i18n("unknown"); } - const QString model = connectorName + QStringLiteral("-") + QString::number(connector->connector_type_id) + QStringLiteral("-") + modelName; + const QString model = connectorName + QStringLiteral("-") + modelName; // read in mode information QVector modes; for (int i = 0; i < connector->count_modes; ++i) { // TODO: in AMS here we could read and store for later every mode's blob_id // would simplify isCurrentMode(..) and presentAtomically(..) in case of mode set auto *m = &connector->modes[i]; KWayland::Server::OutputDeviceInterface::ModeFlags deviceflags; if (isCurrentMode(m)) { deviceflags |= KWayland::Server::OutputDeviceInterface::ModeFlag::Current; } if (m->type & DRM_MODE_TYPE_PREFERRED) { deviceflags |= KWayland::Server::OutputDeviceInterface::ModeFlag::Preferred; } KWayland::Server::OutputDeviceInterface::Mode mode; mode.id = i; mode.size = QSize(m->hdisplay, m->vdisplay); mode.flags = deviceflags; mode.refreshRate = refreshRateForMode(m); modes << mode; } QSize physicalSize = !m_edid.physicalSize().isEmpty() ? m_edid.physicalSize() : QSize(connector->mmWidth, connector->mmHeight); // the size might be completely borked. E.g. Samsung SyncMaster 2494HS reports 160x90 while in truth it's 520x292 // as this information is used to calculate DPI info, it's going to result in everything being huge const QByteArray unknown = QByteArrayLiteral("unknown"); KConfigGroup group = kwinApp()->config()->group("EdidOverwrite").group(m_edid.eisaId().isEmpty() ? unknown : m_edid.eisaId()) .group(m_edid.monitorName().isEmpty() ? unknown : m_edid.monitorName()) .group(m_edid.serialNumber().isEmpty() ? unknown : m_edid.serialNumber()); if (group.hasKey("PhysicalSize")) { const QSize overwriteSize = group.readEntry("PhysicalSize", physicalSize); qCWarning(KWIN_DRM) << "Overwriting monitor physical size for" << m_edid.eisaId() << "/" << m_edid.monitorName() << "/" << m_edid.serialNumber() << " from " << physicalSize << "to " << overwriteSize; physicalSize = overwriteSize; } - + setName(connectorName); initInterfaces(model, manufacturer, m_uuid, physicalSize, modes); } bool DrmOutput::isCurrentMode(const drmModeModeInfo *mode) const { return mode->clock == m_mode.clock && mode->hdisplay == m_mode.hdisplay && mode->hsync_start == m_mode.hsync_start && mode->hsync_end == m_mode.hsync_end && mode->htotal == m_mode.htotal && mode->hskew == m_mode.hskew && mode->vdisplay == m_mode.vdisplay && mode->vsync_start == m_mode.vsync_start && mode->vsync_end == m_mode.vsync_end && mode->vtotal == m_mode.vtotal && mode->vscan == m_mode.vscan && mode->vrefresh == m_mode.vrefresh && mode->flags == m_mode.flags && mode->type == m_mode.type && qstrcmp(mode->name, m_mode.name) == 0; } void DrmOutput::initEdid(drmModeConnector *connector) { DrmScopedPointer edid; for (int i = 0; i < connector->count_props; ++i) { DrmScopedPointer property(drmModeGetProperty(m_backend->fd(), connector->props[i])); if (!property) { continue; } if ((property->flags & DRM_MODE_PROP_BLOB) && qstrcmp(property->name, "EDID") == 0) { edid.reset(drmModeGetPropertyBlob(m_backend->fd(), connector->prop_values[i])); } } if (!edid) { return; } m_edid = Edid(edid->data, edid->length); if (!m_edid.isValid()) { qCWarning(KWIN_DRM, "Couldn't parse EDID for connector with id %d", m_conn->id()); } } bool DrmOutput::initPrimaryPlane() { for (int i = 0; i < m_backend->planes().size(); ++i) { DrmPlane* p = m_backend->planes()[i]; if (!p) { continue; } if (p->type() != DrmPlane::TypeIndex::Primary) { continue; } if (p->output()) { // Plane already has an output continue; } if (m_primaryPlane) { // Output already has a primary plane continue; } if (!p->isCrtcSupported(m_crtc->resIndex())) { continue; } p->setOutput(this); m_primaryPlane = p; qCDebug(KWIN_DRM) << "Initialized primary plane" << p->id() << "on CRTC" << m_crtc->id(); return true; } qCCritical(KWIN_DRM) << "Failed to initialize primary plane."; return false; } bool DrmOutput::initCursorPlane() // TODO: Add call in init (but needs layer support in general first) { for (int i = 0; i < m_backend->planes().size(); ++i) { DrmPlane* p = m_backend->planes()[i]; if (!p) { continue; } if (p->type() != DrmPlane::TypeIndex::Cursor) { continue; } if (p->output()) { // Plane already has an output continue; } if (m_cursorPlane) { // Output already has a cursor plane continue; } if (!p->isCrtcSupported(m_crtc->resIndex())) { continue; } p->setOutput(this); m_cursorPlane = p; qCDebug(KWIN_DRM) << "Initialized cursor plane" << p->id() << "on CRTC" << m_crtc->id(); return true; } return false; } bool DrmOutput::initCursor(const QSize &cursorSize) { auto createCursor = [this, cursorSize] (int index) { m_cursor[index] = m_backend->createBuffer(cursorSize); if (!m_cursor[index]->map(QImage::Format_ARGB32_Premultiplied)) { return false; } return true; }; if (!createCursor(0) || !createCursor(1)) { return false; } return true; } void DrmOutput::initDpms(drmModeConnector *connector) { for (int i = 0; i < connector->count_props; ++i) { DrmScopedPointer property(drmModeGetProperty(m_backend->fd(), connector->props[i])); if (!property) { continue; } if (qstrcmp(property->name, "DPMS") == 0) { m_dpms.swap(property); break; } } } void DrmOutput::updateEnablement(bool enable) { if (enable) { m_dpmsModePending = DpmsMode::On; if (m_backend->atomicModeSetting()) { atomicEnable(); } else { if (dpmsLegacyApply()) { m_backend->enableOutput(this, true); } } } else { m_dpmsModePending = DpmsMode::Off; if (m_backend->atomicModeSetting()) { atomicDisable(); } else { if (dpmsLegacyApply()) { m_backend->enableOutput(this, false); } } } } void DrmOutput::atomicEnable() { m_modesetRequested = true; if (m_atomicOffPending) { Q_ASSERT(m_pageFlipPending); m_atomicOffPending = false; } m_backend->enableOutput(this, true); if (Compositor *compositor = Compositor::self()) { compositor->addRepaintFull(); } } void DrmOutput::atomicDisable() { m_modesetRequested = true; m_backend->enableOutput(this, false); m_atomicOffPending = true; if (!m_pageFlipPending) { dpmsAtomicOff(); } } static DrmOutput::DpmsMode fromWaylandDpmsMode(KWayland::Server::OutputInterface::DpmsMode wlMode) { using namespace KWayland::Server; switch (wlMode) { case OutputInterface::DpmsMode::On: return DrmOutput::DpmsMode::On; case OutputInterface::DpmsMode::Standby: return DrmOutput::DpmsMode::Standby; case OutputInterface::DpmsMode::Suspend: return DrmOutput::DpmsMode::Suspend; case OutputInterface::DpmsMode::Off: return DrmOutput::DpmsMode::Off; default: Q_UNREACHABLE(); } } static KWayland::Server::OutputInterface::DpmsMode toWaylandDpmsMode(DrmOutput::DpmsMode mode) { using namespace KWayland::Server; switch (mode) { case DrmOutput::DpmsMode::On: return OutputInterface::DpmsMode::On; case DrmOutput::DpmsMode::Standby: return OutputInterface::DpmsMode::Standby; case DrmOutput::DpmsMode::Suspend: return OutputInterface::DpmsMode::Suspend; case DrmOutput::DpmsMode::Off: return OutputInterface::DpmsMode::Off; default: Q_UNREACHABLE(); } } void DrmOutput::updateDpms(KWayland::Server::OutputInterface::DpmsMode mode) { if (m_dpms.isNull() || !isEnabled()) { return; } const auto drmMode = fromWaylandDpmsMode(mode); if (drmMode == m_dpmsModePending) { qCDebug(KWIN_DRM) << "New DPMS mode equals old mode. DPMS unchanged."; return; } m_dpmsModePending = drmMode; if (m_backend->atomicModeSetting()) { m_modesetRequested = true; if (drmMode == DpmsMode::On) { if (m_atomicOffPending) { Q_ASSERT(m_pageFlipPending); m_atomicOffPending = false; } dpmsFinishOn(); } else { m_atomicOffPending = true; if (!m_pageFlipPending) { dpmsAtomicOff(); } } } else { dpmsLegacyApply(); } } void DrmOutput::dpmsFinishOn() { qCDebug(KWIN_DRM) << "DPMS mode set for output" << m_crtc->id() << "to On."; auto wlOutput = waylandOutput(); if (wlOutput) { wlOutput->setDpmsMode(toWaylandDpmsMode(DpmsMode::On)); } m_backend->checkOutputsAreOn(); if (!m_backend->atomicModeSetting()) { m_crtc->blank(); } if (Compositor *compositor = Compositor::self()) { compositor->addRepaintFull(); } } void DrmOutput::dpmsFinishOff() { qCDebug(KWIN_DRM) << "DPMS mode set for output" << m_crtc->id() << "to Off."; if (isEnabled()) { waylandOutput()->setDpmsMode(toWaylandDpmsMode(m_dpmsModePending)); m_backend->createDpmsFilter(); } } bool DrmOutput::dpmsLegacyApply() { if (drmModeConnectorSetProperty(m_backend->fd(), m_conn->id(), m_dpms->prop_id, uint64_t(m_dpmsModePending)) < 0) { m_dpmsModePending = m_dpmsMode; qCWarning(KWIN_DRM) << "Setting DPMS failed"; return false; } if (m_dpmsModePending == DpmsMode::On) { dpmsFinishOn(); } else { dpmsFinishOff(); } m_dpmsMode = m_dpmsModePending; return true; } DrmPlane::Transformations outputToPlaneTransform(DrmOutput::Transform transform) { using OutTrans = DrmOutput::Transform; using PlaneTrans = DrmPlane::Transformation; // TODO: Do we want to support reflections (flips)? switch (transform) { case OutTrans::Normal: case OutTrans::Flipped: return PlaneTrans::Rotate0; case OutTrans::Rotated90: case OutTrans::Flipped90: return PlaneTrans::Rotate90; case OutTrans::Rotated180: case OutTrans::Flipped180: return PlaneTrans::Rotate180; case OutTrans::Rotated270: case OutTrans::Flipped270: return PlaneTrans::Rotate270; default: Q_UNREACHABLE(); } } bool DrmOutput::hardwareTransforms() const { if (!m_primaryPlane) { return false; } return m_primaryPlane->transformation() == outputToPlaneTransform(transform()); } int DrmOutput::rotation() const { return transformToRotation(transform()); } void DrmOutput::updateTransform(Transform transform) { const auto planeTransform = outputToPlaneTransform(transform); if (m_primaryPlane) { // At the moment we have to exclude hardware transforms for vertical buffers. // For that we need to support other buffers and graceful fallback from atomic tests. // Reason is that standard linear buffers are not suitable. const bool isPortrait = transform == Transform::Rotated90 || transform == Transform::Flipped90 || transform == Transform::Rotated270 || transform == Transform::Flipped270; if (!qEnvironmentVariableIsSet("KWIN_DRM_SW_ROTATIONS_ONLY") && (m_primaryPlane->supportedTransformations() & planeTransform) && !isPortrait) { m_primaryPlane->setTransformation(planeTransform); } else { m_primaryPlane->setTransformation(DrmPlane::Transformation::Rotate0); } } m_modesetRequested = true; // the cursor might need to get rotated updateCursor(); showCursor(); } void DrmOutput::updateMode(int modeIndex) { // get all modes on the connector DrmScopedPointer connector(drmModeGetConnector(m_backend->fd(), m_conn->id())); if (connector->count_modes <= modeIndex) { // TODO: error? return; } if (isCurrentMode(&connector->modes[modeIndex])) { // nothing to do return; } m_mode = connector->modes[modeIndex]; m_modesetRequested = true; setWaylandMode(); } void DrmOutput::setWaylandMode() { AbstractWaylandOutput::setWaylandMode(QSize(m_mode.hdisplay, m_mode.vdisplay), refreshRateForMode(&m_mode)); } void DrmOutput::pageFlipped() { // In legacy mode we might get a page flip through a blank. Q_ASSERT(m_pageFlipPending || !m_backend->atomicModeSetting()); m_pageFlipPending = false; if (m_deleted) { deleteLater(); return; } if (!m_crtc) { return; } // Egl based surface buffers get destroyed, QPainter based dumb buffers not // TODO: split up DrmOutput in two for dumb and egl/gbm surface buffer compatible subclasses completely? if (m_backend->deleteBufferAfterPageFlip()) { if (m_backend->atomicModeSetting()) { if (!m_primaryPlane->next()) { // on manual vt switch // TODO: when we later use overlay planes it might happen, that we have a page flip with only // damage on one of these, and therefore the primary plane has no next buffer // -> Then we don't want to return here! if (m_primaryPlane->current()) { m_primaryPlane->current()->releaseGbm(); } return; } for (DrmPlane *p : m_nextPlanesFlipList) { p->flipBufferWithDelete(); } m_nextPlanesFlipList.clear(); } else { if (!m_crtc->next()) { // on manual vt switch if (DrmBuffer *b = m_crtc->current()) { b->releaseGbm(); } } m_crtc->flipBuffer(); } } else { if (m_backend->atomicModeSetting()){ for (DrmPlane *p : m_nextPlanesFlipList) { p->flipBuffer(); } m_nextPlanesFlipList.clear(); } else { m_crtc->flipBuffer(); } m_crtc->flipBuffer(); } if (m_atomicOffPending) { dpmsAtomicOff(); } } bool DrmOutput::present(DrmBuffer *buffer) { if (m_dpmsModePending != DpmsMode::On) { return false; } if (m_backend->atomicModeSetting()) { return presentAtomically(buffer); } else { return presentLegacy(buffer); } } bool DrmOutput::dpmsAtomicOff() { m_atomicOffPending = false; // TODO: With multiple planes: deactivate all of them here delete m_primaryPlane->next(); m_primaryPlane->setNext(nullptr); m_nextPlanesFlipList << m_primaryPlane; if (!doAtomicCommit(AtomicCommitMode::Test)) { qCDebug(KWIN_DRM) << "Atomic test commit to Dpms Off failed. Aborting."; return false; } if (!doAtomicCommit(AtomicCommitMode::Real)) { qCDebug(KWIN_DRM) << "Atomic commit to Dpms Off failed. This should have never happened! Aborting."; return false; } m_nextPlanesFlipList.clear(); dpmsFinishOff(); return true; } bool DrmOutput::presentAtomically(DrmBuffer *buffer) { if (!LogindIntegration::self()->isActiveSession()) { qCWarning(KWIN_DRM) << "Logind session not active."; return false; } if (m_pageFlipPending) { qCWarning(KWIN_DRM) << "Page not yet flipped."; return false; } #if HAVE_EGL_STREAMS if (m_backend->useEglStreams() && !m_modesetRequested) { // EglStreamBackend queues normal page flips through EGL, // modesets are still performed through DRM-KMS m_pageFlipPending = true; return true; } #endif m_primaryPlane->setNext(buffer); m_nextPlanesFlipList << m_primaryPlane; if (!doAtomicCommit(AtomicCommitMode::Test)) { //TODO: When we use planes for layered rendering, fallback to renderer instead. Also for direct scanout? //TODO: Probably should undo setNext and reset the flip list qCDebug(KWIN_DRM) << "Atomic test commit failed. Aborting present."; // go back to previous state if (m_lastWorkingState.valid) { m_mode = m_lastWorkingState.mode; setTransform(m_lastWorkingState.transform); setGlobalPos(m_lastWorkingState.globalPos); if (m_primaryPlane) { m_primaryPlane->setTransformation(m_lastWorkingState.planeTransformations); } m_modesetRequested = true; // the cursor might need to get rotated updateCursor(); showCursor(); // TODO: forward to OutputInterface and OutputDeviceInterface setWaylandMode(); emit screens()->changed(); } return false; } const bool wasModeset = m_modesetRequested; if (!doAtomicCommit(AtomicCommitMode::Real)) { qCDebug(KWIN_DRM) << "Atomic commit failed. This should have never happened! Aborting present."; //TODO: Probably should undo setNext and reset the flip list return false; } if (wasModeset) { // store current mode set as new good state m_lastWorkingState.mode = m_mode; m_lastWorkingState.transform = transform(); m_lastWorkingState.globalPos = globalPos(); if (m_primaryPlane) { m_lastWorkingState.planeTransformations = m_primaryPlane->transformation(); } m_lastWorkingState.valid = true; } m_pageFlipPending = true; return true; } bool DrmOutput::presentLegacy(DrmBuffer *buffer) { if (m_crtc->next()) { return false; } if (!LogindIntegration::self()->isActiveSession()) { m_crtc->setNext(buffer); return false; } // Do we need to set a new mode first? if (!m_crtc->current() || m_crtc->current()->needsModeChange(buffer)) { if (!setModeLegacy(buffer)) { return false; } } const bool ok = drmModePageFlip(m_backend->fd(), m_crtc->id(), buffer->bufferId(), DRM_MODE_PAGE_FLIP_EVENT, this) == 0; if (ok) { m_crtc->setNext(buffer); } else { qCWarning(KWIN_DRM) << "Page flip failed:" << strerror(errno); } return ok; } bool DrmOutput::setModeLegacy(DrmBuffer *buffer) { uint32_t connId = m_conn->id(); if (drmModeSetCrtc(m_backend->fd(), m_crtc->id(), buffer->bufferId(), 0, 0, &connId, 1, &m_mode) == 0) { return true; } else { qCWarning(KWIN_DRM) << "Mode setting failed"; return false; } } bool DrmOutput::doAtomicCommit(AtomicCommitMode mode) { drmModeAtomicReq *req = drmModeAtomicAlloc(); auto errorHandler = [this, mode, req] () { if (mode == AtomicCommitMode::Test) { // TODO: when we later test overlay planes, make sure we change only the right stuff back } if (req) { drmModeAtomicFree(req); } if (m_dpmsMode != m_dpmsModePending) { qCWarning(KWIN_DRM) << "Setting DPMS failed"; m_dpmsModePending = m_dpmsMode; if (m_dpmsMode != DpmsMode::On) { dpmsFinishOff(); } } // TODO: see above, rework later for overlay planes! for (DrmPlane *p : m_nextPlanesFlipList) { p->setNext(nullptr); } m_nextPlanesFlipList.clear(); }; if (!req) { qCWarning(KWIN_DRM) << "DRM: couldn't allocate atomic request"; errorHandler(); return false; } uint32_t flags = 0; // Do we need to set a new mode? if (m_modesetRequested) { if (m_dpmsModePending == DpmsMode::On) { if (drmModeCreatePropertyBlob(m_backend->fd(), &m_mode, sizeof(m_mode), &m_blobId) != 0) { qCWarning(KWIN_DRM) << "Failed to create property blob"; errorHandler(); return false; } } if (!atomicReqModesetPopulate(req, m_dpmsModePending == DpmsMode::On)){ qCWarning(KWIN_DRM) << "Failed to populate Atomic Modeset"; errorHandler(); return false; } flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; } if (mode == AtomicCommitMode::Real) { if (m_dpmsModePending == DpmsMode::On) { if (!(flags & DRM_MODE_ATOMIC_ALLOW_MODESET)) { // TODO: Evaluating this condition should only be necessary, as long as we expect older kernels than 4.10. flags |= DRM_MODE_ATOMIC_NONBLOCK; } #if HAVE_EGL_STREAMS if (!m_backend->useEglStreams()) // EglStreamBackend uses the NV_output_drm_flip_event EGL extension // to register the flip event through eglStreamConsumerAcquireAttribNV #endif flags |= DRM_MODE_PAGE_FLIP_EVENT; } } else { flags |= DRM_MODE_ATOMIC_TEST_ONLY; } bool ret = true; // TODO: Make sure when we use more than one plane at a time, that we go through this list in the right order. for (int i = m_nextPlanesFlipList.size() - 1; 0 <= i; i-- ) { DrmPlane *p = m_nextPlanesFlipList[i]; ret &= p->atomicPopulate(req); } if (!ret) { qCWarning(KWIN_DRM) << "Failed to populate atomic planes. Abort atomic commit!"; errorHandler(); return false; } if (drmModeAtomicCommit(m_backend->fd(), req, flags, this)) { qCWarning(KWIN_DRM) << "Atomic request failed to commit:" << strerror(errno); errorHandler(); return false; } if (mode == AtomicCommitMode::Real && (flags & DRM_MODE_ATOMIC_ALLOW_MODESET)) { qCDebug(KWIN_DRM) << "Atomic Modeset successful."; m_modesetRequested = false; m_dpmsMode = m_dpmsModePending; } drmModeAtomicFree(req); return true; } bool DrmOutput::atomicReqModesetPopulate(drmModeAtomicReq *req, bool enable) { if (enable) { const QSize mSize = modeSize(); const QSize sourceSize = hardwareTransforms() ? pixelSize() : mSize; m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcX), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcY), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcW), sourceSize.width() << 16); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcH), sourceSize.height() << 16); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcW), mSize.width()); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcH), mSize.height()); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcId), m_crtc->id()); } else { if (m_backend->deleteBufferAfterPageFlip()) { delete m_primaryPlane->current(); delete m_primaryPlane->next(); } m_primaryPlane->setCurrent(nullptr); m_primaryPlane->setNext(nullptr); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcX), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcY), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcW), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcH), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcW), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcH), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcId), 0); } m_conn->setValue(int(DrmConnector::PropertyIndex::CrtcId), enable ? m_crtc->id() : 0); m_crtc->setValue(int(DrmCrtc::PropertyIndex::ModeId), enable ? m_blobId : 0); m_crtc->setValue(int(DrmCrtc::PropertyIndex::Active), enable); bool ret = true; ret &= m_conn->atomicPopulate(req); ret &= m_crtc->atomicPopulate(req); return ret; } bool DrmOutput::supportsTransformations() const { if (!m_primaryPlane) { return false; } const auto transformations = m_primaryPlane->supportedTransformations(); return transformations.testFlag(DrmPlane::Transformation::Rotate90) || transformations.testFlag(DrmPlane::Transformation::Rotate180) || transformations.testFlag(DrmPlane::Transformation::Rotate270); } int DrmOutput::gammaRampSize() const { return m_crtc->gammaRampSize(); } bool DrmOutput::setGammaRamp(const GammaRamp &gamma) { return m_crtc->setGammaRamp(gamma); } } diff --git a/plugins/platforms/fbdev/fb_backend.cpp b/plugins/platforms/fbdev/fb_backend.cpp index dc0cfeba1..931d7b799 100644 --- a/plugins/platforms/fbdev/fb_backend.cpp +++ b/plugins/platforms/fbdev/fb_backend.cpp @@ -1,279 +1,285 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "fb_backend.h" #include "composite.h" #include "logging.h" #include "logind.h" #include "scene_qpainter_fb_backend.h" #include "outputscreens.h" #include "virtual_terminal.h" #include "udev.h" // system #include #include #include #include // Linux #include namespace KWin { +FramebufferOutput::FramebufferOutput(QObject *parent): + AbstractWaylandOutput(parent) +{ + setName("FB-0"); +} + void FramebufferOutput::init(const QSize &pixelSize, const QSize &physicalSize) { KWayland::Server::OutputDeviceInterface::Mode mode; mode.id = 0; mode.size = pixelSize; mode.flags = KWayland::Server::OutputDeviceInterface::ModeFlag::Current; mode.refreshRate = 60000; // TODO: get actual refresh rate of fb device? initInterfaces("model_TODO", "manufacturer_TODO", "UUID_TODO", physicalSize, { mode }); } FramebufferBackend::FramebufferBackend(QObject *parent) : Platform(parent) { } FramebufferBackend::~FramebufferBackend() { unmap(); if (m_fd >= 0) { close(m_fd); } } Screens *FramebufferBackend::createScreens(QObject *parent) { return new OutputScreens(this, parent); } QPainterBackend *FramebufferBackend::createQPainterBackend() { return new FramebufferQPainterBackend(this); } void FramebufferBackend::init() { setSoftWareCursor(true); LogindIntegration *logind = LogindIntegration::self(); auto takeControl = [logind, this]() { if (logind->hasSessionControl()) { openFrameBuffer(); } else { logind->takeControl(); connect(logind, &LogindIntegration::hasSessionControlChanged, this, &FramebufferBackend::openFrameBuffer); } }; if (logind->isConnected()) { takeControl(); } else { connect(logind, &LogindIntegration::connectedChanged, this, takeControl); } VirtualTerminal::create(this); } void FramebufferBackend::openFrameBuffer() { VirtualTerminal::self()->init(); QString framebufferDevice = deviceIdentifier().constData(); if (framebufferDevice.isEmpty()) { framebufferDevice = QString(Udev().primaryFramebuffer()->devNode()); } int fd = LogindIntegration::self()->takeDevice(framebufferDevice.toUtf8().constData()); qCDebug(KWIN_FB) << "Using frame buffer device:" << framebufferDevice; if (fd < 0) { qCWarning(KWIN_FB) << "Failed to open frame buffer device:" << framebufferDevice << "through logind, trying without"; } fd = open(framebufferDevice.toUtf8().constData(), O_RDWR | O_CLOEXEC); if (fd < 0) { qCWarning(KWIN_FB) << "failed to open frame buffer device:" << framebufferDevice; emit initFailed(); return; } m_fd = fd; if (!handleScreenInfo()) { qCWarning(KWIN_FB) << "failed to handle framebuffer information"; emit initFailed(); return; } initImageFormat(); if (m_imageFormat == QImage::Format_Invalid) { emit initFailed(); return; } setReady(true); emit screensQueried(); } bool FramebufferBackend::handleScreenInfo() { if (m_fd < 0) { return false; } fb_var_screeninfo varinfo; fb_fix_screeninfo fixinfo; // Probe the device for screen information. if (ioctl(m_fd, FBIOGET_FSCREENINFO, &fixinfo) < 0 || ioctl(m_fd, FBIOGET_VSCREENINFO, &varinfo) < 0) { return false; } // Activate the framebuffer device, assuming this is a non-primary framebuffer device varinfo.activate = FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE; ioctl(m_fd, FBIOPUT_VSCREENINFO, &varinfo); // Probe the device for new screen information. if (ioctl(m_fd, FBIOGET_VSCREENINFO, &varinfo) < 0) { return false; } auto *output = new FramebufferOutput(this); output->init(QSize(varinfo.xres, varinfo.yres), QSize(varinfo.width, varinfo.height)); m_outputs << output; m_id = QByteArray(fixinfo.id); m_red = {varinfo.red.offset, varinfo.red.length}; m_green = {varinfo.green.offset, varinfo.green.length}; m_blue = {varinfo.blue.offset, varinfo.blue.length}; m_alpha = {varinfo.transp.offset, varinfo.transp.length}; m_bitsPerPixel = varinfo.bits_per_pixel; m_bufferLength = fixinfo.smem_len; m_bytesPerLine = fixinfo.line_length; return true; } void FramebufferBackend::map() { if (m_memory) { // already mapped; return; } if (m_fd < 0) { // not valid return; } void *mem = mmap(nullptr, m_bufferLength, PROT_WRITE, MAP_SHARED, m_fd, 0); if (mem == MAP_FAILED) { qCWarning(KWIN_FB) << "Failed to mmap frame buffer"; return; } m_memory = mem; } void FramebufferBackend::unmap() { if (!m_memory) { return; } if (munmap(m_memory, m_bufferLength) < 0) { qCWarning(KWIN_FB) << "Failed to munmap frame buffer"; } m_memory = nullptr; } QSize FramebufferBackend::screenSize() const { if (m_outputs.isEmpty()) { return QSize(); } return m_outputs[0]->pixelSize(); } QImage::Format FramebufferBackend::imageFormat() const { return m_imageFormat; } void FramebufferBackend::initImageFormat() { if (m_fd < 0) { return; } qCDebug(KWIN_FB) << "Bits Per Pixel: " << m_bitsPerPixel; qCDebug(KWIN_FB) << "Buffer Length: " << m_bufferLength; qCDebug(KWIN_FB) << "Bytes Per Line: " << m_bytesPerLine; qCDebug(KWIN_FB) << "Alpha Length: " << m_alpha.length; qCDebug(KWIN_FB) << "Red Length: " << m_red.length; qCDebug(KWIN_FB) << "Green Length: " << m_green.length; qCDebug(KWIN_FB) << "Blue Length: " << m_blue.length; qCDebug(KWIN_FB) << "Blue Offset: " << m_blue.offset; qCDebug(KWIN_FB) << "Green Offset: " << m_green.offset; qCDebug(KWIN_FB) << "Red Offset: " << m_red.offset; qCDebug(KWIN_FB) << "Alpha Offset: " << m_alpha.offset; if (m_bitsPerPixel == 32 && m_red.length == 8 && m_green.length == 8 && m_blue.length == 8 && m_blue.offset == 0 && m_green.offset == 8 && m_red.offset == 16) { qCDebug(KWIN_FB) << "Framebuffer format is RGB32"; m_imageFormat = QImage::Format_RGB32; } else if (m_bitsPerPixel == 32 && m_red.length == 8 && m_green.length == 8 && m_blue.length == 8 && m_alpha.length == 8 && m_red.offset == 0 && m_green.offset == 8 && m_blue.offset == 16 && m_alpha.offset == 24) { qCDebug(KWIN_FB) << "Framebuffer format is RGBA8888"; m_imageFormat = QImage::Format_RGBA8888; } else if (m_bitsPerPixel == 24 && m_red.length == 8 && m_green.length == 8 && m_blue.length == 8 && m_blue.offset == 0 && m_green.offset == 8 && m_red.offset == 16) { qCDebug(KWIN_FB) << "Framebuffer Format is RGB888"; m_bgr = true; m_imageFormat = QImage::Format_RGB888; } else if (m_bitsPerPixel == 16 && m_red.length == 5 && m_green.length == 6 && m_blue.length == 5 && m_blue.offset == 0 && m_green.offset == 5 && m_red.offset == 11) { qCDebug(KWIN_FB) << "Framebuffer Format is RGB16"; m_imageFormat = QImage::Format_RGB16; } else { qCWarning(KWIN_FB) << "Framebuffer format is unknown"; } } Outputs FramebufferBackend::outputs() const { return m_outputs; } Outputs FramebufferBackend::enabledOutputs() const { return m_outputs; } } diff --git a/plugins/platforms/fbdev/fb_backend.h b/plugins/platforms/fbdev/fb_backend.h index c71c37721..7edee4f2c 100644 --- a/plugins/platforms/fbdev/fb_backend.h +++ b/plugins/platforms/fbdev/fb_backend.h @@ -1,119 +1,119 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright 2015 Martin Gräßlin Copyright 2019 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_FB_BACKEND_H #define KWIN_FB_BACKEND_H #include "abstract_wayland_output.h" #include "platform.h" #include #include namespace KWin { class FramebufferOutput : public AbstractWaylandOutput { Q_OBJECT public: - FramebufferOutput(QObject *parent = nullptr) : AbstractWaylandOutput(parent) {} + FramebufferOutput(QObject *parent = nullptr); ~FramebufferOutput() override = default; void init(const QSize &pixelSize, const QSize &physicalSize); }; class KWIN_EXPORT FramebufferBackend : public Platform { Q_OBJECT Q_INTERFACES(KWin::Platform) Q_PLUGIN_METADATA(IID "org.kde.kwin.Platform" FILE "fbdev.json") public: explicit FramebufferBackend(QObject *parent = nullptr); ~FramebufferBackend() override; Screens *createScreens(QObject *parent = nullptr) override; QPainterBackend *createQPainterBackend() override; QSize screenSize() const override; void init() override; bool isValid() const { return m_fd >= 0; } void map(); void unmap(); void *mappedMemory() const { return m_memory; } int bytesPerLine() const { return m_bytesPerLine; } int bufferSize() const { return m_bufferLength; } quint32 bitsPerPixel() const { return m_bitsPerPixel; } QImage::Format imageFormat() const; /** * @returns whether the imageFormat is BGR instead of RGB. */ bool isBGR() const { return m_bgr; } Outputs outputs() const override; Outputs enabledOutputs() const override; QVector supportedCompositors() const override { return QVector{QPainterCompositing}; } private: void openFrameBuffer(); bool handleScreenInfo(); void initImageFormat(); QVector m_outputs; QByteArray m_id; struct Color { quint32 offset; quint32 length; }; Color m_red; Color m_green; Color m_blue; Color m_alpha; quint32 m_bitsPerPixel = 0; int m_fd = -1; quint32 m_bufferLength = 0; int m_bytesPerLine = 0; void *m_memory = nullptr; QImage::Format m_imageFormat = QImage::Format_Invalid; bool m_bgr = false; }; } #endif diff --git a/plugins/platforms/virtual/virtual_output.cpp b/plugins/platforms/virtual/virtual_output.cpp index 6b7f11496..28ad0f15e 100644 --- a/plugins/platforms/virtual/virtual_output.cpp +++ b/plugins/platforms/virtual/virtual_output.cpp @@ -1,52 +1,55 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2018 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "virtual_output.h" namespace KWin { VirtualOutput::VirtualOutput(QObject *parent) : AbstractWaylandOutput() { Q_UNUSED(parent); + static int identifier = -1; + identifier++; + setName("Virtual-" + QString::number(identifier)); } VirtualOutput::~VirtualOutput() { } void VirtualOutput::init(const QPoint &logicalPosition, const QSize &pixelSize) { KWayland::Server::OutputDeviceInterface::Mode mode; mode.id = 0; mode.size = pixelSize; mode.flags = KWayland::Server::OutputDeviceInterface::ModeFlag::Current; mode.refreshRate = 60000; // TODO initInterfaces("model_TODO", "manufacturer_TODO", "UUID_TODO", pixelSize, { mode }); setGeometry(QRect(logicalPosition, pixelSize)); } void VirtualOutput::setGeometry(const QRect &geo) { // TODO: set mode to have updated pixelSize setGlobalPos(geo.topLeft()); } } diff --git a/plugins/platforms/wayland/wayland_output.cpp b/plugins/platforms/wayland/wayland_output.cpp index 54c4ba44c..bd253c96b 100644 --- a/plugins/platforms/wayland/wayland_output.cpp +++ b/plugins/platforms/wayland/wayland_output.cpp @@ -1,179 +1,183 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright 2019 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "wayland_output.h" #include "wayland_backend.h" #include "wayland_server.h" #include #include #include #include namespace KWin { namespace Wayland { using namespace KWayland::Client; WaylandOutput::WaylandOutput(Surface *surface, WaylandBackend *backend) : AbstractWaylandOutput(backend) , m_surface(surface) , m_backend(backend) { + static int identifier = -1; + identifier++; + setName("WL-" + QString::number(identifier)); + connect(surface, &Surface::frameRendered, [this] { m_rendered = true; emit frameRendered(); }); } WaylandOutput::~WaylandOutput() { m_surface->destroy(); delete m_surface; } void WaylandOutput::init(const QPoint &logicalPosition, const QSize &pixelSize) { KWayland::Server::OutputDeviceInterface::Mode mode; mode.id = 0; mode.size = pixelSize; mode.flags = KWayland::Server::OutputDeviceInterface::ModeFlag::Current; mode.refreshRate = 60000; // TODO: can we get refresh rate data from Wayland host? initInterfaces("model_TODO", "manufacturer_TODO", "UUID_TODO", pixelSize, { mode }); setGeometry(logicalPosition, pixelSize); setScale(backend()->initialOutputScale()); } void WaylandOutput::setGeometry(const QPoint &logicalPosition, const QSize &pixelSize) { // TODO: set mode to have updated pixelSize Q_UNUSED(pixelSize) setGlobalPos(logicalPosition); } XdgShellOutput::XdgShellOutput(Surface *surface, XdgShell *xdgShell, WaylandBackend *backend, int number) : WaylandOutput(surface, backend) , m_number(number) { m_xdgShellSurface = xdgShell->createSurface(surface, this); updateWindowTitle(); connect(m_xdgShellSurface, &XdgShellSurface::configureRequested, this, &XdgShellOutput::handleConfigure); connect(m_xdgShellSurface, &XdgShellSurface::closeRequested, qApp, &QCoreApplication::quit); connect(backend, &WaylandBackend::pointerLockSupportedChanged, this, &XdgShellOutput::updateWindowTitle); connect(backend, &WaylandBackend::pointerLockChanged, this, [this](bool locked) { if (locked) { if (!m_hasPointerLock) { // some other output has locked the pointer // this surface can stop trying to lock the pointer lockPointer(nullptr, false); // set it true for the other surface m_hasPointerLock = true; } } else { // just try unlocking lockPointer(nullptr, false); } updateWindowTitle(); }); surface->commit(Surface::CommitFlag::None); } XdgShellOutput::~XdgShellOutput() { m_xdgShellSurface->destroy(); delete m_xdgShellSurface; } void XdgShellOutput::handleConfigure(const QSize &size, XdgShellSurface::States states, quint32 serial) { Q_UNUSED(states); if (size.width() > 0 && size.height() > 0) { setGeometry(geometry().topLeft(), size); emit sizeChanged(size); } m_xdgShellSurface->ackConfigure(serial); } void XdgShellOutput::updateWindowTitle() { QString grab; if (m_hasPointerLock) { grab = i18n("Press right control to ungrab pointer"); } else if (backend()->pointerConstraints()) { grab = i18n("Press right control key to grab pointer"); } const QString title = i18nc("Title of nested KWin Wayland with Wayland socket identifier as argument", "KDE Wayland Compositor #%1 (%2)", m_number, waylandServer()->display()->socketName()); if (grab.isEmpty()) { m_xdgShellSurface->setTitle(title); } else { m_xdgShellSurface->setTitle(title + QStringLiteral(" — ") + grab); } } void XdgShellOutput::lockPointer(Pointer *pointer, bool lock) { if (!lock) { const bool surfaceWasLocked = m_pointerLock && m_hasPointerLock; delete m_pointerLock; m_pointerLock = nullptr; m_hasPointerLock = false; if (surfaceWasLocked) { emit backend()->pointerLockChanged(false); } return; } Q_ASSERT(!m_pointerLock); m_pointerLock = backend()->pointerConstraints()->lockPointer(surface(), pointer, nullptr, PointerConstraints::LifeTime::OneShot, this); if (!m_pointerLock->isValid()) { delete m_pointerLock; m_pointerLock = nullptr; return; } connect(m_pointerLock, &LockedPointer::locked, this, [this] { m_hasPointerLock = true; emit backend()->pointerLockChanged(true); } ); connect(m_pointerLock, &LockedPointer::unlocked, this, [this] { delete m_pointerLock; m_pointerLock = nullptr; m_hasPointerLock = false; emit backend()->pointerLockChanged(false); } ); } } } diff --git a/plugins/platforms/x11/windowed/x11windowed_output.cpp b/plugins/platforms/x11/windowed/x11windowed_output.cpp index 57d032dd9..896faf036 100644 --- a/plugins/platforms/x11/windowed/x11windowed_output.cpp +++ b/plugins/platforms/x11/windowed/x11windowed_output.cpp @@ -1,163 +1,167 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright 2019 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "x11windowed_output.h" #include "x11windowed_backend.h" #include #if HAVE_X11_XINPUT #include #endif #include namespace KWin { X11WindowedOutput::X11WindowedOutput(X11WindowedBackend *backend) : AbstractWaylandOutput(backend) , m_backend(backend) { m_window = xcb_generate_id(m_backend->connection()); + + static int identifier = -1; + identifier++; + setName("X11-" + QString::number(identifier)); } X11WindowedOutput::~X11WindowedOutput() { xcb_unmap_window(m_backend->connection(), m_window); xcb_destroy_window(m_backend->connection(), m_window); delete m_winInfo; xcb_flush(m_backend->connection()); } void X11WindowedOutput::init(const QPoint &logicalPosition, const QSize &pixelSize) { KWayland::Server::OutputDeviceInterface::Mode mode; mode.id = 0; mode.size = pixelSize; mode.flags = KWayland::Server::OutputDeviceInterface::ModeFlag::Current; mode.refreshRate = 60000; // TODO: get refresh rate via randr // Physicial size must be adjusted, such that QPA calculates correct sizes of // internal elements. const QSize physicalSize = pixelSize / 96.0 * 25.4 / m_backend->initialOutputScale(); initInterfaces("model_TODO", "manufacturer_TODO", "UUID_TODO", physicalSize, { mode }); setGeometry(logicalPosition, pixelSize); setScale(m_backend->initialOutputScale()); uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK; const uint32_t values[] = { m_backend->screen()->black_pixel, XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE | XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_EXPOSURE }; xcb_create_window(m_backend->connection(), XCB_COPY_FROM_PARENT, m_window, m_backend->screen()->root, 0, 0, pixelSize.width(), pixelSize.height(), 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, mask, values); // select xinput 2 events initXInputForWindow(); m_winInfo = new NETWinInfo(m_backend->connection(), m_window, m_backend->screen()->root, NET::WMWindowType, NET::Properties2()); m_winInfo->setWindowType(NET::Normal); m_winInfo->setPid(QCoreApplication::applicationPid()); QIcon windowIcon = QIcon::fromTheme(QStringLiteral("kwin")); auto addIcon = [&windowIcon, this] (const QSize &size) { if (windowIcon.actualSize(size) != size) { return; } NETIcon icon; icon.data = windowIcon.pixmap(size).toImage().bits(); icon.size.width = size.width(); icon.size.height = size.height(); m_winInfo->setIcon(icon, false); }; addIcon(QSize(16, 16)); addIcon(QSize(32, 32)); addIcon(QSize(48, 48)); xcb_map_window(m_backend->connection(), m_window); } void X11WindowedOutput::initXInputForWindow() { if (!m_backend->hasXInput()) { return; } #if HAVE_X11_XINPUT XIEventMask evmasks[1]; unsigned char mask1[XIMaskLen(XI_LASTEVENT)]; memset(mask1, 0, sizeof(mask1)); XISetMask(mask1, XI_TouchBegin); XISetMask(mask1, XI_TouchUpdate); XISetMask(mask1, XI_TouchOwnership); XISetMask(mask1, XI_TouchEnd); evmasks[0].deviceid = XIAllMasterDevices; evmasks[0].mask_len = sizeof(mask1); evmasks[0].mask = mask1; XISelectEvents(m_backend->display(), m_window, evmasks, 1); #endif } void X11WindowedOutput::setGeometry(const QPoint &logicalPosition, const QSize &pixelSize) { // TODO: set mode to have updated pixelSize Q_UNUSED(pixelSize); setGlobalPos(logicalPosition); } void X11WindowedOutput::setWindowTitle(const QString &title) { m_winInfo->setName(title.toUtf8().constData()); } QPoint X11WindowedOutput::internalPosition() const { return geometry().topLeft(); } void X11WindowedOutput::setHostPosition(const QPoint &pos) { m_hostPosition = pos; } QPointF X11WindowedOutput::mapFromGlobal(const QPointF &pos) const { return (pos - hostPosition() + internalPosition()) / scale(); } }