diff --git a/autotests/client/CMakeLists.txt b/autotests/client/CMakeLists.txt --- a/autotests/client/CMakeLists.txt +++ b/autotests/client/CMakeLists.txt @@ -396,3 +396,14 @@ 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/autotests/client/test_wayland_appmenu.cpp b/autotests/client/test_wayland_appmenu.cpp new file mode 100644 --- /dev/null +++ b/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/autotests/client/test_wayland_surface.cpp b/autotests/client/test_wayland_surface.cpp --- a/autotests/client/test_wayland_surface.cpp +++ b/autotests/client/test_wayland_surface.cpp @@ -166,6 +166,10 @@ 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/client/CMakeLists.txt b/src/client/CMakeLists.txt --- a/src/client/CMakeLists.txt +++ b/src/client/CMakeLists.txt @@ -6,6 +6,7 @@ include_directories(${Qt5Gui_PRIVATE_INCLUDE_DIRS}) set(CLIENT_LIB_SRCS + appmenu.cpp buffer.cpp blur.cpp compositor.cpp @@ -154,6 +155,10 @@ PROTOCOL ${KWayland_SOURCE_DIR}/src/client/protocols/idle-inhibit-unstable-v1.xml BASENAME idle-inhibit-unstable-v1 ) +ecm_add_wayland_client_protocol(CLIENT_LIB_SRCS + PROTOCOL ${KWayland_SOURCE_DIR}/src/client/protocols/appmenu.xml + BASENAME appmenu +) set(CLIENT_GENERATED_FILES ${CMAKE_CURRENT_BINARY_DIR}/wayland-fullscreen-shell-client-protocol.h @@ -208,6 +213,7 @@ set(CLIENT_LIB_HEADERS ${CMAKE_CURRENT_BINARY_DIR}/KWayland/Client/kwaylandclient_export.h + appmenu.h blur.h buffer.h compositor.h diff --git a/src/client/appmenu.h b/src/client/appmenu.h new file mode 100644 --- /dev/null +++ b/src/client/appmenu.h @@ -0,0 +1,197 @@ +/**************************************************************************** +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_CLIENT_APPMENU_H +#define KWAYLAND_CLIENT_APPMENU_H + +#include + +#include + +struct org_kde_kwin_appmenu_manager; +struct org_kde_kwin_appmenu; + +namespace KWayland +{ +namespace Client +{ + +class EventQueue; +class Surface; +class AppMenu; + +/** + * @short Wrapper for the org_kde_kwin_appmenu_manager interface. + * + * This class provides a convenient wrapper for the org_kde_kwin_appmenu_manager interface. + * + * To use this class one needs to interact with the Registry. There are two + * possible ways to create the AppMenuManager interface: + * @code + * AppMenuManager *c = registry->createAppMenuManager(name, version); + * @endcode + * + * This creates the AppMenuManager and sets it up directly. As an alternative this + * can also be done in a more low level way: + * @code + * AppMenuManager *c = new AppMenuManager; + * c->setup(registry->bindAppMenuManager(name, version)); + * @endcode + * + * The AppMenuManager can be used as a drop-in replacement for any org_kde_kwin_appmenu_manager + * pointer as it provides matching cast operators. + * + * @see Registry + **/ +class KWAYLANDCLIENT_EXPORT AppMenuManager : public QObject +{ + Q_OBJECT +public: + /** + * Creates a new AppMenuManager. + * Note: after constructing the AppMenuManager it is not yet valid and one needs + * to call setup. In order to get a ready to use AppMenuManager prefer using + * Registry::createAppMenuManager. + **/ + explicit AppMenuManager(QObject *parent = nullptr); + virtual ~AppMenuManager(); + + /** + * Setup this AppMenuManager to manage the @p appmenumanager. + * When using Registry::createAppMenuManager there is no need to call this + * method. + **/ + void setup(org_kde_kwin_appmenu_manager *appmenumanager); + /** + * @returns @c true if managing a org_kde_kwin_appmenu_manager. + **/ + bool isValid() const; + /** + * Releases the org_kde_kwin_appmenu_manager interface. + * After the interface has been released the AppMenuManager instance is no + * longer valid and can be setup with another org_kde_kwin_appmenu_manager interface. + **/ + void release(); + /** + * Destroys the data held by this AppMenuManager. + * This method is supposed to be used when the connection to the Wayland + * server goes away. If the connection is not valid anymore, it's not + * possible to call release anymore as that calls into the Wayland + * connection and the call would fail. This method cleans up the data, so + * that the instance can be deleted or set up to a new org_kde_kwin_appmenu_manager interface + * once there is a new connection available. + * + * It is suggested to connect this method to ConnectionThread::connectionDied: + * @code + * connect(connection, &ConnectionThread::connectionDied, appmenumanager, &AppMenuManager::destroy); + * @endcode + * + * @see release + **/ + void destroy(); + + /** + * Sets the @p queue to use for creating objects with this AppMenuManager. + **/ + void setEventQueue(EventQueue *queue); + /** + * @returns The event queue to use for creating objects with this AppMenuManager. + **/ + EventQueue *eventQueue(); + + AppMenu *create(Surface *surface, QObject *parent = nullptr); + + operator org_kde_kwin_appmenu_manager*(); + operator org_kde_kwin_appmenu_manager*() const; + +Q_SIGNALS: + /** + * The corresponding global for this interface on the Registry got removed. + * + * This signal gets only emitted if the AppMenuManager got created by + * Registry::createAppMenuManager + **/ + void removed(); + +private: + class Private; + QScopedPointer d; +}; + +class KWAYLANDCLIENT_EXPORT AppMenu : public QObject +{ + Q_OBJECT +public: + virtual ~AppMenu(); + + /** + * Setup this Appmenu to manage the @p appmenu. + * When using AppMenuManager::createAppmenu there is no need to call this + * method. + **/ + void setup(org_kde_kwin_appmenu *appmenu); + /** + * @returns @c true if managing a org_kde_kwin_appmenu. + **/ + bool isValid() const; + /** + * Releases the org_kde_kwin_appmenu interface. + * After the interface has been released the Appmenu instance is no + * longer valid and can be setup with another org_kde_kwin_appmenu interface. + **/ + void release(); + /** + * Destroys the data held by this Appmenu. + * This method is supposed to be used when the connection to the Wayland + * server goes away. If the connection is not valid anymore, it's not + * possible to call release anymore as that calls into the Wayland + * connection and the call would fail. This method cleans up the data, so + * that the instance can be deleted or set up to a new org_kde_kwin_appmenu interface + * once there is a new connection available. + * + * It is suggested to connect this method to ConnectionThread::connectionDied: + * @code + * connect(connection, &ConnectionThread::connectionDied, appmenu, &Appmenu::destroy); + * @endcode + * + * @see release + **/ + void destroy(); + + /** + * Sets the appmenu address. The DBus object should be registered before making this call + * Strings should be valid DBus formatted names, in latin1. + */ + void setAddress(const QString & serviceName, const QString & objectPath); + + operator org_kde_kwin_appmenu*(); + operator org_kde_kwin_appmenu*() const; + +private: + friend class AppMenuManager; + explicit AppMenu(QObject *parent = nullptr); + class Private; + QScopedPointer d; +}; + + +} +} + +#endif diff --git a/src/client/appmenu.cpp b/src/client/appmenu.cpp new file mode 100644 --- /dev/null +++ b/src/client/appmenu.cpp @@ -0,0 +1,184 @@ +/**************************************************************************** +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.h" +#include "event_queue.h" +#include "surface.h" +#include "wayland_pointer_p.h" + +#include + +namespace KWayland +{ +namespace Client +{ + +class AppMenuManager::Private +{ +public: + Private() = default; + + void setup(org_kde_kwin_appmenu_manager *arg); + + WaylandPointer appmenumanager; + EventQueue *queue = nullptr; +}; + +AppMenuManager::AppMenuManager(QObject *parent) + : QObject(parent) + , d(new Private) +{ +} + +void AppMenuManager::Private::setup(org_kde_kwin_appmenu_manager *arg) +{ + Q_ASSERT(arg); + Q_ASSERT(!appmenumanager); + appmenumanager.setup(arg); +} + +AppMenuManager::~AppMenuManager() +{ + release(); +} + +void AppMenuManager::setup(org_kde_kwin_appmenu_manager *appmenumanager) +{ + d->setup(appmenumanager); +} + +void AppMenuManager::release() +{ + d->appmenumanager.release(); +} + +void AppMenuManager::destroy() +{ + d->appmenumanager.destroy(); +} + +AppMenuManager::operator org_kde_kwin_appmenu_manager*() { + return d->appmenumanager; +} + +AppMenuManager::operator org_kde_kwin_appmenu_manager*() const { + return d->appmenumanager; +} + +bool AppMenuManager::isValid() const +{ + return d->appmenumanager.isValid(); +} + +void AppMenuManager::setEventQueue(EventQueue *queue) +{ + d->queue = queue; +} + +EventQueue *AppMenuManager::eventQueue() +{ + return d->queue; +} + +AppMenu *AppMenuManager::create(Surface *surface, QObject *parent) +{ + Q_ASSERT(isValid()); + auto p = new AppMenu(parent); + auto w = org_kde_kwin_appmenu_manager_create(d->appmenumanager, *surface); + if (d->queue) { + d->queue->addProxy(w); + } + p->setup(w); + return p; +} + +class AppMenu::Private +{ +public: + Private(AppMenu *q); + + void setup(org_kde_kwin_appmenu *arg); + + WaylandPointer appmenu; + +private: + AppMenu *q; +}; + +AppMenu::Private::Private(AppMenu *q) + : q(q) +{ +} + +AppMenu::AppMenu(QObject *parent) + : QObject(parent) + , d(new Private(this)) +{ +} + +void AppMenu::Private::setup(org_kde_kwin_appmenu *arg) +{ + Q_ASSERT(arg); + Q_ASSERT(!appmenu); + appmenu.setup(arg); +} + +AppMenu::~AppMenu() +{ + release(); +} + +void AppMenu::setup(org_kde_kwin_appmenu *appmenu) +{ + d->setup(appmenu); +} + +void AppMenu::release() +{ + d->appmenu.release(); +} + +void AppMenu::destroy() +{ + d->appmenu.destroy(); +} + +AppMenu::operator org_kde_kwin_appmenu*() { + return d->appmenu; +} + +AppMenu::operator org_kde_kwin_appmenu*() const { + return d->appmenu; +} + +bool AppMenu::isValid() const +{ + return d->appmenu.isValid(); +} + +void AppMenu::setAddress(const QString &serviceName, const QString &objectPath) +{ + Q_ASSERT(isValid()); + org_kde_kwin_appmenu_set_address(d->appmenu, serviceName.toLatin1(), objectPath.toLatin1()); +} + + +} +} + diff --git a/src/client/protocols/appmenu.xml b/src/client/protocols/appmenu.xml new file mode 100644 --- /dev/null +++ b/src/client/protocols/appmenu.xml @@ -0,0 +1,47 @@ + + + . + ]]> + + + This interface allows a client to link a window (or wl_surface) to an com.canonical.dbusmenu + interface registered on DBus. + + + + + + + + + The DBus service name and object path where the appmenu interface is present + The object should be registered on the session bus before sending this request. + If not applicable, clients should remove this object. + + + + Set or update the service name and object path. + Strings should be formatted in Latin-1 matching the relevant DBus specifications. + + + + + + + + + diff --git a/src/client/registry.h b/src/client/registry.h --- a/src/client/registry.h +++ b/src/client/registry.h @@ -37,6 +37,7 @@ struct wl_text_input_manager; struct zwp_text_input_manager_v2; struct _wl_fullscreen_shell; +struct org_kde_kwin_appmenu_manager; struct org_kde_kwin_outputmanagement; struct org_kde_kwin_outputdevice; struct org_kde_kwin_fake_input; @@ -63,6 +64,7 @@ namespace Client { +class AppMenuManager; class Compositor; class ConnectionThread; class DataDeviceManager; @@ -163,7 +165,8 @@ XdgExporterUnstableV2, ///< refers to zxdg_exporter_v2, @since 5.40 XdgImporterUnstableV2, ///< refers to zxdg_importer_v2, @since 5.40 XdgShellUnstableV6, ///< Refers to zxdg_shell_v6 (unstable version 6), @since 5.XX - IdleInhibitManagerUnstableV1 ///< Refers to zwp_idle_inhibit_manager_v1 (unstable version 1), @since 5.41 + IdleInhibitManagerUnstableV1, ///< Refers to zwp_idle_inhibit_manager_v1 (unstable version 1), @since 5.41 + AppMenu ///Refers to org_kde_kwin_appmenu @since 5.XXX }; explicit Registry(QObject *parent = nullptr); virtual ~Registry(); @@ -572,6 +575,17 @@ zwp_idle_inhibit_manager_v1 *bindIdleInhibitManagerUnstableV1(uint32_t name, uint32_t version) const; ///@} + /** + * Binds the org_kde_kwin_appmenu_manager with @p name and @p version. + * If the @p name does not exist or is not for the appmenu manager interface, + * @c null will be returned. + * + * Prefer using createAppMenuManager instead. + * @see createAppMenuManager + * @since 5.XX + **/ + org_kde_kwin_appmenu_manager *bindAppMenuManager(uint32_t name, uint32_t version) const; + /** * @name Convenient factory methods for global objects. **/ @@ -1027,6 +1041,23 @@ IdleInhibitManager *createIdleInhibitManager(quint32 name, quint32 version, QObject *parent = nullptr); ///@} + /** + * Creates a AppMenuManager and sets it up to manage the interface identified by + * @p name and @p version. + * + * Note: in case @p name is invalid or isn't for the org_kde_kwin_appmenu_manager interface, + * the returned AppMenuManager will not be valid. Therefore it's recommended to call + * isValid on the created instance. + * + * @param name The name of the org_kde_kwin_appmenu_manager interface to bind + * @param version The version or the org_kde_kwin_appmenu_manager interface to use + * @param parent The parent for AppMenuManager + * + * @returns The created AppMenuManager. + * @since 5.XXX + **/ + AppMenuManager *createAppMenuManager(quint32 name, quint32 version, QObject *parent = nullptr); + /** * cast operator to the low-level Wayland @c wl_registry **/ @@ -1246,7 +1277,16 @@ * @since 5.41 */ void idleInhibitManagerUnstableV1Announced(quint32 name, quint32 version); + + /** + * Emitted whenever a org_kde_kwin_appmenu_manager interface gets announced. + * @param name The name for the announced interface + * @param version The maximum supported version of the announced interface + * @since 5.XXX + */ + void appMenuAnnounced(quint32 name, quint32 version); ///@} + /** * @name Interface removed signals. **/ @@ -1426,6 +1466,13 @@ * @since 5.41 **/ void idleInhibitManagerUnstableV1Removed(quint32 name); + + /** + * Emitted whenever a org_kde_kwin_appmenu_manager gets removed. + * @param name The name of the removed interface + * @since 5.XX + **/ + void appMenuRemoved(quint32 name); ///@} /** * Generic announced signal which gets emitted whenever an interface gets diff --git a/src/client/registry.cpp b/src/client/registry.cpp --- a/src/client/registry.cpp +++ b/src/client/registry.cpp @@ -51,6 +51,7 @@ #include "xdgshell_p.h" #include "wayland_pointer_p.h" #include "xdgforeign_v2.h" +#include "appmenu.h" // Qt #include // wayland @@ -77,6 +78,7 @@ #include #include #include +#include /***** * How to add another interface: @@ -314,6 +316,13 @@ &zwp_idle_inhibit_manager_v1_interface, &Registry::idleInhibitManagerUnstableV1Announced, &Registry::idleInhibitManagerUnstableV1Removed + }}, + {Registry::Interface::AppMenu, { + 1, + QByteArrayLiteral("org_kde_kwin_appmenu_manager"), + &org_kde_kwin_appmenu_manager_interface, + &Registry::appMenuAnnounced, + &Registry::appMenuRemoved }} }; @@ -623,6 +632,7 @@ BIND2(ContrastManager, Contrast, org_kde_kwin_contrast_manager) BIND2(SlideManager, Slide, org_kde_kwin_slide_manager) BIND2(DpmsManager, Dpms, org_kde_kwin_dpms_manager) +BIND2(AppMenuManager, AppMenu, org_kde_kwin_appmenu_manager) #undef BIND #undef BIND2 @@ -672,6 +682,7 @@ CREATE(DpmsManager) CREATE(ServerSideDecorationManager) CREATE2(ShmPool, Shm) +CREATE(AppMenuManager) #undef CREATE #undef CREATE2 diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt --- a/src/server/CMakeLists.txt +++ b/src/server/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 @@ 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_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 @@ 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/server/appmenu_interface.h b/src/server/appmenu_interface.h new file mode 100644 --- /dev/null +++ b/src/server/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/server/appmenu_interface.cpp b/src/server/appmenu_interface.cpp new file mode 100644 --- /dev/null +++ b/src/server/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/server/display.h b/src/server/display.h --- a/src/server/display.h +++ b/src/server/display.h @@ -83,6 +83,7 @@ enum class PointerConstraintsInterfaceVersion; class PointerConstraintsInterface; class XdgForeignInterface; +class AppMenuManagerInterface; /** * @brief Class holding the Wayland server display loop. @@ -238,6 +239,15 @@ **/ 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. diff --git a/src/server/display.cpp b/src/server/display.cpp --- a/src/server/display.cpp +++ b/src/server/display.cpp @@ -47,6 +47,7 @@ #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 @@ 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);