diff --git a/src/wayland/CMakeLists.txt b/src/wayland/CMakeLists.txt index 984873631c..65b3078c5d 100644 --- a/src/wayland/CMakeLists.txt +++ b/src/wayland/CMakeLists.txt @@ -1,4 +1,5 @@ set(SERVER_LIB_SRCS + appmenu_interface.cpp buffer_interface.cpp clientconnection.cpp compositor_interface.cpp @@ -162,6 +163,10 @@ ecm_add_wayland_server_protocol(SERVER_LIB_SRCS BASENAME idle-inhibit-unstable-v1 ) +ecm_add_wayland_server_protocol(SERVER_LIB_SRCS + PROTOCOL ${KWayland_SOURCE_DIR}/src/client/protocols/appmenu.xml + BASENAME appmenu +) set(SERVER_GENERATED_SRCS ${CMAKE_CURRENT_BINARY_DIR}/wayland-output-management-client-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-output-management-server-protocol.h @@ -211,7 +216,6 @@ set(SERVER_GENERATED_SRCS set_source_files_properties(${SERVER_GENERATED_SRCS} PROPERTIES SKIP_AUTOMOC ON) - add_library(KF5WaylandServer ${SERVER_LIB_SRCS}) generate_export_header(KF5WaylandServer BASE_NAME @@ -242,6 +246,7 @@ install(TARGETS KF5WaylandServer EXPORT KF5WaylandTargets ${KF5_INSTALL_TARGETS_ set(SERVER_LIB_HEADERS ${CMAKE_CURRENT_BINARY_DIR}/KWayland/Server/kwaylandserver_export.h + appmenu_interface.h blur_interface.h contrast_interface.h buffer_interface.h diff --git a/src/wayland/appmenu_interface.cpp b/src/wayland/appmenu_interface.cpp new file mode 100644 index 0000000000..3745229dc8 --- /dev/null +++ b/src/wayland/appmenu_interface.cpp @@ -0,0 +1,216 @@ +/**************************************************************************** +Copyright 2017 David Edmundson + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) version 3, or any +later version accepted by the membership of KDE e.V. (or its +successor approved by the membership of KDE e.V.), which shall +act as a proxy defined in Section 6 of version 3 of the license. + +This library 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 +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library. If not, see . +****************************************************************************/ +#include "appmenu_interface.h" +#include "display.h" +#include "surface_interface.h" +#include "global_p.h" +#include "resource_p.h" +#include "logging_p.h" + +#include + +#include + +namespace KWayland +{ +namespace Server +{ +class AppMenuManagerInterface::Private : public Global::Private +{ +public: + Private(AppMenuManagerInterface *q, Display *d); + + QVector appmenus; +private: + void bind(wl_client *client, uint32_t version, uint32_t id) override; + + static void unbind(wl_resource *resource); + static Private *cast(wl_resource *r) { + return reinterpret_cast(wl_resource_get_user_data(r)); + } + + static void createCallback(wl_client *client, wl_resource *resource, uint32_t id, wl_resource * surface); + + AppMenuManagerInterface *q; + static const struct org_kde_kwin_appmenu_manager_interface s_interface; + static const quint32 s_version; +}; + +const quint32 AppMenuManagerInterface::Private::s_version = 1; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +const struct org_kde_kwin_appmenu_manager_interface AppMenuManagerInterface::Private::s_interface = { + createCallback +}; +#endif + +void AppMenuManagerInterface::Private::createCallback(wl_client *client, wl_resource *resource, uint32_t id, wl_resource * surface) +{ + auto p = reinterpret_cast(wl_resource_get_user_data(resource)); + Q_ASSERT(p); + + SurfaceInterface *s = SurfaceInterface::get(surface); + if (!s) { + // TODO: send error? + qCWarning(KWAYLAND_SERVER) << "ServerSideDecorationInterface requested for non existing SurfaceInterface"; + return; + } + auto appmenu = new AppMenuInterface(p->q, s, resource); + appmenu->create(p->display->getConnection(client), wl_resource_get_version(resource), id); + if (!appmenu->resource()) { + wl_resource_post_no_memory(resource); + delete appmenu; + return; + } + p->appmenus.append(appmenu); + QObject::connect(appmenu, &QObject::destroyed, p->q, [=]() { + p->appmenus.removeOne(appmenu); + }); + emit p->q->appMenuCreated(appmenu); +} + +AppMenuManagerInterface::Private::Private(AppMenuManagerInterface *q, Display *d) + : Global::Private(d, &org_kde_kwin_appmenu_manager_interface, s_version) + , q(q) +{ +} + +void AppMenuManagerInterface::Private::bind(wl_client *client, uint32_t version, uint32_t id) +{ + auto c = display->getConnection(client); + wl_resource *resource = c->createResource(&org_kde_kwin_appmenu_manager_interface, qMin(version, s_version), id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &s_interface, this, unbind); +} + +void AppMenuManagerInterface::Private::unbind(wl_resource *resource) +{ + Q_UNUSED(resource) +} + +class AppMenuInterface::Private : public Resource::Private +{ +public: + Private(AppMenuInterface *q, AppMenuManagerInterface *c, SurfaceInterface *surface, wl_resource *parentResource); + ~Private(); + + + SurfaceInterface *surface; + InterfaceAddress address; +private: + static void setAddressCallback(wl_client *client, wl_resource *resource, const char * service_name, const char * object_path); + + AppMenuInterface *q_func() { + return reinterpret_cast(q); + } + static AppMenuInterface *get(SurfaceInterface *s); + static const struct org_kde_kwin_appmenu_interface s_interface; +}; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +const struct org_kde_kwin_appmenu_interface AppMenuInterface::Private::s_interface = { + setAddressCallback, + resourceDestroyedCallback +}; +#endif + +void AppMenuInterface::Private::setAddressCallback(wl_client *client, wl_resource *resource, const char * service_name, const char * object_path) +{ + Q_UNUSED(client); + auto p = reinterpret_cast(wl_resource_get_user_data(resource)); + Q_ASSERT(p); + + if (p->address.serviceName == QLatin1String(service_name) && + p->address.objectPath == QLatin1String(object_path)) { + return; + } + p->address.serviceName = QString::fromLatin1(service_name); + p->address.objectPath = QString::fromLatin1(object_path); + emit p->q_func()->addressChanged(p->address); +} + +AppMenuInterface::Private::Private(AppMenuInterface *q, AppMenuManagerInterface *c, SurfaceInterface *s, wl_resource *parentResource) + : Resource::Private(q, c, parentResource, &org_kde_kwin_appmenu_interface, &s_interface), + surface(s) +{ +} + +AppMenuInterface::Private::~Private() +{ + if (resource) { + wl_resource_destroy(resource); + resource = nullptr; + } +} + +AppMenuManagerInterface::AppMenuManagerInterface(Display *display, QObject *parent) + : Global(new Private(this, display), parent) +{ +} + +AppMenuManagerInterface::~AppMenuManagerInterface() +{ +} + +AppMenuManagerInterface::Private *AppMenuManagerInterface::d_func() const +{ + return reinterpret_cast(d.data()); +} + +AppMenuInterface* AppMenuManagerInterface::appMenuForSurface(SurfaceInterface *surface) +{ + Q_D(); + for (AppMenuInterface* menu: d->appmenus) { + if (menu->surface() == surface) { + return menu; + } + } + return nullptr; +} + +AppMenuInterface::AppMenuInterface(AppMenuManagerInterface *parent, SurfaceInterface *s, wl_resource *parentResource): + Resource(new Private(this, parent, s, parentResource)) +{ +} + +AppMenuInterface::Private *AppMenuInterface::d_func() const +{ + return reinterpret_cast(d.data()); +} + +AppMenuInterface::~AppMenuInterface() +{} + +AppMenuInterface::InterfaceAddress AppMenuInterface::address() const { + Q_D(); + return d->address; +} + +SurfaceInterface* AppMenuInterface::surface() const { + Q_D(); + return d->surface; +} + +}//namespace +} + diff --git a/src/wayland/appmenu_interface.h b/src/wayland/appmenu_interface.h new file mode 100644 index 0000000000..9ac57b5480 --- /dev/null +++ b/src/wayland/appmenu_interface.h @@ -0,0 +1,116 @@ +/**************************************************************************** +Copyright 2017 David Edmundson + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) version 3, or any +later version accepted by the membership of KDE e.V. (or its +successor approved by the membership of KDE e.V.), which shall +act as a proxy defined in Section 6 of version 3 of the license. + +This library 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 +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library. If not, see . +****************************************************************************/ +#ifndef KWAYLAND_SERVER_APPMENU_H +#define KWAYLAND_SERVER_APPMENU_H + +#include "global.h" +#include "resource.h" + +#include + +namespace KWayland +{ +namespace Server +{ + +class Display; +class SurfaceInterface; +class AppMenuInterface; + +/** + * Provides the DBus service name and object path to a AppMenu DBus interface. + * + * This global can be used for clients to bind AppmenuInterface instances + * and notifies when a new one is created + * @since 5.XX + */ +class KWAYLANDSERVER_EXPORT AppMenuManagerInterface : public Global +{ + Q_OBJECT +public: + virtual ~AppMenuManagerInterface(); + /** + * Returns any existing appMenu for a given surface + * This returns a null pointer if no AppMenuInterface exists. + */ + AppMenuInterface* appMenuForSurface(SurfaceInterface *); + +Q_SIGNALS: + /** + * Emitted whenever a new AppmenuInterface is created. + **/ + void appMenuCreated(KWayland::Server::AppMenuInterface*); + +private: + explicit AppMenuManagerInterface(Display *display, QObject *parent = nullptr); + friend class Display; + class Private; + Private *d_func() const; +}; + +/** + * Provides the DBus service name and object path to a AppMenu DBus interface. + * This interface is attached to a wl_surface and provides access to where + * the AppMenu DBus interface is registered. + * @since 5.XX + */ +class KWAYLANDSERVER_EXPORT AppMenuInterface : public Resource +{ + Q_OBJECT +public: + /** + * Structure containing DBus service name and path + */ + struct InterfaceAddress { + /** Service name of host with the AppMenu object*/ + QString serviceName; + /** Object path of the AppMenu interface*/ + QString objectPath; + }; + virtual ~AppMenuInterface(); + + /** + * @returns the service name and object path or empty strings if unset + */ + InterfaceAddress address() const; + + /** + * @returns The SurfaceInterface this AppmenuInterface references. + **/ + SurfaceInterface *surface() const; + +Q_SIGNALS: + /** + * Emitted when the address changes or is first received + */ + void addressChanged(KWayland::Server::AppMenuInterface::InterfaceAddress); + +private: + explicit AppMenuInterface(AppMenuManagerInterface *parent, SurfaceInterface *s, wl_resource *parentResource); + friend class AppMenuManagerInterface; + + class Private; + Private *d_func() const; +}; + +} +} + +#endif diff --git a/src/wayland/autotests/client/CMakeLists.txt b/src/wayland/autotests/client/CMakeLists.txt index 5e6d8541a7..c979449b17 100644 --- a/src/wayland/autotests/client/CMakeLists.txt +++ b/src/wayland/autotests/client/CMakeLists.txt @@ -396,3 +396,14 @@ add_executable(testFilter ${testFilter_SRCS}) target_link_libraries( testFilter Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer Wayland::Server) add_test(NAME kwayland-testFilter COMMAND testFilter) ecm_mark_as_test(testFilter) + +######################################################## +# Test Appmenu +######################################################## +set( testAppmenu_SRCS + test_wayland_appmenu.cpp + ) +add_executable(testAppmenu ${testAppmenu_SRCS}) +target_link_libraries( testAppmenu Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer) +add_test(NAME kwayland-testAppmenu COMMAND testAppmenu) +ecm_mark_as_test(testAppmenu) diff --git a/src/wayland/autotests/client/test_wayland_appmenu.cpp b/src/wayland/autotests/client/test_wayland_appmenu.cpp new file mode 100644 index 0000000000..fee3ca3919 --- /dev/null +++ b/src/wayland/autotests/client/test_wayland_appmenu.cpp @@ -0,0 +1,196 @@ +/******************************************************************** +Copyright 2017 David Edmundson +Copyright 2014 Martin Gräßlin + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) version 3, or any +later version accepted by the membership of KDE e.V. (or its +successor approved by the membership of KDE e.V.), which shall +act as a proxy defined in Section 6 of version 3 of the license. + +This library 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 +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library. If not, see . +*********************************************************************/ +// Qt +#include +// KWin +#include "../../src/client/compositor.h" +#include "../../src/client/connection_thread.h" +#include "../../src/client/event_queue.h" +#include "../../src/client/region.h" +#include "../../src/client/registry.h" +#include "../../src/client/surface.h" +#include "../../src/client/appmenu.h" +#include "../../src/server/display.h" +#include "../../src/server/compositor_interface.h" +#include "../../src/server/region_interface.h" +#include "../../src/server/appmenu_interface.h" + +using namespace KWayland::Client; + +Q_DECLARE_METATYPE(KWayland::Server::AppMenuInterface::InterfaceAddress); + +class TestAppmenu : public QObject +{ + Q_OBJECT +public: + explicit TestAppmenu(QObject *parent = nullptr); +private Q_SLOTS: + void init(); + void cleanup(); + + void testCreateAndSet(); + +private: + KWayland::Server::Display *m_display; + KWayland::Server::CompositorInterface *m_compositorInterface; + KWayland::Server::AppMenuManagerInterface *m_appmenuManagerInterface; + KWayland::Client::ConnectionThread *m_connection; + KWayland::Client::Compositor *m_compositor; + KWayland::Client::AppMenuManager *m_appmenuManager; + KWayland::Client::EventQueue *m_queue; + QThread *m_thread; +}; + +static const QString s_socketName = QStringLiteral("kwayland-test-wayland-appmenu-0"); + +TestAppmenu::TestAppmenu(QObject *parent) + : QObject(parent) + , m_display(nullptr) + , m_compositorInterface(nullptr) + , m_connection(nullptr) + , m_compositor(nullptr) + , m_queue(nullptr) + , m_thread(nullptr) +{ +} + +void TestAppmenu::init() +{ + using namespace KWayland::Server; + qRegisterMetaType(); + delete m_display; + m_display = new Display(this); + m_display->setSocketName(s_socketName); + m_display->start(); + QVERIFY(m_display->isRunning()); + + // setup connection + m_connection = new KWayland::Client::ConnectionThread; + QSignalSpy connectedSpy(m_connection, &ConnectionThread::connected); + QVERIFY(connectedSpy.isValid()); + m_connection->setSocketName(s_socketName); + + m_thread = new QThread(this); + m_connection->moveToThread(m_thread); + m_thread->start(); + + m_connection->initConnection(); + QVERIFY(connectedSpy.wait()); + + m_queue = new KWayland::Client::EventQueue(this); + QVERIFY(!m_queue->isValid()); + m_queue->setup(m_connection); + QVERIFY(m_queue->isValid()); + + Registry registry; + QSignalSpy compositorSpy(®istry, &Registry::compositorAnnounced); + QVERIFY(compositorSpy.isValid()); + + QSignalSpy appmenuSpy(®istry, &Registry::appMenuAnnounced); + QVERIFY(appmenuSpy.isValid()); + + QVERIFY(!registry.eventQueue()); + registry.setEventQueue(m_queue); + QCOMPARE(registry.eventQueue(), m_queue); + registry.create(m_connection->display()); + QVERIFY(registry.isValid()); + registry.setup(); + + m_compositorInterface = m_display->createCompositor(m_display); + m_compositorInterface->create(); + QVERIFY(m_compositorInterface->isValid()); + + QVERIFY(compositorSpy.wait()); + m_compositor = registry.createCompositor(compositorSpy.first().first().value(), compositorSpy.first().last().value(), this); + + m_appmenuManagerInterface = m_display->createAppMenuManagerInterface(m_display); + m_appmenuManagerInterface->create(); + QVERIFY(m_appmenuManagerInterface->isValid()); + + QVERIFY(appmenuSpy.wait()); + m_appmenuManager = registry.createAppMenuManager(appmenuSpy.first().first().value(), appmenuSpy.first().last().value(), this); +} + +void TestAppmenu::cleanup() +{ +#define CLEANUP(variable) \ + if (variable) { \ + delete variable; \ + variable = nullptr; \ + } + CLEANUP(m_compositor) + CLEANUP(m_appmenuManager) + CLEANUP(m_queue) + if (m_connection) { + m_connection->deleteLater(); + m_connection = nullptr; + } + if (m_thread) { + m_thread->quit(); + m_thread->wait(); + delete m_thread; + m_thread = nullptr; + } + CLEANUP(m_compositorInterface) + CLEANUP(m_appmenuManagerInterface) + CLEANUP(m_display) +#undef CLEANUP +} + +void TestAppmenu::testCreateAndSet() +{ + QSignalSpy serverSurfaceCreated(m_compositorInterface, SIGNAL(surfaceCreated(KWayland::Server::SurfaceInterface*))); + QVERIFY(serverSurfaceCreated.isValid()); + + QScopedPointer surface(m_compositor->createSurface()); + QVERIFY(serverSurfaceCreated.wait()); + + auto serverSurface = serverSurfaceCreated.first().first().value(); + QSignalSpy appMenuCreated(m_appmenuManagerInterface, &KWayland::Server::AppMenuManagerInterface::appMenuCreated); + + QCOMPARE(m_appmenuManagerInterface->appMenuForSurface(serverSurface), nullptr); + + auto appmenu = m_appmenuManager->create(surface.data(), surface.data()); + QVERIFY(appMenuCreated.wait()); + auto appMenuInterface = appMenuCreated.first().first().value(); + QCOMPARE(m_appmenuManagerInterface->appMenuForSurface(serverSurface), appMenuInterface); + + QCOMPARE(appMenuInterface->address().serviceName, QString()); + QCOMPARE(appMenuInterface->address().objectPath, QString()); + + QSignalSpy appMenuChangedSpy(appMenuInterface, &KWayland::Server::AppMenuInterface::addressChanged); + + appmenu->setAddress("net.somename", "/test/path"); + + QVERIFY(appMenuChangedSpy.wait()); + QCOMPARE(appMenuInterface->address().serviceName, "net.somename"); + QCOMPARE(appMenuInterface->address().objectPath, "/test/path"); + + // and destroy + QSignalSpy destroyedSpy(appMenuInterface, &QObject::destroyed); + QVERIFY(destroyedSpy.isValid()); + delete appmenu; + QVERIFY(destroyedSpy.wait()); + QCOMPARE(m_appmenuManagerInterface->appMenuForSurface(serverSurface), nullptr); +} + +QTEST_GUILESS_MAIN(TestAppmenu) +#include "test_wayland_appmenu.moc" diff --git a/src/wayland/autotests/client/test_wayland_surface.cpp b/src/wayland/autotests/client/test_wayland_surface.cpp index 9cd99bd68b..b19df50b73 100644 --- a/src/wayland/autotests/client/test_wayland_surface.cpp +++ b/src/wayland/autotests/client/test_wayland_surface.cpp @@ -166,6 +166,10 @@ void TestWaylandSurface::cleanup() delete m_compositor; m_compositor = nullptr; } + if (m_idleInhibitManager) { + delete m_idleInhibitManager; + m_idleInhibitManager = nullptr; + } if (m_shm) { delete m_shm; m_shm = nullptr; diff --git a/src/wayland/display.cpp b/src/wayland/display.cpp index f5e16640ed..f0a0bf8efd 100644 --- a/src/wayland/display.cpp +++ b/src/wayland/display.cpp @@ -47,6 +47,7 @@ License along with this library. If not, see . #include "xdgshell_v5_interface_p.h" #include "xdgforeign_interface.h" #include "xdgshell_v6_interface_p.h" +#include "appmenu_interface.h" #include #include @@ -433,6 +434,13 @@ IdleInhibitManagerInterface *Display::createIdleInhibitManager(const IdleInhibit return i; } +AppMenuManagerInterface *Display::createAppMenuManagerInterface(QObject *parent) +{ + auto b = new AppMenuManagerInterface(this, parent); + connect(this, &Display::aboutToTerminate, b, [this, b] { delete b; }); + return b; +} + void Display::createShm() { Q_ASSERT(d->display); diff --git a/src/wayland/display.h b/src/wayland/display.h index d964a0eea0..70fd896ccf 100644 --- a/src/wayland/display.h +++ b/src/wayland/display.h @@ -83,6 +83,7 @@ class PointerGesturesInterface; enum class PointerConstraintsInterfaceVersion; class PointerConstraintsInterface; class XdgForeignInterface; +class AppMenuManagerInterface; /** * @brief Class holding the Wayland server display loop. @@ -238,6 +239,15 @@ public: **/ IdleInhibitManagerInterface *createIdleInhibitManager(const IdleInhibitManagerInterfaceVersion &version, QObject *parent = nullptr); + /** + * Creates the AppMenuManagerInterface in interface @p version. + * + * @returns The created manager object + * @since 5.XX + **/ + AppMenuManagerInterface *createAppMenuManagerInterface(QObject *parent = nullptr); + + /** * Gets the ClientConnection for the given @p client. * If there is no ClientConnection yet for the given @p client, it will be created.