diff --git a/CMakeLists.txt b/CMakeLists.txt index 5ce8635399..3f0df8b26c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -128,13 +128,14 @@ set_package_properties(epoxy PROPERTIES DESCRIPTION "libepoxy" PURPOSE "OpenGL dispatch library" ) -find_package(Wayland 1.2 COMPONENTS Client Egl Cursor) +find_package(Wayland 1.2 COMPONENTS Client Egl Cursor Server) set_package_properties(Wayland PROPERTIES TYPE OPTIONAL PURPOSE "Required for building KWin with Wayland support" ) add_feature_info("Wayland-Client" Wayland_Client_FOUND "Required for building the Wayland backend in KWin") add_feature_info("Wayland-EGL" Wayland_Egl_FOUND "Required for building the Wayland EGL compositing backend in KWin") +add_feature_info("Wayland-Server" Wayland_Server_FOUND "Required for building Wayland server backend in KWin") find_program(WAYLAND_SCANNER_EXECUTABLE NAMES wayland-scanner) add_feature_info("wayland-scanner" WAYLAND_SCANNER_EXECUTABLE "Required for generating Wayland protocols") diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index 4bf1cac8a5..74fbb7cc2f 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -1,7 +1,8 @@ add_definitions(-DKWIN_UNIT_TEST) -if(Wayland_Client_FOUND AND XKB_FOUND) +if(Wayland_Client_FOUND AND XKB_FOUND AND Wayland_Server_FOUND) add_subdirectory(wayland_client) + add_subdirectory(wayland_server) endif() ######################################################## diff --git a/autotests/wayland_client/CMakeLists.txt b/autotests/wayland_client/CMakeLists.txt index 130cd4eaa5..cd6ef85c71 100644 --- a/autotests/wayland_client/CMakeLists.txt +++ b/autotests/wayland_client/CMakeLists.txt @@ -3,9 +3,14 @@ set_source_files_properties(${CMAKE_BINARY_DIR}/wayland_protocols/wayland-client ######################################################## # Test WaylandConnectionThread ######################################################## -set( testWaylandConnectionThread_SRCS test_wayland_connection_thread.cpp ${KWIN_SOURCE_DIR}/wayland_client/connection_thread.cpp ) +set( testWaylandConnectionThread_SRCS + test_wayland_connection_thread.cpp + ${KWIN_SOURCE_DIR}/wayland_client/connection_thread.cpp + ${KWIN_SOURCE_DIR}/wayland_server/display.cpp + ${KWIN_SOURCE_DIR}/wayland_server/output_interface.cpp + ) add_executable(testWaylandConnectionThread ${testWaylandConnectionThread_SRCS}) -target_link_libraries( testWaylandConnectionThread Qt5::Test Wayland::Client) +target_link_libraries( testWaylandConnectionThread Qt5::Test Wayland::Client Wayland::Server) add_test(kwin-testWaylandConnectionThread testWaylandConnectionThread) ecm_mark_as_test(testWaylandConnectionThread) @@ -52,10 +57,12 @@ set( testWaylandOutput_SRCS ${KWIN_SOURCE_DIR}/wayland_client/fullscreen_shell.cpp ${KWIN_SOURCE_DIR}/wayland_client/output.cpp ${CMAKE_BINARY_DIR}/wayland_protocols/wayland-client-fullscreen-shell.c + ${KWIN_SOURCE_DIR}/wayland_server/display.cpp + ${KWIN_SOURCE_DIR}/wayland_server/output_interface.cpp ) add_executable(testWaylandOutput ${testWaylandOutput_SRCS}) add_dependencies(testWaylandOutput wayland-client-fullscreen-shell) -target_link_libraries( testWaylandOutput Qt5::Test Wayland::Client) +target_link_libraries( testWaylandOutput Qt5::Test Wayland::Client Wayland::Server) add_test(kwin-testWaylandOutput testWaylandOutput) ecm_mark_as_test(testWaylandOutput) diff --git a/autotests/wayland_client/test_wayland_connection_thread.cpp b/autotests/wayland_client/test_wayland_connection_thread.cpp index c045e5cfcd..c60b63c282 100644 --- a/autotests/wayland_client/test_wayland_connection_thread.cpp +++ b/autotests/wayland_client/test_wayland_connection_thread.cpp @@ -21,6 +21,7 @@ along with this program. If not, see . #include // KWin #include "../../wayland_client/connection_thread.h" +#include "../../wayland_server/display.h" // Wayland #include @@ -39,60 +40,36 @@ private Q_SLOTS: void testConnectionThread(); private: - QProcess *m_westonProcess; + KWin::WaylandServer::Display *m_display; }; static const QString s_socketName = QStringLiteral("kwin-test-wayland-connection-0"); TestWaylandConnectionThread::TestWaylandConnectionThread(QObject *parent) : QObject(parent) - , m_westonProcess(nullptr) + , m_display(nullptr) { } void TestWaylandConnectionThread::init() { - QVERIFY(!m_westonProcess); - // starts weston - m_westonProcess = new QProcess(this); - m_westonProcess->setProgram(QStringLiteral("weston")); - - m_westonProcess->setArguments(QStringList({QStringLiteral("--socket=%1").arg(s_socketName), QStringLiteral("--use-pixman")})); - m_westonProcess->start(); - QVERIFY(m_westonProcess->waitForStarted()); - - // wait for the socket to appear - QDir runtimeDir(qgetenv("XDG_RUNTIME_DIR")); - if (runtimeDir.exists(s_socketName)) { - return; - } - QFileSystemWatcher *socketWatcher = new QFileSystemWatcher(QStringList({runtimeDir.absolutePath()}), this); - QSignalSpy socketSpy(socketWatcher, SIGNAL(directoryChanged(QString))); - - // limit to maximum of 10 waits - for (int i = 0; i < 10; ++i) { - QVERIFY(socketSpy.wait()); - if (runtimeDir.exists(s_socketName)) { - delete socketWatcher; - return; - } - } + using namespace KWin::WaylandServer; + delete m_display; + m_display = new Display(this); + QSignalSpy displayRunning(m_display, SIGNAL(runningChanged(bool))); + m_display->setSocketName(s_socketName); + m_display->start(); + QVERIFY(m_display->isRunning()); } void TestWaylandConnectionThread::cleanup() { - // terminates weston - m_westonProcess->terminate(); - QVERIFY(m_westonProcess->waitForFinished()); - delete m_westonProcess; - m_westonProcess = nullptr; + delete m_display; + m_display = nullptr; } void TestWaylandConnectionThread::testInitConnectionNoThread() { - if (m_westonProcess->state() != QProcess::Running) { - QSKIP("This test requires a running wayland server"); - } QScopedPointer connection(new KWin::Wayland::ConnectionThread); QCOMPARE(connection->socketName(), QStringLiteral("wayland-0")); connection->setSocketName(s_socketName); @@ -109,9 +86,6 @@ void TestWaylandConnectionThread::testInitConnectionNoThread() void TestWaylandConnectionThread::testConnectionFailure() { - if (m_westonProcess->state() != QProcess::Running) { - QSKIP("This test requires a running wayland server"); - } QScopedPointer connection(new KWin::Wayland::ConnectionThread); connection->setSocketName(QStringLiteral("kwin-test-socket-does-not-exist")); @@ -148,9 +122,6 @@ static const struct wl_registry_listener s_registryListener = { void TestWaylandConnectionThread::testConnectionThread() { - if (m_westonProcess->state() != QProcess::Running) { - QSKIP("This test requires a running wayland server"); - } QScopedPointer connection(new KWin::Wayland::ConnectionThread); connection->setSocketName(s_socketName); @@ -177,7 +148,10 @@ void TestWaylandConnectionThread::testConnectionThread() wl_registry_add_listener(registry, &s_registryListener, this); wl_display_flush(display); - QVERIFY(eventsSpy.wait()); + if (eventsSpy.isEmpty()) { + QVERIFY(eventsSpy.wait()); + } + QVERIFY(!eventsSpy.isEmpty()); wl_registry_destroy(registry); wl_event_queue_destroy(queue); @@ -189,9 +163,6 @@ void TestWaylandConnectionThread::testConnectionThread() void TestWaylandConnectionThread::testConnectionDieing() { - if (m_westonProcess->state() != QProcess::Running) { - QSKIP("This test requires a running wayland server"); - } QScopedPointer connection(new KWin::Wayland::ConnectionThread); QSignalSpy connectedSpy(connection.data(), SIGNAL(connected())); connection->setSocketName(s_socketName); @@ -200,8 +171,8 @@ void TestWaylandConnectionThread::testConnectionDieing() QVERIFY(connection->display()); QSignalSpy diedSpy(connection.data(), SIGNAL(connectionDied())); - m_westonProcess->terminate(); - QVERIFY(m_westonProcess->waitForFinished()); + m_display->terminate(); + QVERIFY(!m_display->isRunning()); QVERIFY(diedSpy.wait()); QCOMPARE(diedSpy.count(), 1); QVERIFY(!connection->display()); @@ -209,9 +180,8 @@ void TestWaylandConnectionThread::testConnectionDieing() connectedSpy.clear(); QVERIFY(connectedSpy.isEmpty()); // restarts the server - delete m_westonProcess; - m_westonProcess = nullptr; - init(); + m_display->start(); + QVERIFY(m_display->isRunning()); if (connectedSpy.count() == 0) { QVERIFY(connectedSpy.wait()); } diff --git a/autotests/wayland_client/test_wayland_output.cpp b/autotests/wayland_client/test_wayland_output.cpp index 1f9fe959b4..854512bbcc 100644 --- a/autotests/wayland_client/test_wayland_output.cpp +++ b/autotests/wayland_client/test_wayland_output.cpp @@ -23,6 +23,8 @@ along with this program. If not, see . #include "../../wayland_client/connection_thread.h" #include "../../wayland_client/output.h" #include "../../wayland_client/registry.h" +#include "../../wayland_server/display.h" +#include "../../wayland_server/output_interface.h" // Wayland #include @@ -36,79 +38,91 @@ private Q_SLOTS: void cleanup(); void testRegistry(); + void testModeChanges(); + void testScaleChange(); - // TODO: add tests for removal - requires more control over the compositor + void testSubPixel_data(); + void testSubPixel(); + + void testTransform_data(); + void testTransform(); private: - QProcess *m_westonProcess; + KWin::WaylandServer::Display *m_display; + KWin::WaylandServer::OutputInterface *m_serverOutput; + KWin::Wayland::ConnectionThread *m_connection; + QThread *m_thread; }; static const QString s_socketName = QStringLiteral("kwin-test-wayland-output-0"); TestWaylandOutput::TestWaylandOutput(QObject *parent) : QObject(parent) - , m_westonProcess(nullptr) + , m_display(nullptr) + , m_serverOutput(nullptr) + , m_connection(nullptr) + , m_thread(nullptr) { } void TestWaylandOutput::init() { - QVERIFY(!m_westonProcess); - // starts weston - m_westonProcess = new QProcess(this); - m_westonProcess->setProgram(QStringLiteral("weston")); + using namespace KWin::WaylandServer; + delete m_display; + m_display = new Display(this); + m_display->setSocketName(s_socketName); + m_display->start(); + QVERIFY(m_display->isRunning()); - m_westonProcess->setArguments(QStringList({QStringLiteral("--socket=%1").arg(s_socketName), - QStringLiteral("--use-pixman"), - QStringLiteral("--width=1024"), - QStringLiteral("--height=768")})); - m_westonProcess->start(); - QVERIFY(m_westonProcess->waitForStarted()); + m_serverOutput = m_display->createOutput(this); + m_serverOutput->addMode(QSize(800, 600)); + m_serverOutput->addMode(QSize(1024, 768)); + m_serverOutput->addMode(QSize(1280, 1024)); + m_serverOutput->setCurrentMode(QSize(1024, 768)); + m_serverOutput->create(); - // wait for the socket to appear - QDir runtimeDir(qgetenv("XDG_RUNTIME_DIR")); - if (runtimeDir.exists(s_socketName)) { - return; - } - QFileSystemWatcher *socketWatcher = new QFileSystemWatcher(QStringList({runtimeDir.absolutePath()}), this); - QSignalSpy socketSpy(socketWatcher, SIGNAL(directoryChanged(QString))); + // setup connection + m_connection = new KWin::Wayland::ConnectionThread; + QSignalSpy connectedSpy(m_connection, SIGNAL(connected())); + m_connection->setSocketName(s_socketName); - // limit to maximum of 10 waits - for (int i = 0; i < 10; ++i) { - QVERIFY(socketSpy.wait()); - if (runtimeDir.exists(s_socketName)) { - delete socketWatcher; - return; - } - } + m_thread = new QThread(this); + m_connection->moveToThread(m_thread); + m_thread->start(); + + m_connection->initConnection(); + QVERIFY(connectedSpy.wait()); } void TestWaylandOutput::cleanup() { - // terminates weston - m_westonProcess->terminate(); - QVERIFY(m_westonProcess->waitForFinished()); - delete m_westonProcess; - m_westonProcess = nullptr; + if (m_thread) { + m_thread->quit(); + m_thread->wait(); + delete m_thread; + m_thread = nullptr; + } + delete m_connection; + m_connection = nullptr; + + delete m_serverOutput; + m_serverOutput = nullptr; + + delete m_display; + m_display = nullptr; } void TestWaylandOutput::testRegistry() { - if (m_westonProcess->state() != QProcess::Running) { - QSKIP("This test requires a running wayland server"); - } - KWin::Wayland::ConnectionThread connection; - QSignalSpy connectedSpy(&connection, SIGNAL(connected())); - connection.setSocketName(s_socketName); - connection.initConnection(); - QVERIFY(connectedSpy.wait()); + m_serverOutput->setGlobalPosition(QPoint(100, 50)); + m_serverOutput->setPhysicalSize(QSize(200, 100)); KWin::Wayland::Registry registry; QSignalSpy announced(®istry, SIGNAL(outputAnnounced(quint32,quint32))); - registry.create(connection.display()); + registry.create(m_connection->display()); QVERIFY(registry.isValid()); registry.setup(); - wl_display_flush(connection.display()); + wl_display_flush(m_connection->display()); QVERIFY(announced.wait()); KWin::Wayland::Output output; @@ -128,14 +142,14 @@ void TestWaylandOutput::testRegistry() QVERIFY(outputChanged.isValid()); output.setup(registry.bindOutput(announced.first().first().value(), announced.first().last().value())); - wl_display_flush(connection.display()); + wl_display_flush(m_connection->display()); QVERIFY(outputChanged.wait()); - QCOMPARE(output.geometry(), QRect(0, 0, 1024, 768)); - QCOMPARE(output.globalPosition(), QPoint(0, 0)); - QCOMPARE(output.manufacturer(), QStringLiteral("xwayland")); + QCOMPARE(output.geometry(), QRect(100, 50, 1024, 768)); + QCOMPARE(output.globalPosition(), QPoint(100, 50)); + QCOMPARE(output.manufacturer(), QStringLiteral("org.kde.kwin")); QCOMPARE(output.model(), QStringLiteral("none")); - // TODO: add test for physicalSize + QCOMPARE(output.physicalSize(), QSize(200, 100)); QCOMPARE(output.pixelSize(), QSize(1024, 768)); QCOMPARE(output.refreshRate(), 60000); QCOMPARE(output.scale(), 1); @@ -145,5 +159,168 @@ void TestWaylandOutput::testRegistry() QCOMPARE(output.transform(), KWin::Wayland::Output::Transform::Normal); } +void TestWaylandOutput::testModeChanges() +{ + KWin::Wayland::Registry registry; + QSignalSpy announced(®istry, SIGNAL(outputAnnounced(quint32,quint32))); + registry.create(m_connection->display()); + QVERIFY(registry.isValid()); + registry.setup(); + wl_display_flush(m_connection->display()); + QVERIFY(announced.wait()); + + KWin::Wayland::Output output; + QSignalSpy outputChanged(&output, SIGNAL(changed())); + QVERIFY(outputChanged.isValid()); + output.setup(registry.bindOutput(announced.first().first().value(), announced.first().last().value())); + wl_display_flush(m_connection->display()); + QVERIFY(outputChanged.wait()); + + QCOMPARE(output.pixelSize(), QSize(1024, 768)); + + // change the current mode + outputChanged.clear(); + m_serverOutput->setCurrentMode(QSize(800, 600)); + QVERIFY(outputChanged.wait()); + QCOMPARE(output.pixelSize(), QSize(800, 600)); + + // change once more + outputChanged.clear(); + m_serverOutput->setCurrentMode(QSize(1280, 1024)); + QVERIFY(outputChanged.wait()); + QCOMPARE(output.pixelSize(), QSize(1280, 1024)); +} + +void TestWaylandOutput::testScaleChange() +{ + KWin::Wayland::Registry registry; + QSignalSpy announced(®istry, SIGNAL(outputAnnounced(quint32,quint32))); + registry.create(m_connection->display()); + QVERIFY(registry.isValid()); + registry.setup(); + wl_display_flush(m_connection->display()); + QVERIFY(announced.wait()); + + KWin::Wayland::Output output; + QSignalSpy outputChanged(&output, SIGNAL(changed())); + QVERIFY(outputChanged.isValid()); + output.setup(registry.bindOutput(announced.first().first().value(), announced.first().last().value())); + wl_display_flush(m_connection->display()); + QVERIFY(outputChanged.wait()); + QCOMPARE(output.scale(), 1); + + // change the scale + outputChanged.clear(); + m_serverOutput->setScale(2); + QVERIFY(outputChanged.wait()); + QCOMPARE(output.scale(), 2); + + // change once more + outputChanged.clear(); + m_serverOutput->setScale(4); + QVERIFY(outputChanged.wait()); + QCOMPARE(output.scale(), 4); +} + +void TestWaylandOutput::testSubPixel_data() +{ + using namespace KWin::Wayland; + using namespace KWin::WaylandServer; + QTest::addColumn("expected"); + QTest::addColumn("actual"); + + QTest::newRow("none") << Output::SubPixel::None << OutputInterface::SubPixel::None; + QTest::newRow("horizontal/rgb") << Output::SubPixel::HorizontalRGB << OutputInterface::SubPixel::HorizontalRGB; + QTest::newRow("horizontal/bgr") << Output::SubPixel::HorizontalBGR << OutputInterface::SubPixel::HorizontalBGR; + QTest::newRow("vertical/rgb") << Output::SubPixel::VerticalRGB << OutputInterface::SubPixel::VerticalRGB; + QTest::newRow("vertical/bgr") << Output::SubPixel::VerticalBGR << OutputInterface::SubPixel::VerticalBGR; +} + +void TestWaylandOutput::testSubPixel() +{ + using namespace KWin::Wayland; + using namespace KWin::WaylandServer; + QFETCH(OutputInterface::SubPixel, actual); + m_serverOutput->setSubPixel(actual); + + KWin::Wayland::Registry registry; + QSignalSpy announced(®istry, SIGNAL(outputAnnounced(quint32,quint32))); + registry.create(m_connection->display()); + QVERIFY(registry.isValid()); + registry.setup(); + wl_display_flush(m_connection->display()); + QVERIFY(announced.wait()); + + KWin::Wayland::Output output; + QSignalSpy outputChanged(&output, SIGNAL(changed())); + QVERIFY(outputChanged.isValid()); + output.setup(registry.bindOutput(announced.first().first().value(), announced.first().last().value())); + wl_display_flush(m_connection->display()); + if (outputChanged.isEmpty()) { + QVERIFY(outputChanged.wait()); + } + + QTEST(output.subPixel(), "expected"); + + // change back to unknown + outputChanged.clear(); + m_serverOutput->setSubPixel(OutputInterface::SubPixel::Unknown); + if (outputChanged.isEmpty()) { + QVERIFY(outputChanged.wait()); + } + QCOMPARE(output.subPixel(), Output::SubPixel::Unknown); +} + +void TestWaylandOutput::testTransform_data() +{ + using namespace KWin::Wayland; + using namespace KWin::WaylandServer; + QTest::addColumn("expected"); + QTest::addColumn("actual"); + + QTest::newRow("90") << Output::Transform::Rotated90 << OutputInterface::Transform::Rotated90; + QTest::newRow("180") << Output::Transform::Rotated180 << OutputInterface::Transform::Rotated180; + QTest::newRow("270") << Output::Transform::Rotated270 << OutputInterface::Transform::Rotated270; + QTest::newRow("Flipped") << Output::Transform::Flipped << OutputInterface::Transform::Flipped; + QTest::newRow("Flipped 90") << Output::Transform::Flipped90 << OutputInterface::Transform::Flipped90; + QTest::newRow("Flipped 180") << Output::Transform::Flipped180 << OutputInterface::Transform::Flipped180; + QTest::newRow("Flipped 280") << Output::Transform::Flipped270 << OutputInterface::Transform::Flipped270; +} + +void TestWaylandOutput::testTransform() +{ + using namespace KWin::Wayland; + using namespace KWin::WaylandServer; + QFETCH(OutputInterface::Transform, actual); + m_serverOutput->setTransform(actual); + + KWin::Wayland::Registry registry; + QSignalSpy announced(®istry, SIGNAL(outputAnnounced(quint32,quint32))); + registry.create(m_connection->display()); + QVERIFY(registry.isValid()); + registry.setup(); + wl_display_flush(m_connection->display()); + QVERIFY(announced.wait()); + + KWin::Wayland::Output output; + QSignalSpy outputChanged(&output, SIGNAL(changed())); + QVERIFY(outputChanged.isValid()); + output.setup(registry.bindOutput(announced.first().first().value(), announced.first().last().value())); + wl_display_flush(m_connection->display()); + if (outputChanged.isEmpty()) { + QVERIFY(outputChanged.wait()); + } + + QTEST(output.transform(), "expected"); + + // change back to normal + outputChanged.clear(); + m_serverOutput->setTransform(OutputInterface::Transform::Normal); + if (outputChanged.isEmpty()) { + QVERIFY(outputChanged.wait()); + } + QCOMPARE(output.transform(), Output::Transform::Normal); +} + QTEST_MAIN(TestWaylandOutput) #include "test_wayland_output.moc" diff --git a/autotests/wayland_server/CMakeLists.txt b/autotests/wayland_server/CMakeLists.txt new file mode 100644 index 0000000000..48897d0133 --- /dev/null +++ b/autotests/wayland_server/CMakeLists.txt @@ -0,0 +1,12 @@ +######################################################## +# Test WaylandServerDisplay +######################################################## +set( testWaylandServerDisplay_SRCS + test_display.cpp + ${KWIN_SOURCE_DIR}/wayland_server/display.cpp + ${KWIN_SOURCE_DIR}/wayland_server/output_interface.cpp + ) +add_executable(testWaylandServerDisplay ${testWaylandServerDisplay_SRCS}) +target_link_libraries( testWaylandServerDisplay Qt5::Test Wayland::Server) +add_test(kwin-testWaylandServerDisplay testWaylandServerDisplay) +ecm_mark_as_test(testWaylandServerDisplay) diff --git a/autotests/wayland_server/test_display.cpp b/autotests/wayland_server/test_display.cpp new file mode 100644 index 0000000000..d9a739cfa6 --- /dev/null +++ b/autotests/wayland_server/test_display.cpp @@ -0,0 +1,80 @@ +/******************************************************************** +KWin - the KDE window manager +This file is part of the KDE project. + +Copyright (C) 2014 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 . +*********************************************************************/ +// Qt +#include +// WaylandServer +#include "../../wayland_server/display.h" + +using namespace KWin::WaylandServer; + +class TestWaylandServerDisplay : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void testSocketName(); + void testStartStop(); +}; + +void TestWaylandServerDisplay::testSocketName() +{ + Display display; + QSignalSpy changedSpy(&display, SIGNAL(socketNameChanged(QString))); + QVERIFY(changedSpy.isValid()); + QCOMPARE(display.socketName(), QStringLiteral("wayland-0")); + const QString testSName = QStringLiteral("fooBar"); + display.setSocketName(testSName); + QCOMPARE(display.socketName(), testSName); + QCOMPARE(changedSpy.count(), 1); + QCOMPARE(changedSpy.first().first().toString(), testSName); + + // changing to same name again should not emit signal + display.setSocketName(testSName); + QCOMPARE(changedSpy.count(), 1); +} + +void TestWaylandServerDisplay::testStartStop() +{ + const QString testSocketName = QStringLiteral("kwin-wayland-server-display-test-0"); + QDir runtimeDir(qgetenv("XDG_RUNTIME_DIR")); + QVERIFY(runtimeDir.exists()); + QVERIFY(!runtimeDir.exists(testSocketName)); + + Display display; + QSignalSpy runningSpy(&display, SIGNAL(runningChanged(bool))); + QVERIFY(runningSpy.isValid()); + display.setSocketName(testSocketName); + QVERIFY(!display.isRunning()); + display.start(); +// QVERIFY(runningSpy.wait()); + QCOMPARE(runningSpy.count(), 1); + QVERIFY(runningSpy.first().first().toBool()); + QVERIFY(display.isRunning()); + QVERIFY(runtimeDir.exists(testSocketName)); + + display.terminate(); + QVERIFY(!display.isRunning()); + QCOMPARE(runningSpy.count(), 2); + QVERIFY(runningSpy.first().first().toBool()); + QVERIFY(!runningSpy.last().first().toBool()); + QVERIFY(!runtimeDir.exists(testSocketName)); +} + +QTEST_MAIN(TestWaylandServerDisplay) +#include "test_display.moc" diff --git a/wayland_client/output.h b/wayland_client/output.h index 905351c429..15dcb43b49 100644 --- a/wayland_client/output.h +++ b/wayland_client/output.h @@ -179,4 +179,7 @@ Output::Transform Output::transform() const } } +Q_DECLARE_METATYPE(KWin::Wayland::Output::SubPixel) +Q_DECLARE_METATYPE(KWin::Wayland::Output::Transform) + #endif diff --git a/wayland_server/display.cpp b/wayland_server/display.cpp new file mode 100644 index 0000000000..af4b410ffe --- /dev/null +++ b/wayland_server/display.cpp @@ -0,0 +1,133 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2014 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 "display.h" +#include "output_interface.h" + +#include +#include +#include +#include + +#include + +namespace KWin +{ +namespace WaylandServer +{ + +Display::Display(QObject *parent) + : QObject(parent) + , m_display(nullptr) + , m_loop(nullptr) + , m_socketName(QStringLiteral("wayland-0")) + , m_running(false) +{ + connect(QCoreApplication::eventDispatcher(), &QAbstractEventDispatcher::aboutToBlock, this, &Display::flush); +} + +Display::~Display() +{ + terminate(); +} + +void Display::flush() +{ + if (!m_display || !m_loop) { + return; + } + if (wl_event_loop_dispatch(m_loop, 0) != 0) { + qWarning() << "Error on dispatching Wayland event loop"; + } + wl_display_flush_clients(m_display); +} + +void Display::setSocketName(const QString &name) +{ + if (m_socketName == name) { + return; + } + m_socketName = name; + emit socketNameChanged(m_socketName); +} + +QString Display::socketName() const +{ + return m_socketName; +} + +void Display::start() +{ + Q_ASSERT(!m_running); + Q_ASSERT(!m_display); + m_display = wl_display_create(); + if (wl_display_add_socket(m_display, qPrintable(m_socketName)) != 0) { + return; + } + + m_loop = wl_display_get_event_loop(m_display); + int fd = wl_event_loop_get_fd(m_loop); + if (fd == -1) { + qWarning() << "Did not get the file descriptor for the event loop"; + return; + } + QSocketNotifier *m_notifier = new QSocketNotifier(fd, QSocketNotifier::Read, this); + connect(m_notifier, &QSocketNotifier::activated, this, &Display::flush); + setRunning(true); +} + +void Display::terminate() +{ + if (!m_running) { + return; + } + emit aboutToTerminate(); + wl_display_terminate(m_display); + wl_display_destroy(m_display); + m_display = nullptr; + m_loop = nullptr; + setRunning(false); +} + +void Display::setRunning(bool running) +{ + if (m_running == running) { + return; + } + m_running = running; + emit runningChanged(m_running); +} + +OutputInterface *Display::createOutput(QObject *parent) +{ + OutputInterface *output = new OutputInterface(this, parent); + connect(output, &QObject::destroyed, this, [this,output] { m_outputs.removeAll(output); }); + connect(this, &Display::aboutToTerminate, output, [this,output] { removeOutput(output); }); + m_outputs << output; + return output; +} + +void Display::removeOutput(OutputInterface *output) +{ + m_outputs.removeAll(output); + delete output; +} + +} +} diff --git a/wayland_server/display.h b/wayland_server/display.h new file mode 100644 index 0000000000..c63dbe2a18 --- /dev/null +++ b/wayland_server/display.h @@ -0,0 +1,85 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2014 Martin Gräßlin + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#ifndef KWIN_WAYLAND_SERVER_DISPLAY_H +#define KWIN_WAYLAND_SERVER_DISPLAY_H + +#include +#include + +struct wl_display; +struct wl_event_loop; + +namespace KWin +{ +namespace WaylandServer +{ + +class OutputInterface; + +class Display : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString socketName READ socketName WRITE setSocketName NOTIFY socketNameChanged) + Q_PROPERTY(bool running READ isRunning NOTIFY runningChanged) +public: + explicit Display(QObject *parent = nullptr); + virtual ~Display(); + + void setSocketName(const QString &name); + QString socketName() const; + + void start(); + void terminate(); + + operator wl_display*() { + return m_display; + } + operator wl_display*() const { + return m_display; + } + bool isRunning() const { + return m_running; + } + + OutputInterface *createOutput(QObject *parent = nullptr); + void removeOutput(OutputInterface *output); + const QList outputs() const { + return m_outputs; + } + +Q_SIGNALS: + void socketNameChanged(const QString&); + void runningChanged(bool); + void aboutToTerminate(); + +private: + void flush(); + void setRunning(bool running); + wl_display *m_display; + wl_event_loop *m_loop; + QString m_socketName; + bool m_running; + QList m_outputs; +}; + +} +} + +#endif diff --git a/wayland_server/output_interface.cpp b/wayland_server/output_interface.cpp new file mode 100644 index 0000000000..4614503e6c --- /dev/null +++ b/wayland_server/output_interface.cpp @@ -0,0 +1,371 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2014 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 "output_interface.h" +#include "display.h" + +#include + +namespace KWin +{ +namespace WaylandServer +{ + +static const quint32 s_version = 2; + +OutputInterface::OutputInterface(Display *display, QObject *parent) + : QObject(parent) + , m_display(display) + , m_output(nullptr) + , m_physicalSize(QSize()) + , m_globalPosition(QPoint()) + , m_manufacturer(QStringLiteral("org.kde.kwin")) + , m_model(QStringLiteral("none")) + , m_scale(1) + , m_subPixel(SubPixel::Unknown) + , m_transform(Transform::Normal) +{ + connect(this, &OutputInterface::currentModeChanged, this, + [this] { + auto currentModeIt = std::find_if(m_modes.constBegin(), m_modes.constEnd(), [](const Mode &mode) { return mode.flags.testFlag(ModeFlag::Current); }); + if (currentModeIt == m_modes.constEnd()) { + return; + } + for (auto it = m_resources.constBegin(); it != m_resources.constEnd(); ++it) { + sendMode((*it).resource, *currentModeIt); + sendDone(*it); + } + } + ); + connect(this, &OutputInterface::subPixelChanged, this, &OutputInterface::updateGeometry); + connect(this, &OutputInterface::transformChanged, this, &OutputInterface::updateGeometry); + connect(this, &OutputInterface::globalPositionChanged, this, &OutputInterface::updateGeometry); + connect(this, &OutputInterface::modelChanged, this, &OutputInterface::updateGeometry); + connect(this, &OutputInterface::manufacturerChanged, this, &OutputInterface::updateGeometry); + connect(this, &OutputInterface::scaleChanged, this, &OutputInterface::updateScale); +} + +OutputInterface::~OutputInterface() +{ + destroy(); +} + +void OutputInterface::create() +{ + Q_ASSERT(!m_output); + m_output = wl_global_create(*m_display, &wl_output_interface, s_version, this, OutputInterface::bind); +} + +void OutputInterface::destroy() +{ + if (!m_output) { + return; + } + wl_global_destroy(m_output); + m_output = nullptr; +} + +QSize OutputInterface::pixelSize() const +{ + auto it = std::find_if(m_modes.begin(), m_modes.end(), + [](const Mode &mode) { + return mode.flags.testFlag(ModeFlag::Current); + } + ); + if (it == m_modes.end()) { + return QSize(); + } + return (*it).size; +} + +int OutputInterface::refreshRate() const +{ + auto it = std::find_if(m_modes.begin(), m_modes.end(), + [](const Mode &mode) { + return mode.flags.testFlag(ModeFlag::Current); + } + ); + if (it == m_modes.end()) { + return 60000; + } + return (*it).refreshRate; +} + +void OutputInterface::addMode(const QSize &size, OutputInterface::ModeFlags flags, int refreshRate) +{ + Q_ASSERT(!isValid()); + + auto currentModeIt = std::find_if(m_modes.begin(), m_modes.end(), + [](const Mode &mode) { + return mode.flags.testFlag(ModeFlag::Current); + } + ); + if (currentModeIt == m_modes.end() && !flags.testFlag(ModeFlag::Current)) { + // no mode with current flag - enforce + flags |= ModeFlag::Current; + } + if (currentModeIt != m_modes.end() && flags.testFlag(ModeFlag::Current)) { + // another mode has the current flag - remove + (*currentModeIt).flags &= ~uint(ModeFlag::Current); + } + + if (flags.testFlag(ModeFlag::Preferred)) { + // remove from existing Preferred mode + auto preferredIt = std::find_if(m_modes.begin(), m_modes.end(), + [](const Mode &mode) { + return mode.flags.testFlag(ModeFlag::Preferred); + } + ); + if (preferredIt != m_modes.end()) { + (*preferredIt).flags &= ~uint(ModeFlag::Preferred); + } + } + + auto existingModeIt = std::find_if(m_modes.begin(), m_modes.end(), + [size,refreshRate](const Mode &mode) { + return mode.size == size && mode.refreshRate == refreshRate; + } + ); + auto emitChanges = [this,flags,size,refreshRate] { + emit modesChanged(); + if (flags.testFlag(ModeFlag::Current)) { + emit refreshRateChanged(refreshRate); + emit pixelSizeChanged(size); + emit currentModeChanged(); + } + }; + if (existingModeIt != m_modes.end()) { + if ((*existingModeIt).flags == flags) { + // nothing to do + return; + } + (*existingModeIt).flags = flags; + emitChanges(); + return; + } + Mode mode; + mode.size = size; + mode.refreshRate = refreshRate; + mode.flags = flags; + m_modes << mode; + emitChanges(); +} + +void OutputInterface::setCurrentMode(const QSize &size, int refreshRate) +{ + auto currentModeIt = std::find_if(m_modes.begin(), m_modes.end(), + [](const Mode &mode) { + return mode.flags.testFlag(ModeFlag::Current); + } + ); + if (currentModeIt != m_modes.end()) { + // another mode has the current flag - remove + (*currentModeIt).flags &= ~uint(ModeFlag::Current); + } + + auto existingModeIt = std::find_if(m_modes.begin(), m_modes.end(), + [size,refreshRate](const Mode &mode) { + return mode.size == size && mode.refreshRate == refreshRate; + } + ); + + Q_ASSERT(existingModeIt != m_modes.end()); + (*existingModeIt).flags |= ModeFlag::Current; + emit modesChanged(); + emit refreshRateChanged((*existingModeIt).refreshRate); + emit pixelSizeChanged((*existingModeIt).size); + emit currentModeChanged(); +} + +void OutputInterface::bind(wl_client *client, void *data, uint32_t version, uint32_t id) +{ + OutputInterface *output = reinterpret_cast(data); + output->bind(client, version, id); +} + +int32_t OutputInterface::toTransform() const +{ + switch (m_transform) { + case Transform::Normal: + return WL_OUTPUT_TRANSFORM_NORMAL; + case Transform::Rotated90: + return WL_OUTPUT_TRANSFORM_90; + case Transform::Rotated180: + return WL_OUTPUT_TRANSFORM_180; + case Transform::Rotated270: + return WL_OUTPUT_TRANSFORM_270; + case Transform::Flipped: + return WL_OUTPUT_TRANSFORM_FLIPPED; + case Transform::Flipped90: + return WL_OUTPUT_TRANSFORM_FLIPPED_90; + case Transform::Flipped180: + return WL_OUTPUT_TRANSFORM_FLIPPED_180; + case Transform::Flipped270: + return WL_OUTPUT_TRANSFORM_FLIPPED_270; + } + abort(); +} + +int32_t OutputInterface::toSubPixel() const +{ + switch (m_subPixel) { + case SubPixel::Unknown: + return WL_OUTPUT_SUBPIXEL_UNKNOWN; + case SubPixel::None: + return WL_OUTPUT_SUBPIXEL_NONE; + case SubPixel::HorizontalRGB: + return WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB; + case SubPixel::HorizontalBGR: + return WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR; + case SubPixel::VerticalRGB: + return WL_OUTPUT_SUBPIXEL_VERTICAL_RGB; + case SubPixel::VerticalBGR: + return WL_OUTPUT_SUBPIXEL_VERTICAL_BGR; + } + abort(); +} + +void OutputInterface::bind(wl_client *client, uint32_t version, uint32_t id) +{ + wl_resource *resource = wl_resource_create(client, &wl_output_interface, qMin(version, s_version), id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_user_data(resource, this); + wl_resource_set_destructor(resource, OutputInterface::unbind); + ResourceData r; + r.resource = resource; + r.version = version; + m_resources << r; + + sendGeometry(resource); + sendScale(r); + + auto currentModeIt = m_modes.constEnd(); + for (auto it = m_modes.constBegin(); it != m_modes.constEnd(); ++it) { + const Mode &mode = *it; + if (mode.flags.testFlag(ModeFlag::Current)) { + // needs to be sent as last mode + currentModeIt = it; + continue; + } + sendMode(resource, mode); + } + + if (currentModeIt != m_modes.constEnd()) { + sendMode(resource, *currentModeIt); + } + + sendDone(r); +} + +void OutputInterface::unbind(wl_resource *resource) +{ + OutputInterface *o = reinterpret_cast(wl_resource_get_user_data(resource)); + auto it = std::find_if(o->m_resources.begin(), o->m_resources.end(), [resource](const ResourceData &r) { return r.resource == resource; }); + if (it != o->m_resources.end()) { + o->m_resources.erase(it); + } +} + +void OutputInterface::sendMode(wl_resource *resource, const Mode &mode) +{ + int32_t flags = 0; + if (mode.flags.testFlag(ModeFlag::Current)) { + flags |= WL_OUTPUT_MODE_CURRENT; + } + if (mode.flags.testFlag(ModeFlag::Preferred)) { + flags |= WL_OUTPUT_MODE_PREFERRED; + } + wl_output_send_mode(resource, + flags, + mode.size.width(), + mode.size.height(), + mode.refreshRate); + +} + +void OutputInterface::sendGeometry(wl_resource *resource) +{ + wl_output_send_geometry(resource, + m_globalPosition.x(), + m_globalPosition.y(), + m_physicalSize.width(), + m_physicalSize.height(), + toSubPixel(), + qPrintable(m_manufacturer), + qPrintable(m_model), + toTransform()); +} + +void OutputInterface::sendScale(const OutputInterface::ResourceData &data) +{ + if (data.version < 2) { + return; + } + wl_output_send_scale(data.resource, m_scale); +} + +void OutputInterface::sendDone(const OutputInterface::ResourceData &data) +{ + if (data.version < 2) { + return; + } + wl_output_send_done(data.resource); +} + +void OutputInterface::updateGeometry() +{ + for (auto it = m_resources.constBegin(); it != m_resources.constEnd(); ++it) { + sendGeometry((*it).resource); + sendDone(*it); + } +} + +void OutputInterface::updateScale() +{ + for (auto it = m_resources.constBegin(); it != m_resources.constEnd(); ++it) { + sendScale(*it); + sendDone(*it); + } +} + +#define SETTER(setterName, type, argumentName) \ + void OutputInterface::setterName(type arg) \ + { \ + if (m_##argumentName == arg) { \ + return; \ + } \ + m_##argumentName = arg; \ + emit argumentName##Changed(m_##argumentName); \ + } + +SETTER(setPhysicalSize, const QSize&, physicalSize) +SETTER(setGlobalPosition, const QPoint&, globalPosition) +SETTER(setManufacturer, const QString&, manufacturer) +SETTER(setModel, const QString&, model) +SETTER(setScale, int, scale) +SETTER(setSubPixel, SubPixel, subPixel) +SETTER(setTransform, Transform, transform) + +#undef SETTER + +} +} diff --git a/wayland_server/output_interface.h b/wayland_server/output_interface.h new file mode 100644 index 0000000000..b13410de51 --- /dev/null +++ b/wayland_server/output_interface.h @@ -0,0 +1,179 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2014 Martin Gräßlin + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#ifndef KWIN_WAYLAND_SERVER_OUTPUT_INTERFACE_H +#define KWIN_WAYLAND_SERVER_OUTPUT_INTERFACE_H + +#include +#include +#include + +struct wl_global; +struct wl_client; +struct wl_resource; + +namespace KWin +{ +namespace WaylandServer +{ + +class Display; + +class OutputInterface : public QObject +{ + Q_OBJECT + Q_PROPERTY(QSize physicalSize READ physicalSize WRITE setPhysicalSize NOTIFY physicalSizeChanged) + Q_PROPERTY(QPoint globalPosition READ globalPosition WRITE setGlobalPosition NOTIFY globalPositionChanged) + Q_PROPERTY(QString manufacturer READ manufacturer WRITE setManufacturer NOTIFY manufacturerChanged) + Q_PROPERTY(QString model READ model WRITE setModel NOTIFY modelChanged) + Q_PROPERTY(QSize pixelSize READ pixelSize NOTIFY pixelSizeChanged) + Q_PROPERTY(int refreshRate READ refreshRate NOTIFY refreshRateChanged) + Q_PROPERTY(int scale READ scale WRITE setScale NOTIFY scaleChanged) +public: + enum class SubPixel { + Unknown, + None, + HorizontalRGB, + HorizontalBGR, + VerticalRGB, + VerticalBGR + }; + enum class Transform { + Normal, + Rotated90, + Rotated180, + Rotated270, + Flipped, + Flipped90, + Flipped180, + Flipped270 + }; + enum class ModeFlag { + Current = 1, + Preferred = 2 + }; + Q_DECLARE_FLAGS(ModeFlags, ModeFlag) + struct Mode { + QSize size = QSize(); + int refreshRate = 60000; + ModeFlags flags; + }; + virtual ~OutputInterface(); + void create(); + void destroy(); + bool isValid() const { + return m_output != nullptr; + } + + const QSize &physicalSize() const { + return m_physicalSize; + } + const QPoint &globalPosition() const { + return m_globalPosition; + } + const QString &manufacturer() const { + return m_manufacturer; + } + const QString &model() const { + return m_model; + } + QSize pixelSize() const; + int refreshRate() const; + int scale() const { + return m_scale; + } + SubPixel subPixel() const { + return m_subPixel; + } + Transform transform() const { + return m_transform; + } + QList modes() const { + return m_modes; + } + + void setPhysicalSize(const QSize &size); + void setGlobalPosition(const QPoint &pos); + void setManufacturer(const QString &manufacturer); + void setModel(const QString &model); + void setScale(int scale); + void setSubPixel(SubPixel subPixel); + void setTransform(Transform transform); + void addMode(const QSize &size, ModeFlags flags = ModeFlags(), int refreshRate = 60000); + void setCurrentMode(const QSize &size, int refreshRate = 60000); + + operator wl_global*() { + return m_output; + } + operator wl_global*() const { + return m_output; + } + +Q_SIGNALS: + void physicalSizeChanged(const QSize&); + void globalPositionChanged(const QPoint&); + void manufacturerChanged(const QString&); + void modelChanged(const QString&); + void pixelSizeChanged(const QSize&); + void refreshRateChanged(int); + void scaleChanged(int); + void subPixelChanged(SubPixel); + void transformChanged(Transform); + void modesChanged(); + void currentModeChanged(); + +private: + struct ResourceData { + wl_resource *resource; + uint32_t version; + }; + friend class Display; + explicit OutputInterface(Display *display, QObject *parent = nullptr); + static void bind(wl_client *client, void *data, uint32_t version, uint32_t id); + static void unbind(wl_resource *resource); + void bind(wl_client *client, uint32_t version, uint32_t id); + int32_t toTransform() const; + int32_t toSubPixel() const; + void sendMode(wl_resource *resource, const Mode &mode); + void sendDone(const ResourceData &data); + void updateGeometry(); + void sendGeometry(wl_resource *resource); + void sendScale(const ResourceData &data); + void updateScale(); + Display *m_display; + wl_global *m_output; + QSize m_physicalSize; + QPoint m_globalPosition; + QString m_manufacturer; + QString m_model; + int m_scale; + SubPixel m_subPixel; + Transform m_transform; + QList m_modes; + QList m_resources; +}; + +} +} + +Q_DECLARE_OPERATORS_FOR_FLAGS(KWin::WaylandServer::OutputInterface::ModeFlags) +Q_DECLARE_METATYPE(KWin::WaylandServer::OutputInterface::SubPixel) +Q_DECLARE_METATYPE(KWin::WaylandServer::OutputInterface::Transform) + +#endif