Add TestOutputManagement::testOutputDeviceDisabled test

Allow VirtualBackend to supports Output changes.
This commit is contained in:
Méven Car 2021-01-14 08:21:59 +00:00
parent 866dfb4e89
commit 4f744d1bb6
11 changed files with 329 additions and 15 deletions

View file

@ -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);

View file

@ -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)

View file

@ -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<KWayland::Client::Output *> waylandOutputs();
QVector<KWayland::Client::OutputDevice *> waylandOutputDevices();
bool waitForWaylandPointer();
bool waitForWaylandTouch();

View file

@ -0,0 +1,176 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2020 Méven Car <meven.car@enioka.com>
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 <KWayland/Client/outputmanagement.h>
#include <KWayland/Client/outputconfiguration.h>
#include <KWayland/Client/outputdevice.h>
#include <KWaylandServer/outputmanagement_interface.h>
#include <KWaylandServer/outputconfiguration_interface.h>
#include <KWaylandServer/outputdevice_interface.h>
#include <KWayland/Client/output.h>
#include <KWayland/Client/outputdevice.h>
#include <KWayland/Client/server_decoration.h>
#include <KWayland/Client/surface.h>
#include <KWayland/Client/xdgshell.h>
#include <KWaylandServer/display.h>
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<KWin::Deleted*>();
qRegisterMetaType<KWin::AbstractClient*>();
qRegisterMetaType<KWin::AbstractOutput*>();
qRegisterMetaType<KWin::AbstractOutput*>("AbstractOutput *");
qRegisterMetaType<KWayland::Client::Output*>();
qRegisterMetaType<KWayland::Client::OutputDevice::Enablement>();
qRegisterMetaType<OutputDevice::Enablement>("OutputDevice::Enablement");
qRegisterMetaType<KWaylandServer::OutputDeviceInterface::Enablement>();
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> surface(Test::createSurface());
QScopedPointer<XdgShellSurface> 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"

View file

@ -27,6 +27,7 @@
#include <KWayland/Client/shadow.h>
#include <KWayland/Client/shm_pool.h>
#include <KWayland/Client/output.h>
#include <KWayland/Client/outputdevice.h>
#include <KWayland/Client/subcompositor.h>
#include <KWayland/Client/subsurface.h>
#include <KWayland/Client/surface.h>
@ -200,6 +201,7 @@ static struct {
OutputManagement* outputManagement = nullptr;
QThread *thread = nullptr;
QVector<Output*> outputs;
QVector<OutputDevice*> 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<KWayland::Client::Output *> waylandOutputs()
return s_waylandConnection.outputs;
}
QVector<OutputDevice *> waylandOutputDevices()
{
return s_waylandConnection.outputDevices;
}
bool waitForWaylandPointer()
{
if (!s_waylandConnection.seat) {

View file

@ -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();
}

View file

@ -0,0 +1,44 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
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();
}
}

View file

@ -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<QRect> geometries, QVector<int> scales)
@ -105,9 +107,15 @@ void VirtualBackend::setVirtualOutputs(int count, QVector<QRect> 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<QRect> 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<QRect> 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<VirtualOutput *>(output);
if (m_outputsEnabled.removeOne(virtualOutput)) {
emit outputDisabled(virtualOutput);
}
emit outputRemoved(virtualOutput);
m_outputsEnabled.removeOne(virtualOutput);
delete virtualOutput;
emit screensQueried();
}
}

View file

@ -52,11 +52,16 @@ public:
return QVector<CompositingType>{OpenGLCompositing, QPainterCompositing};
}
void enableOutput(VirtualOutput *output, bool enable);
void removeOutput(AbstractOutput *output);
Q_SIGNALS:
void virtualOutputsSet(bool countChanged);
private:
QVector<VirtualOutput*> m_outputs;
QVector<VirtualOutput*> m_outputsEnabled;
QScopedPointer<QTemporaryDir> m_screenshotDir;
};

View file

@ -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);
}
}

View file

@ -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;
};
}