diff --git a/autotests/wayland_client/CMakeLists.txt b/autotests/wayland_client/CMakeLists.txt index d46327e4db..8fbc5e4124 100644 --- a/autotests/wayland_client/CMakeLists.txt +++ b/autotests/wayland_client/CMakeLists.txt @@ -10,6 +10,7 @@ set( testWaylandConnectionThread_SRCS ${KWIN_SOURCE_DIR}/wayland_server/compositor_interface.cpp ${KWIN_SOURCE_DIR}/wayland_server/display.cpp ${KWIN_SOURCE_DIR}/wayland_server/output_interface.cpp + ${KWIN_SOURCE_DIR}/wayland_server/shell_interface.cpp ${KWIN_SOURCE_DIR}/wayland_server/surface_interface.cpp ) add_executable(testWaylandConnectionThread ${testWaylandConnectionThread_SRCS}) @@ -64,6 +65,7 @@ set( testWaylandOutput_SRCS ${KWIN_SOURCE_DIR}/wayland_server/compositor_interface.cpp ${KWIN_SOURCE_DIR}/wayland_server/display.cpp ${KWIN_SOURCE_DIR}/wayland_server/output_interface.cpp + ${KWIN_SOURCE_DIR}/wayland_server/shell_interface.cpp ${KWIN_SOURCE_DIR}/wayland_server/surface_interface.cpp ) add_executable(testWaylandOutput ${testWaylandOutput_SRCS}) @@ -84,10 +86,16 @@ set( testWaylandShell_SRCS ${KWIN_SOURCE_DIR}/wayland_client/shell.cpp ${KWIN_SOURCE_DIR}/wayland_client/surface.cpp ${CMAKE_BINARY_DIR}/wayland_protocols/wayland-client-fullscreen-shell.c + ${KWIN_SOURCE_DIR}/wayland_server/buffer_interface.cpp + ${KWIN_SOURCE_DIR}/wayland_server/compositor_interface.cpp + ${KWIN_SOURCE_DIR}/wayland_server/display.cpp + ${KWIN_SOURCE_DIR}/wayland_server/output_interface.cpp + ${KWIN_SOURCE_DIR}/wayland_server/shell_interface.cpp + ${KWIN_SOURCE_DIR}/wayland_server/surface_interface.cpp ) add_executable(testWaylandShell ${testWaylandShell_SRCS}) add_dependencies(testWaylandShell wayland-client-fullscreen-shell) -target_link_libraries( testWaylandShell Qt5::Test Qt5::Gui Wayland::Client) +target_link_libraries( testWaylandShell Qt5::Test Qt5::Gui Wayland::Client Wayland::Server) add_test(kwin-testWaylandShell testWaylandShell) ecm_mark_as_test(testWaylandShell) @@ -108,6 +116,7 @@ set( testWaylandSurface_SRCS ${KWIN_SOURCE_DIR}/wayland_server/compositor_interface.cpp ${KWIN_SOURCE_DIR}/wayland_server/display.cpp ${KWIN_SOURCE_DIR}/wayland_server/output_interface.cpp + ${KWIN_SOURCE_DIR}/wayland_server/shell_interface.cpp ${KWIN_SOURCE_DIR}/wayland_server/surface_interface.cpp ) add_executable(testWaylandSurface ${testWaylandSurface_SRCS}) diff --git a/autotests/wayland_client/test_wayland_shell.cpp b/autotests/wayland_client/test_wayland_shell.cpp index 7dcb95b198..e35f2b5ec5 100644 --- a/autotests/wayland_client/test_wayland_shell.cpp +++ b/autotests/wayland_client/test_wayland_shell.cpp @@ -25,6 +25,11 @@ along with this program. If not, see . #include "../../wayland_client/shell.h" #include "../../wayland_client/surface.h" #include "../../wayland_client/registry.h" +#include "../../wayland_server/buffer_interface.h" +#include "../../wayland_server/compositor_interface.h" +#include "../../wayland_server/display.h" +#include "../../wayland_server/shell_interface.h" +#include "../../wayland_server/surface_interface.h" // Wayland #include @@ -37,143 +42,252 @@ private Q_SLOTS: void init(); void cleanup(); - void testRegistry(); - void testShell(); - - // TODO: add tests for removal - requires more control over the compositor + void testFullscreen(); + void testPing(); + void testTitle(); + void testWindowClass(); private: - QProcess *m_westonProcess; + KWin::WaylandServer::Display *m_display; + KWin::WaylandServer::CompositorInterface *m_compositorInterface; + KWin::WaylandServer::ShellInterface *m_shellInterface; + KWin::Wayland::ConnectionThread *m_connection; + KWin::Wayland::Compositor *m_compositor; + KWin::Wayland::Shell *m_shell; + QThread *m_thread; }; static const QString s_socketName = QStringLiteral("kwin-test-wayland-shell-0"); TestWaylandShell::TestWaylandShell(QObject *parent) : QObject(parent) - , m_westonProcess(nullptr) + , m_display(nullptr) + , m_compositorInterface(nullptr) + , m_shellInterface(nullptr) + , m_connection(nullptr) + , m_compositor(nullptr) + , m_shell(nullptr) + , m_thread(nullptr) { } void TestWaylandShell::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_compositorInterface = m_display->createCompositor(m_display); + QVERIFY(m_compositorInterface); + m_compositorInterface->create(); + QVERIFY(m_compositorInterface->isValid()); - // 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))); + m_shellInterface = m_display->createShell(m_display); + QVERIFY(m_shellInterface); + m_shellInterface->create(); + QVERIFY(m_shellInterface->isValid()); - // limit to maximum of 10 waits - for (int i = 0; i < 10; ++i) { - QVERIFY(socketSpy.wait()); - if (runtimeDir.exists(s_socketName)) { - delete socketWatcher; - return; + // setup connection + m_connection = new KWin::Wayland::ConnectionThread; + QSignalSpy connectedSpy(m_connection, SIGNAL(connected())); + m_connection->setSocketName(s_socketName); + + m_thread = new QThread(this); + m_connection->moveToThread(m_thread); + m_thread->start(); + + connect(QCoreApplication::eventDispatcher(), &QAbstractEventDispatcher::aboutToBlock, m_connection, + [this]() { + if (m_connection->display()) { + wl_display_flush(m_connection->display()); + } } - } -} + ); -void TestWaylandShell::cleanup() -{ - // terminates weston - m_westonProcess->terminate(); - QVERIFY(m_westonProcess->waitForFinished()); - delete m_westonProcess; - m_westonProcess = nullptr; -} - -void TestWaylandShell::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(shellAnnounced(quint32,quint32))); - registry.create(connection.display()); - QVERIFY(registry.isValid()); - registry.setup(); - wl_display_flush(connection.display()); - QVERIFY(announced.wait()); - - KWin::Wayland::Shell shell; - QVERIFY(!shell.isValid()); - - shell.setup(registry.bindShell(announced.first().first().value(), announced.first().last().value())); - wl_display_flush(connection.display()); - QVERIFY(shell.isValid()); - - shell.release(); - QVERIFY(!shell.isValid()); -} - -void TestWaylandShell::testShell() -{ - 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(); + m_connection->initConnection(); QVERIFY(connectedSpy.wait()); KWin::Wayland::Registry registry; QSignalSpy compositorSpy(®istry, SIGNAL(compositorAnnounced(quint32,quint32))); - QSignalSpy announced(®istry, SIGNAL(shellAnnounced(quint32,quint32))); - registry.create(connection.display()); + QSignalSpy shellSpy(®istry, SIGNAL(shellAnnounced(quint32,quint32))); + registry.create(m_connection->display()); QVERIFY(registry.isValid()); registry.setup(); - wl_display_flush(connection.display()); - QVERIFY(announced.wait()); + QVERIFY(compositorSpy.wait()); - if (compositorSpy.isEmpty()) { - QVERIFY(compositorSpy.wait()); + m_compositor = new KWin::Wayland::Compositor(this); + m_compositor->setup(registry.bindCompositor(compositorSpy.first().first().value(), compositorSpy.first().last().value())); + QVERIFY(m_compositor->isValid()); + + if (shellSpy.isEmpty()) { + QVERIFY(shellSpy.wait()); } - KWin::Wayland::Compositor compositor; - compositor.setup(registry.bindCompositor(compositorSpy.first().first().value(), compositorSpy.first().last().value())); + m_shell = new KWin::Wayland::Shell(this); + m_shell->setup(registry.bindShell(shellSpy.first().first().value(), shellSpy.first().last().value())); + QVERIFY(m_shell->isValid()); +} - KWin::Wayland::Shell shell; - shell.setup(registry.bindShell(announced.first().first().value(), announced.first().last().value())); - wl_display_flush(connection.display()); - QScopedPointer s(compositor.createSurface()); +void TestWaylandShell::cleanup() +{ + if (m_shell) { + delete m_shell; + m_shell = nullptr; + } + if (m_compositor) { + delete m_compositor; + m_compositor = nullptr; + } + if (m_thread) { + m_thread->quit(); + m_thread->wait(); + delete m_thread; + m_thread = nullptr; + } + delete m_connection; + m_connection = nullptr; + + delete m_shellInterface; + m_shellInterface = nullptr; + + delete m_compositorInterface; + m_compositorInterface = nullptr; + + delete m_display; + m_display = nullptr; +} + +void TestWaylandShell::testFullscreen() +{ + using namespace KWin::WaylandServer; + QScopedPointer s(m_compositor->createSurface()); QVERIFY(!s.isNull()); QVERIFY(s->isValid()); - KWin::Wayland::ShellSurface *surface = shell.createSurface(s.data(), &shell); + KWin::Wayland::ShellSurface *surface = m_shell->createSurface(s.data(), m_shell); QSignalSpy sizeSpy(surface, SIGNAL(sizeChanged(QSize))); QVERIFY(sizeSpy.isValid()); QCOMPARE(surface->size(), QSize()); + QSignalSpy serverSurfaceSpy(m_shellInterface, SIGNAL(surfaceCreated(KWin::WaylandServer::ShellSurfaceInterface*))); + QVERIFY(serverSurfaceSpy.isValid()); + QVERIFY(serverSurfaceSpy.wait()); + ShellSurfaceInterface *serverSurface = serverSurfaceSpy.first().first().value(); + QVERIFY(serverSurface); + + QSignalSpy fullscreenSpy(serverSurface, SIGNAL(fullscreenChanged(bool))); + QVERIFY(fullscreenSpy.isValid()); + surface->setFullscreen(); - wl_display_flush(connection.display()); + QVERIFY(fullscreenSpy.wait()); + QCOMPARE(fullscreenSpy.count(), 1); + QVERIFY(fullscreenSpy.first().first().toBool()); + serverSurface->requestSize(QSize(1024, 768)); + QVERIFY(sizeSpy.wait()); QCOMPARE(sizeSpy.count(), 1); QCOMPARE(sizeSpy.first().first().toSize(), QSize(1024, 768)); QCOMPARE(surface->size(), QSize(1024, 768)); - QVERIFY(surface->isValid()); - shell.release(); - QVERIFY(!surface->isValid()); + // set back to toplevel + fullscreenSpy.clear(); + wl_shell_surface_set_toplevel(*surface); + QVERIFY(fullscreenSpy.wait()); + QCOMPARE(fullscreenSpy.count(), 1); + QVERIFY(!fullscreenSpy.first().first().toBool()); +} - compositor.release(); +void TestWaylandShell::testPing() +{ + using namespace KWin::WaylandServer; + QScopedPointer s(m_compositor->createSurface()); + QVERIFY(!s.isNull()); + QVERIFY(s->isValid()); + KWin::Wayland::ShellSurface *surface = m_shell->createSurface(s.data(), m_shell); + QSignalSpy pingSpy(surface, SIGNAL(pinged())); + + QSignalSpy serverSurfaceSpy(m_shellInterface, SIGNAL(surfaceCreated(KWin::WaylandServer::ShellSurfaceInterface*))); + QVERIFY(serverSurfaceSpy.isValid()); + QVERIFY(serverSurfaceSpy.wait()); + ShellSurfaceInterface *serverSurface = serverSurfaceSpy.first().first().value(); + QVERIFY(serverSurface); + + QSignalSpy pingTimeoutSpy(serverSurface, SIGNAL(pingTimeout())); + QVERIFY(pingTimeoutSpy.isValid()); + QSignalSpy pongSpy(serverSurface, SIGNAL(pongReceived())); + QVERIFY(pongSpy.isValid()); + + serverSurface->ping(); + QVERIFY(pingSpy.wait()); + wl_display_flush(m_connection->display()); + + if (pongSpy.isEmpty()) { + QVERIFY(pongSpy.wait()); + } + QVERIFY(!pongSpy.isEmpty()); + QVERIFY(pingTimeoutSpy.isEmpty()); + + // evil trick - timeout of zero will make it not get the pong + serverSurface->setPingTimeout(0); + pongSpy.clear(); + pingTimeoutSpy.clear(); + serverSurface->ping(); + if (pingTimeoutSpy.isEmpty()) { + QVERIFY(pingTimeoutSpy.wait()); + } + QCOMPARE(pingTimeoutSpy.count(), 1); + QVERIFY(pongSpy.isEmpty()); +} + +void TestWaylandShell::testTitle() +{ + using namespace KWin::WaylandServer; + QScopedPointer s(m_compositor->createSurface()); + QVERIFY(!s.isNull()); + QVERIFY(s->isValid()); + KWin::Wayland::ShellSurface *surface = m_shell->createSurface(s.data(), m_shell); + + QSignalSpy serverSurfaceSpy(m_shellInterface, SIGNAL(surfaceCreated(KWin::WaylandServer::ShellSurfaceInterface*))); + QVERIFY(serverSurfaceSpy.isValid()); + QVERIFY(serverSurfaceSpy.wait()); + ShellSurfaceInterface *serverSurface = serverSurfaceSpy.first().first().value(); + QVERIFY(serverSurface); + + QSignalSpy titleSpy(serverSurface, SIGNAL(titleChanged(QString))); + QVERIFY(titleSpy.isValid()); + QString testTitle = QStringLiteral("fooBar"); + QVERIFY(serverSurface->title().isNull()); + + wl_shell_surface_set_title(*surface, testTitle.toUtf8().constData()); + QVERIFY(titleSpy.wait()); + QCOMPARE(serverSurface->title(), testTitle); + QCOMPARE(titleSpy.first().first().toString(), testTitle); +} + +void TestWaylandShell::testWindowClass() +{ + using namespace KWin::WaylandServer; + QScopedPointer s(m_compositor->createSurface()); + QVERIFY(!s.isNull()); + QVERIFY(s->isValid()); + KWin::Wayland::ShellSurface *surface = m_shell->createSurface(s.data(), m_shell); + + QSignalSpy serverSurfaceSpy(m_shellInterface, SIGNAL(surfaceCreated(KWin::WaylandServer::ShellSurfaceInterface*))); + QVERIFY(serverSurfaceSpy.isValid()); + QVERIFY(serverSurfaceSpy.wait()); + ShellSurfaceInterface *serverSurface = serverSurfaceSpy.first().first().value(); + QVERIFY(serverSurface); + + QSignalSpy windowClassSpy(serverSurface, SIGNAL(windowClassChanged(QByteArray))); + QVERIFY(windowClassSpy.isValid()); + QByteArray testClass = QByteArrayLiteral("fooBar"); + QVERIFY(serverSurface->windowClass().isNull()); + + wl_shell_surface_set_class(*surface, testClass.constData()); + QVERIFY(windowClassSpy.wait()); + QCOMPARE(serverSurface->windowClass(), testClass); + QCOMPARE(windowClassSpy.first().first().toByteArray(), testClass); } QTEST_MAIN(TestWaylandShell) diff --git a/autotests/wayland_server/CMakeLists.txt b/autotests/wayland_server/CMakeLists.txt index 4589b27449..2a8d0c5737 100644 --- a/autotests/wayland_server/CMakeLists.txt +++ b/autotests/wayland_server/CMakeLists.txt @@ -7,6 +7,7 @@ set( testWaylandServerDisplay_SRCS ${KWIN_SOURCE_DIR}/wayland_server/compositor_interface.cpp ${KWIN_SOURCE_DIR}/wayland_server/display.cpp ${KWIN_SOURCE_DIR}/wayland_server/output_interface.cpp + ${KWIN_SOURCE_DIR}/wayland_server/shell_interface.cpp ${KWIN_SOURCE_DIR}/wayland_server/surface_interface.cpp ) add_executable(testWaylandServerDisplay ${testWaylandServerDisplay_SRCS}) diff --git a/wayland_server/display.cpp b/wayland_server/display.cpp index 6778128704..7a0bf161e7 100644 --- a/wayland_server/display.cpp +++ b/wayland_server/display.cpp @@ -20,6 +20,7 @@ along with this program. If not, see . #include "display.h" #include "compositor_interface.h" #include "output_interface.h" +#include "shell_interface.h" #include #include @@ -131,6 +132,13 @@ CompositorInterface *Display::createCompositor(QObject *parent) return compositor; } +ShellInterface *Display::createShell(QObject *parent) +{ + ShellInterface *shell = new ShellInterface(this, parent); + connect(this, &Display::aboutToTerminate, shell, [this,shell] { delete shell; }); + return shell; +} + void Display::createShm() { Q_ASSERT(m_running); @@ -143,5 +151,15 @@ void Display::removeOutput(OutputInterface *output) delete output; } +quint32 Display::nextSerial() +{ + return wl_display_next_serial(m_display); +} + +quint32 Display::serial() +{ + return wl_display_get_serial(m_display); +} + } } diff --git a/wayland_server/display.h b/wayland_server/display.h index dc9633c7d0..961455ef0c 100644 --- a/wayland_server/display.h +++ b/wayland_server/display.h @@ -33,6 +33,7 @@ namespace WaylandServer class CompositorInterface; class OutputInterface; +class ShellInterface; class Display : public QObject { @@ -46,6 +47,9 @@ public: void setSocketName(const QString &name); QString socketName() const; + quint32 serial(); + quint32 nextSerial(); + void start(); void terminate(); @@ -67,6 +71,7 @@ public: CompositorInterface *createCompositor(QObject *parent = nullptr); void createShm(); + ShellInterface *createShell(QObject *parent = nullptr); Q_SIGNALS: void socketNameChanged(const QString&); diff --git a/wayland_server/shell_interface.cpp b/wayland_server/shell_interface.cpp new file mode 100644 index 0000000000..b2dc5e790c --- /dev/null +++ b/wayland_server/shell_interface.cpp @@ -0,0 +1,356 @@ +/******************************************************************** + 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 "shell_interface.h" +#include "display.h" +#include "surface_interface.h" + +#include + +namespace KWin +{ +namespace WaylandServer +{ + +static const quint32 s_version = 1; + +const struct wl_shell_interface ShellInterface::s_interface = { + ShellInterface::createSurfaceCallback +}; + +ShellInterface::ShellInterface(Display *display, QObject *parent) + : QObject(parent) + , m_display(display) + , m_shell(nullptr) +{ +} + +ShellInterface::~ShellInterface() +{ + destroy(); +} + +void ShellInterface::create() +{ + Q_ASSERT(!m_shell); + m_shell = wl_global_create(*m_display, &wl_shell_interface, s_version, this, &ShellInterface::bind); +} + +void ShellInterface::destroy() +{ + if (!m_shell) { + return; + } + wl_global_destroy(m_shell); + m_shell = nullptr; +} + +void ShellInterface::bind(wl_client *client, void *data, uint32_t version, uint32_t id) +{ + reinterpret_cast(data)->bind(client, version, id); +} + +void ShellInterface::bind(wl_client *client, uint32_t version, uint32_t id) +{ + wl_resource *shell = wl_resource_create(client, &wl_shell_interface, qMin(version, s_version), id); + if (!shell) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(shell, &ShellInterface::s_interface, this, nullptr); +} + +void ShellInterface::createSurfaceCallback(wl_client *client, wl_resource *resource, uint32_t id, wl_resource *surface) +{ + ShellInterface *s = reinterpret_cast(wl_resource_get_user_data(resource)); + s->createSurface(client, wl_resource_get_version(resource), id, + reinterpret_cast(wl_resource_get_user_data(surface))); +} + +void ShellInterface::createSurface(wl_client *client, uint32_t version, uint32_t id, SurfaceInterface *surface) +{ + auto it = std::find_if(m_surfaces.constBegin(), m_surfaces.constEnd(), + [surface](ShellSurfaceInterface *s) { + return surface == s->surface(); + } + ); + if (it != m_surfaces.constBegin()) { + wl_resource_post_error(surface->surface(), WL_DISPLAY_ERROR_INVALID_OBJECT, "ShellSurface already created"); + return; + } + ShellSurfaceInterface *shellSurface = new ShellSurfaceInterface(this, surface); + m_surfaces << shellSurface; + connect(shellSurface, &ShellSurfaceInterface::destroyed, this, + [this, shellSurface] { + m_surfaces.removeAll(shellSurface); + } + ); + shellSurface->create(client, version, id); + emit surfaceCreated(shellSurface); +} + +/********************************* + * ShellSurfaceInterface + *********************************/ + +const struct wl_shell_surface_interface ShellSurfaceInterface::s_interface = { + ShellSurfaceInterface::pongCallback, + ShellSurfaceInterface::moveCallback, + ShellSurfaceInterface::resizeCallback, + ShellSurfaceInterface::setToplevelCallback, + ShellSurfaceInterface::setTransientCallback, + ShellSurfaceInterface::setFullscreenCallback, + ShellSurfaceInterface::setPopupCalback, + ShellSurfaceInterface::setMaximizedCallback, + ShellSurfaceInterface::setTitleCallback, + ShellSurfaceInterface::setClassCallback +}; + +ShellSurfaceInterface::ShellSurfaceInterface(ShellInterface *shell, SurfaceInterface *parent) + : QObject(parent) + , m_surface(parent) + , m_shell(shell) + , m_shellSurface(nullptr) + , m_client(nullptr) + , m_clientPid(0) + , m_clientUser(0) + , m_clientGroup(0) + , m_title() + , m_windowClass(QByteArray()) + , m_pingTimer(new QTimer(this)) + , m_fullscreen(false) + , m_toplevel(false) +{ + m_pingTimer->setSingleShot(true); + m_pingTimer->setInterval(1000); + connect(m_pingTimer, &QTimer::timeout, this, &ShellSurfaceInterface::pingTimeout); + connect(this, &ShellSurfaceInterface::fullscreenChanged, this, + [this] (bool fullscreen) { + if (!fullscreen) { + return; + } + setToplevel(false); + } + ); + connect(this, &ShellSurfaceInterface::toplevelChanged, this, + [this] (bool toplevel) { + if (!toplevel) { + return; + } + setFullscreen(false); + } + ); +} + +ShellSurfaceInterface::~ShellSurfaceInterface() +{ + if (m_shellSurface) { + wl_resource_destroy(m_shellSurface); + } +} + +void ShellSurfaceInterface::create(wl_client *client, quint32 version, quint32 id) +{ + Q_ASSERT(!m_client); + Q_ASSERT(!m_shellSurface); + m_shellSurface = wl_resource_create(client, &wl_shell_surface_interface, version, id); + if (!m_shellSurface) { + wl_client_post_no_memory(client); + return; + } + m_client = client; + wl_client_get_credentials(m_client, &m_clientPid, &m_clientUser, &m_clientGroup); + + wl_resource_set_implementation(m_shellSurface, &ShellSurfaceInterface::s_interface, this, ShellSurfaceInterface::unbind); +} + +void ShellSurfaceInterface::unbind(wl_resource *r) +{ + ShellSurfaceInterface *s = cast(r); + s->m_shellSurface = nullptr; + s->deleteLater(); +} + +void ShellSurfaceInterface::pongCallback(wl_client *client, wl_resource *resource, uint32_t serial) +{ + ShellSurfaceInterface *s = cast(resource); + Q_ASSERT(client == s->m_client); + s->pong(serial); +} + +void ShellSurfaceInterface::pong(quint32 serial) +{ + if (m_pingTimer->isActive() && serial == m_pingSerial) { + m_pingTimer->stop(); + emit pongReceived(); + } +} + +void ShellSurfaceInterface::ping() +{ + if (m_pingTimer->isActive()) { + return; + } + m_pingSerial = m_shell->display()->nextSerial(); + wl_shell_surface_send_ping(m_shellSurface, m_pingSerial); + wl_client_flush(m_client); + m_pingTimer->start(); +} + +void ShellSurfaceInterface::setPingTimeout(uint msec) +{ + m_pingTimer->setInterval(msec); +} + +bool ShellSurfaceInterface::isPinged() const +{ + return m_pingTimer->isActive(); +} + +void ShellSurfaceInterface::requestSize(const QSize &size) +{ + // TODO: what about the edges? + wl_shell_surface_send_configure(m_shellSurface, 0, size.width(), size.height()); + wl_client_flush(m_client); +} + +void ShellSurfaceInterface::moveCallback(wl_client *client, wl_resource *resource, wl_resource *seat, uint32_t serial) +{ + Q_UNUSED(seat) + Q_UNUSED(serial) + ShellSurfaceInterface *s = cast(resource); + Q_ASSERT(client == s->m_client); + // TODO: implement +} + +void ShellSurfaceInterface::resizeCallback(wl_client *client, wl_resource *resource, wl_resource *seat, uint32_t serial, uint32_t edges) +{ + Q_UNUSED(seat) + Q_UNUSED(serial) + Q_UNUSED(edges) + ShellSurfaceInterface *s = cast(resource); + Q_ASSERT(client == s->m_client); + // TODO: implement +} + +void ShellSurfaceInterface::setToplevelCallback(wl_client *client, wl_resource *resource) +{ + ShellSurfaceInterface *s = cast(resource); + Q_ASSERT(client == s->m_client); + s->setToplevel(true); +} + +void ShellSurfaceInterface::setToplevel(bool toplevel) +{ + if (m_toplevel == toplevel) { + return; + } + m_toplevel = toplevel; + emit toplevelChanged(m_toplevel); +} + +void ShellSurfaceInterface::setTransientCallback(wl_client *client, wl_resource *resource, wl_resource *parent, + int32_t x, int32_t y, uint32_t flags) +{ + Q_UNUSED(parent) + Q_UNUSED(x) + Q_UNUSED(y) + Q_UNUSED(flags) + ShellSurfaceInterface *s = cast(resource); + Q_ASSERT(client == s->m_client); + // TODO: implement +} + +void ShellSurfaceInterface::setFullscreenCallback(wl_client *client, wl_resource *resource, uint32_t method, + uint32_t framerate, wl_resource *output) +{ + Q_UNUSED(method) + Q_UNUSED(framerate) + Q_UNUSED(output) + ShellSurfaceInterface *s = cast(resource); + Q_ASSERT(client == s->m_client); + // TODO: add method, framerate and output + s->setFullscreen(true); +} + +void ShellSurfaceInterface::setFullscreen(bool fullscreen) +{ + if (m_fullscreen == fullscreen) { + return; + } + m_fullscreen = fullscreen; + emit fullscreenChanged(m_fullscreen); +} + +void ShellSurfaceInterface::setPopupCalback(wl_client *client, wl_resource *resource, wl_resource *seat, uint32_t serial, + wl_resource *parent, int32_t x, int32_t y, uint32_t flags) +{ + Q_UNUSED(seat) + Q_UNUSED(serial) + Q_UNUSED(parent) + Q_UNUSED(x) + Q_UNUSED(y) + Q_UNUSED(flags) + ShellSurfaceInterface *s = cast(resource); + Q_ASSERT(client == s->m_client); + // TODO: implement +} + +void ShellSurfaceInterface::setMaximizedCallback(wl_client *client, wl_resource *resource, wl_resource *output) +{ + Q_UNUSED(output) + ShellSurfaceInterface *s = cast(resource); + Q_ASSERT(client == s->m_client); + // TODO: implement +} + +void ShellSurfaceInterface::setTitleCallback(wl_client *client, wl_resource *resource, const char *title) +{ + ShellSurfaceInterface *s = cast(resource); + Q_ASSERT(client == s->m_client); + s->setTitle(QString::fromUtf8(title)); +} + +void ShellSurfaceInterface::setTitle(const QString &title) +{ + if (m_title == title) { + return; + } + m_title = title; + emit titleChanged(m_title); +} + +void ShellSurfaceInterface::setClassCallback(wl_client *client, wl_resource *resource, const char *class_) +{ + ShellSurfaceInterface *s = cast(resource); + Q_ASSERT(client == s->m_client); + s->setWindowClass(QByteArray(class_)); +} + +void ShellSurfaceInterface::setWindowClass(const QByteArray &windowClass) +{ + if (m_windowClass == windowClass) { + return; + } + m_windowClass = windowClass; + emit windowClassChanged(m_windowClass); +} + +} +} diff --git a/wayland_server/shell_interface.h b/wayland_server/shell_interface.h new file mode 100644 index 0000000000..82b6221332 --- /dev/null +++ b/wayland_server/shell_interface.h @@ -0,0 +1,181 @@ +/******************************************************************** + 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_SHELL_INTERFACE_H +#define KWIN_WAYLAND_SERVER_SHELL_INTERFACE_H + +#include + +#include + +class QSize; +class QTimer; +struct wl_global; + +namespace KWin +{ +namespace WaylandServer +{ + +class Display; +class SurfaceInterface; +class ShellSurfaceInterface; + +class ShellInterface : public QObject +{ + Q_OBJECT +public: + virtual ~ShellInterface(); + + void create(); + void destroy(); + + bool isValid() const { + return m_shell != nullptr; + } + + Display *display() const { + return m_display; + } + +Q_SIGNALS: + void surfaceCreated(KWin::WaylandServer::ShellSurfaceInterface*); + +private: + friend class Display; + explicit ShellInterface(Display *display, QObject *parent); + static void bind(wl_client *client, void *data, uint32_t version, uint32_t id); + static void createSurfaceCallback(wl_client *client, wl_resource *resource, uint32_t id, wl_resource *surface); + void bind(wl_client *client, uint32_t version, uint32_t id); + void createSurface(wl_client *client, uint32_t version, uint32_t id, SurfaceInterface *surface); + Display *m_display; + wl_global *m_shell; + static const struct wl_shell_interface s_interface; + QList m_surfaces; +}; + +class ShellSurfaceInterface : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString title READ title NOTIFY titleChanged) + Q_PROPERTY(QByteArray windowClass READ windowClass NOTIFY windowClassChanged) + Q_PROPERTY(bool fullscreen READ isFullscreen NOTIFY fullscreenChanged) + Q_PROPERTY(bool toplevel READ isToplevel NOTIFY toplevelChanged) +public: + virtual ~ShellSurfaceInterface(); + + void ping(); + void setPingTimeout(uint msec); + bool isPinged() const; + void requestSize(const QSize &size); + + SurfaceInterface *surface() const { + return m_surface; + } + ShellInterface *shell() const { + return m_shell; + } + wl_resource *shellSurface() const { + return m_shellSurface; + } + + const QString &title() const { + return m_title; + } + const QByteArray &windowClass() const { + return m_windowClass; + } + bool isFullscreen() const { + return m_fullscreen; + } + bool isToplevel() const { + return m_toplevel; + } + + // TODO: keep them here or add a better encapsulation? + pid_t clientPid() const { + return m_clientPid; + } + uid_t clientUser() const { + return m_clientUser; + } + gid_t clientGroup() const { + return m_clientGroup; + } + +Q_SIGNALS: + void titleChanged(const QString&); + void windowClassChanged(const QByteArray&); + void pingTimeout(); + void pongReceived(); + void fullscreenChanged(bool); + void toplevelChanged(bool); + +private: + friend class ShellInterface; + explicit ShellSurfaceInterface(ShellInterface *shell, SurfaceInterface *parent); + void create(wl_client *client, quint32 version, quint32 id); + void setTitle(const QString &title); + void setWindowClass(const QByteArray &windowClass); + void pong(quint32 serial); + void setFullscreen(bool fullscreen); + void setToplevel(bool toplevel); + + static ShellSurfaceInterface *cast(wl_resource *r) { + return reinterpret_cast(wl_resource_get_user_data(r)); + } + + static void unbind(wl_resource *r); + // interface callbacks + static void pongCallback(wl_client *client, wl_resource *resource, uint32_t serial); + static void moveCallback(wl_client *client, wl_resource *resource, wl_resource *seat, uint32_t serial); + static void resizeCallback(wl_client *client, wl_resource *resource, wl_resource *seat, + uint32_t serial, uint32_t edges); + static void setToplevelCallback(wl_client *client, wl_resource *resource); + static void setTransientCallback(wl_client *client, wl_resource *resource, wl_resource *parent, + int32_t x, int32_t y, uint32_t flags); + static void setFullscreenCallback(wl_client *client, wl_resource *resource, uint32_t method, + uint32_t framerate, wl_resource *output); + static void setPopupCalback(wl_client *client, wl_resource *resource, wl_resource *seat, uint32_t serial, + wl_resource *parent, int32_t x, int32_t y, uint32_t flags); + static void setMaximizedCallback(wl_client *client, wl_resource *resource, wl_resource *output); + static void setTitleCallback(wl_client *client, wl_resource *resource, const char *title); + static void setClassCallback(wl_client *client, wl_resource *resource, const char *class_); + + SurfaceInterface *m_surface; + ShellInterface *m_shell; + wl_resource *m_shellSurface; + wl_client *m_client; + pid_t m_clientPid; + uid_t m_clientUser; + gid_t m_clientGroup; + QString m_title; + QByteArray m_windowClass; + QTimer *m_pingTimer; + quint32 m_pingSerial; + bool m_fullscreen; + bool m_toplevel; + + static const struct wl_shell_surface_interface s_interface; +}; + +} +} + +#endif