[kwin_wayland] Initial addition of the WaylandServer module
So far this new module contains: * Display * OutputInterface Display manages the server socket and server event loop. In general it's the entry point to any part of the server. OutputInterface is the abstraction for the wl_output interface on server side. An OutputInterface is created through the Display. The auto tests for ConnectionThread and Output are adjusted to use the internal server instead of starting Weston. Especially the Output test could be extended to test much more as we have absolute control over the server now.
This commit is contained in:
parent
81eab11dbb
commit
1e230fda77
12 changed files with 1122 additions and 103 deletions
|
@ -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")
|
||||
|
|
|
@ -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()
|
||||
|
||||
########################################################
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
#include <QtTest/QtTest>
|
||||
// KWin
|
||||
#include "../../wayland_client/connection_thread.h"
|
||||
#include "../../wayland_server/display.h"
|
||||
// Wayland
|
||||
#include <wayland-client-protocol.h>
|
||||
|
||||
|
@ -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<KWin::Wayland::ConnectionThread> 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<KWin::Wayland::ConnectionThread> 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<KWin::Wayland::ConnectionThread> 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);
|
||||
|
||||
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<KWin::Wayland::ConnectionThread> 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());
|
||||
}
|
||||
|
|
|
@ -23,6 +23,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
#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 <wayland-client-protocol.h>
|
||||
|
||||
|
@ -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<quint32>(), announced.first().last().value<quint32>()));
|
||||
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<quint32>(), announced.first().last().value<quint32>()));
|
||||
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<quint32>(), announced.first().last().value<quint32>()));
|
||||
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<KWin::Wayland::Output::SubPixel>("expected");
|
||||
QTest::addColumn<KWin::WaylandServer::OutputInterface::SubPixel>("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<quint32>(), announced.first().last().value<quint32>()));
|
||||
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<KWin::Wayland::Output::Transform>("expected");
|
||||
QTest::addColumn<KWin::WaylandServer::OutputInterface::Transform>("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<quint32>(), announced.first().last().value<quint32>()));
|
||||
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"
|
||||
|
|
12
autotests/wayland_server/CMakeLists.txt
Normal file
12
autotests/wayland_server/CMakeLists.txt
Normal file
|
@ -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)
|
80
autotests/wayland_server/test_display.cpp
Normal file
80
autotests/wayland_server/test_display.cpp
Normal file
|
@ -0,0 +1,80 @@
|
|||
/********************************************************************
|
||||
KWin - the KDE window manager
|
||||
This file is part of the KDE project.
|
||||
|
||||
Copyright (C) 2014 Martin Gräßlin <mgraesslin@kde.org>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************/
|
||||
// Qt
|
||||
#include <QtTest/QtTest>
|
||||
// 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"
|
|
@ -179,4 +179,7 @@ Output::Transform Output::transform() const
|
|||
}
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(KWin::Wayland::Output::SubPixel)
|
||||
Q_DECLARE_METATYPE(KWin::Wayland::Output::Transform)
|
||||
|
||||
#endif
|
||||
|
|
133
wayland_server/display.cpp
Normal file
133
wayland_server/display.cpp
Normal file
|
@ -0,0 +1,133 @@
|
|||
/********************************************************************
|
||||
KWin - the KDE window manager
|
||||
This file is part of the KDE project.
|
||||
|
||||
Copyright (C) 2014 Martin Gräßlin <mgraesslin@kde.org>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************/
|
||||
#include "display.h"
|
||||
#include "output_interface.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QAbstractEventDispatcher>
|
||||
#include <QSocketNotifier>
|
||||
|
||||
#include <wayland-server.h>
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
85
wayland_server/display.h
Normal file
85
wayland_server/display.h
Normal file
|
@ -0,0 +1,85 @@
|
|||
/********************************************************************
|
||||
KWin - the KDE window manager
|
||||
This file is part of the KDE project.
|
||||
|
||||
Copyright (C) 2014 Martin Gräßlin <mgraesslin@kde.org>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************/
|
||||
#ifndef KWIN_WAYLAND_SERVER_DISPLAY_H
|
||||
#define KWIN_WAYLAND_SERVER_DISPLAY_H
|
||||
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
|
||||
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<OutputInterface*> 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<OutputInterface*> m_outputs;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
371
wayland_server/output_interface.cpp
Normal file
371
wayland_server/output_interface.cpp
Normal file
|
@ -0,0 +1,371 @@
|
|||
/********************************************************************
|
||||
KWin - the KDE window manager
|
||||
This file is part of the KDE project.
|
||||
|
||||
Copyright (C) 2014 Martin Gräßlin <mgraesslin@kde.org>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************/
|
||||
#include "output_interface.h"
|
||||
#include "display.h"
|
||||
|
||||
#include <wayland-server.h>
|
||||
|
||||
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<OutputInterface*>(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<OutputInterface*>(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
|
||||
|
||||
}
|
||||
}
|
179
wayland_server/output_interface.h
Normal file
179
wayland_server/output_interface.h
Normal file
|
@ -0,0 +1,179 @@
|
|||
/********************************************************************
|
||||
KWin - the KDE window manager
|
||||
This file is part of the KDE project.
|
||||
|
||||
Copyright (C) 2014 Martin Gräßlin <mgraesslin@kde.org>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************/
|
||||
#ifndef KWIN_WAYLAND_SERVER_OUTPUT_INTERFACE_H
|
||||
#define KWIN_WAYLAND_SERVER_OUTPUT_INTERFACE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QPoint>
|
||||
#include <QSize>
|
||||
|
||||
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<Mode> 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<Mode> m_modes;
|
||||
QList<ResourceData> 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
|
Loading…
Reference in a new issue