diff --git a/CMakeLists.txt b/CMakeLists.txt index 8be1ae43e6..3161fe0450 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -429,6 +429,7 @@ if(Wayland_Client_FOUND AND XKB_FOUND) wayland_client/connection_thread.cpp wayland_client/registry.cpp wayland_client/fullscreen_shell.cpp + wayland_client/output.cpp ${CMAKE_BINARY_DIR}/wayland_protocols/wayland-client-fullscreen-shell.c ) if(KWIN_HAVE_EGL AND Wayland_Egl_FOUND) diff --git a/autotests/wayland_client/CMakeLists.txt b/autotests/wayland_client/CMakeLists.txt index c8fff047bf..25e4751b35 100644 --- a/autotests/wayland_client/CMakeLists.txt +++ b/autotests/wayland_client/CMakeLists.txt @@ -39,3 +39,20 @@ add_dependencies(testWaylandFullscreenShell wayland-client-fullscreen-shell) target_link_libraries( testWaylandFullscreenShell Qt5::Test Wayland::Client) add_test(kwin-testWaylandFullscreenShell testWaylandFullscreenShell) ecm_mark_as_test(testWaylandFullscreenShell) + +######################################################## +# Test WaylandOutput +######################################################## +set( testWaylandOutput_SRCS + test_wayland_output.cpp + ${KWIN_SOURCE_DIR}/wayland_client/connection_thread.cpp + ${KWIN_SOURCE_DIR}/wayland_client/registry.cpp + ${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 + ) +add_executable(testWaylandOutput ${testWaylandOutput_SRCS}) +add_dependencies(testWaylandOutput wayland-client-fullscreen-shell) +target_link_libraries( testWaylandOutput Qt5::Test Wayland::Client) +add_test(kwin-testWaylandOutput testWaylandOutput) +ecm_mark_as_test(testWaylandOutput) diff --git a/autotests/wayland_client/test_wayland_output.cpp b/autotests/wayland_client/test_wayland_output.cpp new file mode 100644 index 0000000000..1f9fe959b4 --- /dev/null +++ b/autotests/wayland_client/test_wayland_output.cpp @@ -0,0 +1,149 @@ +/******************************************************************** +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 +// KWin +#include "../../wayland_client/connection_thread.h" +#include "../../wayland_client/output.h" +#include "../../wayland_client/registry.h" +// Wayland +#include + +class TestWaylandOutput : public QObject +{ + Q_OBJECT +public: + explicit TestWaylandOutput(QObject *parent = nullptr); +private Q_SLOTS: + void init(); + void cleanup(); + + void testRegistry(); + + // TODO: add tests for removal - requires more control over the compositor + +private: + QProcess *m_westonProcess; +}; + +static const QString s_socketName = QStringLiteral("kwin-test-wayland-output-0"); + +TestWaylandOutput::TestWaylandOutput(QObject *parent) + : QObject(parent) + , m_westonProcess(nullptr) +{ +} + +void TestWaylandOutput::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"), + QStringLiteral("--width=1024"), + QStringLiteral("--height=768")})); + 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; + } + } +} + +void TestWaylandOutput::cleanup() +{ + // terminates weston + m_westonProcess->terminate(); + QVERIFY(m_westonProcess->waitForFinished()); + delete m_westonProcess; + m_westonProcess = 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()); + + KWin::Wayland::Registry registry; + QSignalSpy announced(®istry, SIGNAL(outputAnnounced(quint32,quint32))); + registry.create(connection.display()); + QVERIFY(registry.isValid()); + registry.setup(); + wl_display_flush(connection.display()); + QVERIFY(announced.wait()); + + KWin::Wayland::Output output; + QVERIFY(!output.isValid()); + QCOMPARE(output.geometry(), QRect()); + QCOMPARE(output.globalPosition(), QPoint()); + QCOMPARE(output.manufacturer(), QString()); + QCOMPARE(output.model(), QString()); + QCOMPARE(output.physicalSize(), QSize()); + QCOMPARE(output.pixelSize(), QSize()); + QCOMPARE(output.refreshRate(), 0); + QCOMPARE(output.scale(), 1); + QCOMPARE(output.subPixel(), KWin::Wayland::Output::SubPixel::Unknown); + QCOMPARE(output.transform(), KWin::Wayland::Output::Transform::Normal); + + QSignalSpy outputChanged(&output, SIGNAL(changed())); + QVERIFY(outputChanged.isValid()); + + output.setup(registry.bindOutput(announced.first().first().value(), announced.first().last().value())); + wl_display_flush(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.model(), QStringLiteral("none")); + // TODO: add test for physicalSize + QCOMPARE(output.pixelSize(), QSize(1024, 768)); + QCOMPARE(output.refreshRate(), 60000); + QCOMPARE(output.scale(), 1); + // for xwayland output it's unknown + QCOMPARE(output.subPixel(), KWin::Wayland::Output::SubPixel::Unknown); + // for xwayland transform is normal + QCOMPARE(output.transform(), KWin::Wayland::Output::Transform::Normal); +} + +QTEST_MAIN(TestWaylandOutput) +#include "test_wayland_output.moc" diff --git a/screens.cpp b/screens.cpp index 150054dfcd..01dbbc20c5 100644 --- a/screens.cpp +++ b/screens.cpp @@ -24,6 +24,7 @@ along with this program. If not, see . #include "workspace.h" #if HAVE_WAYLAND #include "wayland_backend.h" +#include "wayland_client/output.h" #include #endif diff --git a/wayland_backend.cpp b/wayland_backend.cpp index 9321f74bf1..cd94ab2c2a 100644 --- a/wayland_backend.cpp +++ b/wayland_backend.cpp @@ -24,6 +24,7 @@ along with this program. If not, see . #include "input.h" #include "wayland_client/connection_thread.h" #include "wayland_client/fullscreen_shell.h" +#include "wayland_client/output.h" #include "wayland_client/registry.h" // Qt #include @@ -184,48 +185,6 @@ static void bufferRelease(void *data, wl_buffer *wl_buffer) buffer->setReleased(true); } -static void outputHandleGeometry(void *data, wl_output *output, int32_t x, int32_t y, - int32_t physicalWidth, int32_t physicalHeight, int32_t subPixel, - const char *make, const char *model, int32_t transform) -{ - Q_UNUSED(subPixel) - Q_UNUSED(transform) - Output *o = reinterpret_cast(data); - if (o->output() != output) { - return; - } - o->setGlobalPosition(QPoint(x, y)); - o->setManufacturer(make); - o->setModel(model); - o->setPhysicalSize(QSize(physicalWidth, physicalHeight)); - o->emitChanged(); -} - -static void outputHandleMode(void *data, wl_output *output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) -{ - Q_UNUSED(flags) - Output *o = reinterpret_cast(data); - if (o->output() != output) { - return; - } - o->setPixelSize(QSize(width, height)); - o->setRefreshRate(refresh); - o->emitChanged(); -} - -static void outputHandleDone(void *data, wl_output *output) -{ - Q_UNUSED(data) - Q_UNUSED(output) -} - -static void outputHandleScale(void *data, wl_output *output, int32_t scale) -{ - Q_UNUSED(data) - Q_UNUSED(output) - Q_UNUSED(scale) -} - // handlers static const struct wl_shell_surface_listener s_shellSurfaceListener = { handlePing, @@ -257,13 +216,6 @@ static const struct wl_buffer_listener s_bufferListener = { bufferRelease }; -static const struct wl_output_listener s_outputListener = { - outputHandleGeometry, - outputHandleMode, - outputHandleDone, - outputHandleScale -}; - CursorData::CursorData() : m_valid(init()) { @@ -627,59 +579,6 @@ void WaylandSeat::destroyTheme() } } -Output::Output(wl_output *output, QObject *parent) - : QObject(parent) - , m_output(output) - , m_physicalSize() - , m_globalPosition() - , m_manufacturer() - , m_model() - , m_pixelSize() - , m_refreshRate(0) -{ - wl_output_add_listener(m_output, &s_outputListener, this); -} - -Output::~Output() -{ - wl_output_destroy(m_output); -} - -void Output::setGlobalPosition(const QPoint &pos) -{ - m_globalPosition = pos; -} - -void Output::setManufacturer(const QString &manufacturer) -{ - m_manufacturer = manufacturer; -} - -void Output::setModel(const QString &model) -{ - m_model = model; -} - -void Output::setPhysicalSize(const QSize &size) -{ - m_physicalSize = size; -} - -void Output::setPixelSize(const QSize& size) -{ - m_pixelSize = size; -} - -void Output::setRefreshRate(int refreshRate) -{ - m_refreshRate = refreshRate; -} - -void Output::emitChanged() -{ - emit changed(); -} - WaylandBackend *WaylandBackend::s_self = 0; WaylandBackend *WaylandBackend::create(QObject *parent) { @@ -720,7 +619,7 @@ WaylandBackend::WaylandBackend(QObject *parent) ); connect(m_registry, &Registry::outputAnnounced, this, [this](quint32 name) { - addOutput(m_registry->bindOutput(name, 1)); + addOutput(m_registry->bindOutput(name, 2)); } ); connect(m_registry, &Registry::seatAnnounced, this, &WaylandBackend::createSeat); @@ -905,7 +804,8 @@ void WaylandBackend::setShellSurfaceSize(const QSize &size) void WaylandBackend::addOutput(wl_output *o) { - Output *output = new Output(o, this); + Output *output = new Output(this); + output->setup(o); m_outputs.append(output); connect(output, &Output::changed, this, &WaylandBackend::outputsChanged); } diff --git a/wayland_backend.h b/wayland_backend.h index 46cf86b4f1..981c1821d1 100644 --- a/wayland_backend.h +++ b/wayland_backend.h @@ -48,6 +48,7 @@ class WaylandBackend; class WaylandSeat; class ConnectionThread; class FullscreenShell; +class Output; class Registry; class CursorData @@ -163,44 +164,6 @@ private: WaylandBackend *m_backend; }; -class Output : public QObject -{ - Q_OBJECT -public: - Output(wl_output *output, QObject *parent); - virtual ~Output(); - - wl_output *output(); - const QSize &physicalSize() const; - const QPoint &globalPosition() const; - const QString &manufacturer() const; - const QString &model() const; - const QSize &pixelSize() const; - QRect geometry() const; - int refreshRate() const; - - void setPhysicalSize(const QSize &size); - void setGlobalPosition(const QPoint &pos); - void setManufacturer(const QString &manufacturer); - void setModel(const QString &model); - void setPixelSize(const QSize &size); - void setRefreshRate(int refreshRate); - - void emitChanged(); - -Q_SIGNALS: - void changed(); - -private: - wl_output *m_output; - QSize m_physicalSize; - QPoint m_globalPosition; - QString m_manufacturer; - QString m_model; - QSize m_pixelSize; - int m_refreshRate; -}; - /** * @brief Class encapsulating all Wayland data structures needed by the Egl backend. * @@ -301,54 +264,6 @@ wl_shm *ShmPool::shm() return m_shm; } -inline -QRect Output::geometry() const -{ - return QRect(m_globalPosition, m_pixelSize); -} - -inline -const QPoint &Output::globalPosition() const -{ - return m_globalPosition; -} - -inline -const QString &Output::manufacturer() const -{ - return m_manufacturer; -} - -inline -const QString &Output::model() const -{ - return m_model; -} - -inline -wl_output *Output::output() -{ - return m_output; -} - -inline -const QSize &Output::physicalSize() const -{ - return m_physicalSize; -} - -inline -const QSize &Output::pixelSize() const -{ - return m_pixelSize; -} - -inline -int Output::refreshRate() const -{ - return m_refreshRate; -} - inline wl_display *WaylandBackend::display() { diff --git a/wayland_client/output.cpp b/wayland_client/output.cpp new file mode 100644 index 0000000000..2360ac71f6 --- /dev/null +++ b/wayland_client/output.cpp @@ -0,0 +1,201 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2013 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.h" + +#include + +namespace KWin +{ + +namespace Wayland +{ + +Output::Output(QObject *parent) + : QObject(parent) + , m_output(nullptr) + , m_physicalSize() + , m_globalPosition() + , m_manufacturer() + , m_model() + , m_pixelSize() + , m_refreshRate(0) + , m_scale(1) + , m_subPixel(SubPixel::Unknown) + , m_transform(Transform::Normal) +{ +} + +Output::~Output() +{ + if (m_output) { + wl_output_destroy(m_output); + } +} + +wl_output_listener Output::s_outputListener = { + Output::geometryCallback, + Output::modeCallback, + Output::doneCallback, + Output::scaleCallback +}; + +void Output::geometryCallback(void *data, wl_output *output, + int32_t x, int32_t y, + int32_t physicalWidth, int32_t physicalHeight, + int32_t subPixel, const char *make, const char *model, int32_t transform) +{ + Q_UNUSED(transform) + Output *o = reinterpret_cast(data); + Q_ASSERT(o->output() == output); + o->setGlobalPosition(QPoint(x, y)); + o->setManufacturer(make); + o->setModel(model); + o->setPhysicalSize(QSize(physicalWidth, physicalHeight)); + auto toSubPixel = [subPixel]() { + switch (subPixel) { + case WL_OUTPUT_SUBPIXEL_NONE: + return SubPixel::None; + case WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB: + return SubPixel::HorizontalRGB; + case WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR: + return SubPixel::HorizontalBGR; + case WL_OUTPUT_SUBPIXEL_VERTICAL_RGB: + return SubPixel::VerticalRGB; + case WL_OUTPUT_SUBPIXEL_VERTICAL_BGR: + return SubPixel::VerticalBGR; + case WL_OUTPUT_SUBPIXEL_UNKNOWN: + default: + return SubPixel::Unknown; + } + }; + o->setSubPixel(toSubPixel()); + auto toTransform = [transform]() { + switch (transform) { + case WL_OUTPUT_TRANSFORM_90: + return Transform::Rotated90; + case WL_OUTPUT_TRANSFORM_180: + return Transform::Rotated180; + case WL_OUTPUT_TRANSFORM_270: + return Transform::Rotated270; + case WL_OUTPUT_TRANSFORM_FLIPPED: + return Transform::Flipped; + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + return Transform::Flipped90; + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + return Transform::Flipped180; + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + return Transform::Flipped270; + case WL_OUTPUT_TRANSFORM_NORMAL: + default: + return Transform::Normal; + } + }; + o->setTransform(toTransform()); +} + +void Output::modeCallback(void *data, wl_output *output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) +{ + if (!(flags & WL_OUTPUT_MODE_CURRENT)) { + // ignore all non-current modes; + return; + } + Output *o = reinterpret_cast(data); + Q_ASSERT(o->output() == output); + o->setPixelSize(QSize(width, height)); + o->setRefreshRate(refresh); +} + +void Output::scaleCallback(void *data, wl_output *output, int32_t scale) +{ + Output *o = reinterpret_cast(data); + Q_ASSERT(o->output() == output); + o->setScale(scale); +} + +void Output::doneCallback(void *data, wl_output *output) +{ + Output *o = reinterpret_cast(data); + Q_ASSERT(o->output() == output); + o->changed(); +} + +void Output::setup(wl_output *output) +{ + Q_ASSERT(output); + Q_ASSERT(!m_output); + m_output = output; + wl_output_add_listener(m_output, &s_outputListener, this); +} + +void Output::setGlobalPosition(const QPoint &pos) +{ + m_globalPosition = pos; +} + +void Output::setManufacturer(const QString &manufacturer) +{ + m_manufacturer = manufacturer; +} + +void Output::setModel(const QString &model) +{ + m_model = model; +} + +void Output::setPhysicalSize(const QSize &size) +{ + m_physicalSize = size; +} + +void Output::setPixelSize(const QSize& size) +{ + m_pixelSize = size; +} + +void Output::setRefreshRate(int refreshRate) +{ + m_refreshRate = refreshRate; +} + +void Output::setScale(int scale) +{ + m_scale = scale; +} + +QRect Output::geometry() const +{ + if (!m_pixelSize.isValid()) { + return QRect(); + } + return QRect(m_globalPosition, m_pixelSize); +} + +void Output::setSubPixel(Output::SubPixel subPixel) +{ + m_subPixel = subPixel; +} + +void Output::setTransform(Output::Transform transform) +{ + m_transform = transform; +} + +} +} diff --git a/wayland_client/output.h b/wayland_client/output.h new file mode 100644 index 0000000000..905351c429 --- /dev/null +++ b/wayland_client/output.h @@ -0,0 +1,182 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2013 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_OUTPUT_H +#define KWIN_WAYLAND_OUTPUT_H + +#include +#include +#include + +#include + +namespace KWin +{ +namespace Wayland +{ + +class Output : public QObject +{ + Q_OBJECT +public: + enum class SubPixel { + Unknown, + None, + HorizontalRGB, + HorizontalBGR, + VerticalRGB, + VerticalBGR + }; + enum class Transform { + Normal, + Rotated90, + Rotated180, + Rotated270, + Flipped, + Flipped90, + Flipped180, + Flipped270 + }; + explicit Output(QObject *parent = nullptr); + virtual ~Output(); + + void setup(wl_output *output); + + bool isValid() const; + operator wl_output*() { + return m_output; + } + operator wl_output*() const { + return m_output; + } + wl_output *output(); + const QSize &physicalSize() const; + const QPoint &globalPosition() const; + const QString &manufacturer() const; + const QString &model() const; + const QSize &pixelSize() const; + QRect geometry() const; + int refreshRate() const; + int scale() const; + SubPixel subPixel() const; + Transform transform() const; + + static void geometryCallback(void *data, wl_output *output, int32_t x, int32_t y, + int32_t physicalWidth, int32_t physicalHeight, int32_t subPixel, + const char *make, const char *model, int32_t transform); + static void modeCallback(void *data, wl_output *output, uint32_t flags, int32_t width, int32_t height, int32_t refresh); + static void doneCallback(void *data, wl_output *output); + static void scaleCallback(void *data, wl_output *output, int32_t scale); + +Q_SIGNALS: + void changed(); + +private: + void setPhysicalSize(const QSize &size); + void setGlobalPosition(const QPoint &pos); + void setManufacturer(const QString &manufacturer); + void setModel(const QString &model); + void setPixelSize(const QSize &size); + void setRefreshRate(int refreshRate); + void setScale(int scale); + void setSubPixel(SubPixel subPixel); + void setTransform(Transform transform); + static struct wl_output_listener s_outputListener; + wl_output *m_output; + QSize m_physicalSize; + QPoint m_globalPosition; + QString m_manufacturer; + QString m_model; + QSize m_pixelSize; + int m_refreshRate; + int m_scale; + SubPixel m_subPixel; + Transform m_transform; +}; + +inline +const QPoint &Output::globalPosition() const +{ + return m_globalPosition; +} + +inline +const QString &Output::manufacturer() const +{ + return m_manufacturer; +} + +inline +const QString &Output::model() const +{ + return m_model; +} + +inline +wl_output *Output::output() +{ + return m_output; +} + +inline +const QSize &Output::physicalSize() const +{ + return m_physicalSize; +} + +inline +const QSize &Output::pixelSize() const +{ + return m_pixelSize; +} + +inline +int Output::refreshRate() const +{ + return m_refreshRate; +} + +inline +int Output::scale() const +{ + return m_scale; +} + +inline +bool Output::isValid() const +{ + return m_output != nullptr; +} + +inline +Output::SubPixel Output::subPixel() const +{ + return m_subPixel; +} + +inline +Output::Transform Output::transform() const +{ + return m_transform; +} + +} +} + +#endif