diff --git a/abstract_wayland_output.cpp b/abstract_wayland_output.cpp index 90274cf498..cbd3945f02 100644 --- a/abstract_wayland_output.cpp +++ b/abstract_wayland_output.cpp @@ -263,10 +263,10 @@ void AbstractWaylandOutput::initInterfaces(const QString &model, const QString & { m_waylandOutputDevice->setUuid(uuid); - if (!manufacturer.isEmpty()) { - m_waylandOutputDevice->setManufacturer(manufacturer); - } else { + if (manufacturer.isEmpty()) { m_waylandOutputDevice->setManufacturer(i18n("unknown")); + } else { + m_waylandOutputDevice->setManufacturer(manufacturer); } m_waylandOutputDevice->setEdid(edid); diff --git a/autotests/integration/CMakeLists.txt b/autotests/integration/CMakeLists.txt index 53753c692d..6b072970fa 100644 --- a/autotests/integration/CMakeLists.txt +++ b/autotests/integration/CMakeLists.txt @@ -97,6 +97,7 @@ integrationTest(WAYLAND_ONLY NAME testPlacement SRCS placement_test.cpp) integrationTest(WAYLAND_ONLY NAME testActivation SRCS activation_test.cpp) integrationTest(WAYLAND_ONLY NAME testInputMethod SRCS inputmethod_test.cpp) integrationTest(WAYLAND_ONLY NAME testScreens SRCS screens_test.cpp) +integrationTest(WAYLAND_ONLY NAME testOutputManagement SRCS outputmanagement_test.cpp) if (KWIN_BUILD_CMS) integrationTest(WAYLAND_ONLY NAME testNightColor SRCS nightcolor_test.cpp LIBS KWinNightColorPlugin) diff --git a/autotests/integration/kwin_wayland_test.h b/autotests/integration/kwin_wayland_test.h index cd0fc00b67..6a494b7510 100644 --- a/autotests/integration/kwin_wayland_test.h +++ b/autotests/integration/kwin_wayland_test.h @@ -42,6 +42,7 @@ class Surface; class XdgDecorationManager; class OutputManagement; class TextInputManager; +class OutputDevice; } } @@ -236,7 +237,8 @@ enum class AdditionalWaylandInterface { TextInputManagerV2 = 1 << 10, InputMethodV1 = 1 << 11, LayerShellV1 = 1 << 12, - TextInputManagerV3 = 1 << 13 + TextInputManagerV3 = 1 << 13, + OutputDevice = 1 << 14 }; Q_DECLARE_FLAGS(AdditionalWaylandInterfaces, AdditionalWaylandInterface) /** @@ -271,6 +273,7 @@ KWayland::Client::XdgDecorationManager *xdgDecorationManager(); KWayland::Client::OutputManagement *waylandOutputManagement(); KWayland::Client::TextInputManager *waylandTextInputManager(); QVector waylandOutputs(); +QVector waylandOutputDevices(); bool waitForWaylandPointer(); bool waitForWaylandTouch(); diff --git a/autotests/integration/outputmanagement_test.cpp b/autotests/integration/outputmanagement_test.cpp new file mode 100644 index 0000000000..f723f2214e --- /dev/null +++ b/autotests/integration/outputmanagement_test.cpp @@ -0,0 +1,176 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2020 Méven Car + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" +#include "abstract_client.h" +#include "abstract_wayland_output.h" +#include "deleted.h" +#include "platform.h" +#include "screens.h" +#include "wayland_server.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace KWin; +using namespace KWayland::Client; + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_outputmanagement-0"); + +class TestOutputManagement : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testOutputDeviceDisabled(); +}; + +void TestOutputManagement::initTestCase() +{ + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType("AbstractOutput *"); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType("OutputDevice::Enablement"); + qRegisterMetaType(); + + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + QVERIFY(applicationStartedSpy.isValid()); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + QCOMPARE(screens()->count(), 2); + QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); + QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); + waylandServer()->initWorkspace(); +} + +void TestOutputManagement::init() +{ + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::OutputManagement | + Test::AdditionalWaylandInterface::OutputDevice)); + + screens()->setCurrent(0); + //put mouse in the middle of screen one + KWin::Cursors::self()->mouse()->setPos(QPoint(512, 512)); +} + +void TestOutputManagement::cleanup() +{ + Test::destroyWaylandConnection(); +} + +void TestOutputManagement::testOutputDeviceDisabled() +{ + // This tests checks that OutputConfiguration::apply aka Platform::requestOutputsChange works as expected + // when disabling and enabling virtual OutputDevice + + QScopedPointer surface(Test::createSurface()); + QScopedPointer shellSurface(Test::createXdgShellStableSurface(surface.data())); + auto size = QSize(200,200); + + QSignalSpy outputEnteredSpy(surface.data(), &Surface::outputEntered); + QSignalSpy outputLeftSpy(surface.data(), &Surface::outputLeft); + + QSignalSpy outputEnabledSpy(kwinApp()->platform(), &Platform::outputEnabled); + QSignalSpy outputDisabledSpy(kwinApp()->platform(), &Platform::outputDisabled); + + auto c = Test::renderAndWaitForShown(surface.data(), size, Qt::blue); + //move to be in the first screen + c->setFrameGeometry(QRect(QPoint(100,100), size)); + //we don't don't know where the compositor first placed this window, + //this might fire, it might not + outputEnteredSpy.wait(5); + outputEnteredSpy.clear(); + QCOMPARE(waylandServer()->display()->outputs().count(), 2); + + QCOMPARE(surface->outputs().count(), 1); + Output *firstOutput = surface->outputs().first(); + QCOMPARE(firstOutput->globalPosition(), QPoint(0,0)); + QSignalSpy modesChangedSpy(firstOutput, &Output::modeChanged); + + QSignalSpy screenChangedSpy(screens(), &KWin::Screens::changed); + + OutputManagement *outManagement = Test::waylandOutputManagement(); + + auto outputDevices = Test::waylandOutputDevices(); + QCOMPARE(outputDevices.count(), 2); + + OutputDevice *device = outputDevices.first(); + QCOMPARE(device->enabled(), OutputDevice::Enablement::Enabled); + QSignalSpy outputDeviceEnabledChangedSpy(device, &OutputDevice::enabledChanged); + OutputConfiguration *config; + + // Disables an output + config = outManagement->createConfiguration(); + QSignalSpy configAppliedSpy (config, &OutputConfiguration::applied); + config->setEnabled(device, OutputDevice::Enablement::Disabled); + config->apply(); + QVERIFY(configAppliedSpy.wait()); + + QCOMPARE(outputDeviceEnabledChangedSpy.count(), 1); + QCOMPARE(device->enabled(), OutputDevice::Enablement::Disabled); + QCOMPARE(screenChangedSpy.count(), 3); + QCOMPARE(outputLeftSpy.count(), 1); + QCOMPARE(outputEnteredSpy.count(), 1); // surface was moved to other screen + QCOMPARE(surface->outputs().count(), 1); + QCOMPARE(screens()->count(), 1); + QCOMPARE(modesChangedSpy.count(), 0); + QCOMPARE(outputEnabledSpy.count(), 0); + QCOMPARE(outputDisabledSpy.count(), 1); + + screenChangedSpy.clear(); + outputLeftSpy.clear(); + outputEnteredSpy.clear(); + outputDeviceEnabledChangedSpy.clear(); + outputEnabledSpy.clear(); + outputDisabledSpy.clear(); + + // Enable the disabled output + config = outManagement->createConfiguration(); + QSignalSpy configAppliedSpy2 (config, &OutputConfiguration::applied); + config->setEnabled(device, OutputDevice::Enablement::Enabled); + config->apply(); + QVERIFY(configAppliedSpy2.wait()); + + QVERIFY(outputEnteredSpy.wait()); + + QCOMPARE(outputDeviceEnabledChangedSpy.count(), 1); + QCOMPARE(device->enabled(), OutputDevice::Enablement::Enabled); + QCOMPARE(screenChangedSpy.count(), 3); + QCOMPARE(outputLeftSpy.count(), 1); + QCOMPARE(outputEnteredSpy.count(), 1); // surface moved back to first screen + QCOMPARE(surface->outputs().count(), 1); + QCOMPARE(screens()->count(), 2); + QCOMPARE(modesChangedSpy.count(), 0); + QCOMPARE(outputEnabledSpy.count(), 1); + QCOMPARE(outputDisabledSpy.count(), 0); +} + +WAYLANDTEST_MAIN(TestOutputManagement) +#include "outputmanagement_test.moc" diff --git a/autotests/integration/test_helpers.cpp b/autotests/integration/test_helpers.cpp index f863c74104..4674c1859a 100644 --- a/autotests/integration/test_helpers.cpp +++ b/autotests/integration/test_helpers.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -200,6 +201,7 @@ static struct { OutputManagement* outputManagement = nullptr; QThread *thread = nullptr; QVector outputs; + QVector outputDevices; IdleInhibitManager *idleInhibit = nullptr; AppMenuManager *appMenu = nullptr; XdgDecorationManager *xdgDecoration = nullptr; @@ -299,7 +301,7 @@ bool setupWaylandConnection(AdditionalWaylandInterfaces flags) registry->setEventQueue(s_waylandConnection.queue); QObject::connect(registry, &Registry::outputAnnounced, [=](quint32 name, quint32 version) { - auto output = registry->createOutput(name, version, s_waylandConnection.registry); + Output* output = registry->createOutput(name, version, s_waylandConnection.registry); s_waylandConnection.outputs << output; QObject::connect(output, &Output::removed, [=]() { output->deleteLater(); @@ -310,6 +312,22 @@ bool setupWaylandConnection(AdditionalWaylandInterfaces flags) }); }); + if (flags.testFlag(AdditionalWaylandInterface::OutputDevice)) { + QObject::connect(registry, &KWayland::Client::Registry::outputDeviceAnnounced, + [=](quint32 name, quint32 version) { + + OutputDevice *device = registry->createOutputDevice(name, version); + s_waylandConnection.outputDevices << device; + + QObject::connect(device, &OutputDevice::removed, [=]() { + s_waylandConnection.outputDevices.removeOne(device); + }); + QObject::connect(device, &OutputDevice::destroyed, [=]() { + s_waylandConnection.outputDevices.removeOne(device); + }); + }); + } + QObject::connect(registry, &Registry::interfaceAnnounced, [=](const QByteArray &interface, quint32 name, quint32 version) { if (flags & AdditionalWaylandInterface::InputMethodV1) { if (interface == QByteArrayLiteral("zwp_input_method_v1")) { @@ -497,6 +515,8 @@ void destroyWaylandConnection() s_waylandConnection.thread = nullptr; s_waylandConnection.connection = nullptr; } + s_waylandConnection.outputs.clear(); + s_waylandConnection.outputDevices.clear(); } ConnectionThread *waylandConnection() @@ -584,6 +604,11 @@ QVector waylandOutputs() return s_waylandConnection.outputs; } +QVector waylandOutputDevices() +{ + return s_waylandConnection.outputDevices; +} + bool waitForWaylandPointer() { if (!s_waylandConnection.seat) { diff --git a/platform.cpp b/platform.cpp index 9ed997c6bf..a5a12fdbc5 100644 --- a/platform.cpp +++ b/platform.cpp @@ -122,16 +122,19 @@ void Platform::requestOutputsChange(KWaylandServer::OutputConfigurationInterface for (auto it = changes.begin(); it != changes.end(); it++) { const KWaylandServer::OutputChangeSet *changeset = it.value(); - auto output = findOutput(it.key()->uuid()); + AbstractOutput* output = findOutput(it.key()->uuid()); if (!output) { qCWarning(KWIN_CORE) << "Could NOT find output matching " << it.key()->uuid(); continue; } + qDebug(KWIN_CORE) << "Platform::requestOutputsChange enabling" << changeset << it.key()->uuid() << changeset->enabledChanged() << (changeset->enabled() == Enablement::Enabled); + if (changeset->enabledChanged() && changeset->enabled() == Enablement::Enabled) { output->setEnabled(true); } + output->applyChanges(changeset); } @@ -152,9 +155,11 @@ void Platform::requestOutputsChange(KWaylandServer::OutputConfigurationInterface qCWarning(KWIN_CORE) << "Could NOT find output matching " << it.key()->uuid(); continue; } + qDebug(KWIN_CORE) << "Platform::requestOutputsChange disabling false" << it.key()->uuid(); output->setEnabled(false); } } + emit screens()->changed(); config->setApplied(); } diff --git a/plugins/platforms/virtual/screens_virtual.cpp b/plugins/platforms/virtual/screens_virtual.cpp new file mode 100644 index 0000000000..a3dbf349d0 --- /dev/null +++ b/plugins/platforms/virtual/screens_virtual.cpp @@ -0,0 +1,44 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2015 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "screens_virtual.h" +#include "virtual_backend.h" +#include "virtual_output.h" + +namespace KWin +{ + +VirtualScreens::VirtualScreens(VirtualBackend *backend, QObject *parent) + : OutputScreens(backend, parent) + , m_backend(backend) +{ + connect(backend, &VirtualBackend::screensQueried, this, &VirtualScreens::updateCount); + connect(backend, &VirtualBackend::screensQueried, this, &VirtualScreens::changed); +} + +VirtualScreens::~VirtualScreens() = default; + +void VirtualScreens::init() +{ + updateCount(); + KWin::Screens::init(); + + connect(m_backend, &VirtualBackend::virtualOutputsSet, this, + [this] (bool countChanged) { + if (countChanged) { + setCount(m_backend->outputs().size()); + } else { + emit changed(); + } + } + ); + + emit changed(); +} + +} diff --git a/plugins/platforms/virtual/virtual_backend.cpp b/plugins/platforms/virtual/virtual_backend.cpp index 12cb27e9a1..838db3a07b 100644 --- a/plugins/platforms/virtual/virtual_backend.cpp +++ b/plugins/platforms/virtual/virtual_backend.cpp @@ -35,6 +35,8 @@ VirtualBackend::VirtualBackend(QObject *parent) qDebug() << "Screenshots saved to: " << m_screenshotDir->path(); } } + + supportsOutputChanges(); setSupportsPointerWarping(true); setSupportsGammaControl(true); setPerScreenRenderingEnabled(true); @@ -97,7 +99,7 @@ Outputs VirtualBackend::outputs() const Outputs VirtualBackend::enabledOutputs() const { - return m_outputs; + return m_outputsEnabled; } void VirtualBackend::setVirtualOutputs(int count, QVector geometries, QVector scales) @@ -105,9 +107,15 @@ void VirtualBackend::setVirtualOutputs(int count, QVector geometries, QVe Q_ASSERT(geometries.size() == 0 || geometries.size() == count); Q_ASSERT(scales.size() == 0 || scales.size() == count); + bool countChanged = m_outputs.size() != count; + + while (!m_outputsEnabled.isEmpty()) { + VirtualOutput *output = m_outputsEnabled.takeLast(); + emit outputDisabled(output); + } + while (!m_outputs.isEmpty()) { VirtualOutput *output = m_outputs.takeLast(); - emit outputDisabled(output); emit outputRemoved(output); delete output; } @@ -126,6 +134,7 @@ void VirtualBackend::setVirtualOutputs(int count, QVector geometries, QVe vo->setScale(scales.at(i)); } m_outputs.append(vo); + m_outputsEnabled.append(vo); emit outputAdded(vo); emit outputEnabled(vo); } @@ -133,4 +142,37 @@ void VirtualBackend::setVirtualOutputs(int count, QVector geometries, QVe emit screensQueried(); } +void VirtualBackend::enableOutput(VirtualOutput *output, bool enable) +{ + if (enable) { + Q_ASSERT(!m_outputsEnabled.contains(output)); + m_outputsEnabled << output; + + emit outputEnabled(output); + } else { + Q_ASSERT(m_outputsEnabled.contains(output)); + m_outputsEnabled.removeOne(output); + + emit outputDisabled(output); + } + + emit screensQueried(); +} + +void VirtualBackend::removeOutput(AbstractOutput *output) +{ + + VirtualOutput* virtualOutput = static_cast(output); + if (m_outputsEnabled.removeOne(virtualOutput)) { + emit outputDisabled(virtualOutput); + } + + emit outputRemoved(virtualOutput); + m_outputsEnabled.removeOne(virtualOutput); + + delete virtualOutput; + + emit screensQueried(); +} + } diff --git a/plugins/platforms/virtual/virtual_backend.h b/plugins/platforms/virtual/virtual_backend.h index ffe386c4d8..151e3d1d82 100644 --- a/plugins/platforms/virtual/virtual_backend.h +++ b/plugins/platforms/virtual/virtual_backend.h @@ -52,11 +52,16 @@ public: return QVector{OpenGLCompositing, QPainterCompositing}; } + void enableOutput(VirtualOutput *output, bool enable); + + void removeOutput(AbstractOutput *output); + Q_SIGNALS: void virtualOutputsSet(bool countChanged); private: QVector m_outputs; + QVector m_outputsEnabled; QScopedPointer m_screenshotDir; }; diff --git a/plugins/platforms/virtual/virtual_output.cpp b/plugins/platforms/virtual/virtual_output.cpp index 6d1393885d..b8e47d2b82 100644 --- a/plugins/platforms/virtual/virtual_output.cpp +++ b/plugins/platforms/virtual/virtual_output.cpp @@ -7,23 +7,24 @@ SPDX-License-Identifier: GPL-2.0-or-later */ #include "virtual_output.h" +#include "virtual_backend.h" + #include "renderloop_p.h" #include "softwarevsyncmonitor.h" namespace KWin { -VirtualOutput::VirtualOutput(QObject *parent) - : AbstractWaylandOutput() +VirtualOutput::VirtualOutput(VirtualBackend *parent) + : AbstractWaylandOutput(parent) + , m_backend(parent) , m_renderLoop(new RenderLoop(this)) , m_vsyncMonitor(SoftwareVsyncMonitor::create(this)) { - Q_UNUSED(parent); - connect(m_vsyncMonitor, &VsyncMonitor::vblankOccurred, this, &VirtualOutput::vblank); static int identifier = -1; - identifier++; + m_identifier = ++identifier; setName("Virtual-" + QString::number(identifier)); } @@ -52,7 +53,10 @@ void VirtualOutput::init(const QPoint &logicalPosition, const QSize &pixelSize) mode.size = pixelSize; mode.flags = KWaylandServer::OutputDeviceInterface::ModeFlag::Current; mode.refreshRate = refreshRate; - initInterfaces("model_TODO", "manufacturer_TODO", "UUID_TODO", pixelSize, { mode }, {}); + initInterfaces(QByteArray("model_").append(QString::number(m_identifier)), + QByteArray("manufacturer_").append(QString::number(m_identifier)), + QByteArray("UUID_").append(QString::number(m_identifier)), + pixelSize, { mode }, QByteArray("EDID_").append(QString::number(m_identifier))); setGeometry(QRect(logicalPosition, pixelSize)); } @@ -68,4 +72,9 @@ void VirtualOutput::vblank(std::chrono::nanoseconds timestamp) renderLoopPrivate->notifyFrameCompleted(timestamp); } +void VirtualOutput::updateEnablement(bool enable) +{ + m_backend->enableOutput(this, enable); +} + } diff --git a/plugins/platforms/virtual/virtual_output.h b/plugins/platforms/virtual/virtual_output.h index 9767e5829c..a8f45662fe 100644 --- a/plugins/platforms/virtual/virtual_output.h +++ b/plugins/platforms/virtual/virtual_output.h @@ -25,7 +25,7 @@ class VirtualOutput : public AbstractWaylandOutput Q_OBJECT public: - VirtualOutput(QObject *parent = nullptr); + VirtualOutput(VirtualBackend *parent = nullptr); ~VirtualOutput() override; RenderLoop *renderLoop() const override; @@ -43,16 +43,20 @@ public: return m_gammaResult; } + void updateEnablement(bool enable) override; + private: void vblank(std::chrono::nanoseconds timestamp); Q_DISABLE_COPY(VirtualOutput); friend class VirtualBackend; + VirtualBackend *m_backend; RenderLoop *m_renderLoop; SoftwareVsyncMonitor *m_vsyncMonitor; int m_gammaSize = 200; bool m_gammaResult = true; + int m_identifier; }; }