diff --git a/autotests/client/CMakeLists.txt b/autotests/client/CMakeLists.txt --- a/autotests/client/CMakeLists.txt +++ b/autotests/client/CMakeLists.txt @@ -429,3 +429,14 @@ target_link_libraries( testRemoteAccess Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer) add_test(NAME kwayland-testRemoteAccess COMMAND testRemoteAccess) ecm_mark_as_test(testRemoteAccess) + +######################################################## +# Test XDG Output +######################################################## +set( testXdgOutput_SRCS + test_xdg_output.cpp + ) +add_executable(testXdgOutput ${testXdgOutput_SRCS}) +target_link_libraries( testXdgOutput Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer Wayland::Client Wayland::Server) +add_test(NAME kwayland-testXdgOutput COMMAND testXdgOutput) +ecm_mark_as_test(testXdgOutput) diff --git a/autotests/client/test_xdg_output.cpp b/autotests/client/test_xdg_output.cpp new file mode 100644 --- /dev/null +++ b/autotests/client/test_xdg_output.cpp @@ -0,0 +1,174 @@ +/******************************************************************** +Copyright 2018 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 . +*********************************************************************/ +// Qt +#include +// KWin +#include "../../src/client/connection_thread.h" +#include "../../src/client/event_queue.h" +#include "../../src/client/dpms.h" +#include "../../src/client/output.h" +#include "../../src/client/xdgoutput.h" +#include "../../src/client/registry.h" +#include "../../src/server/display.h" +#include "../../src/server/dpms_interface.h" +#include "../../src/server/output_interface.h" +#include "../../src/server/xdgoutput_interface.h" + +// Wayland + +class TestXdgOutput : public QObject +{ + Q_OBJECT +public: + explicit TestXdgOutput(QObject *parent = nullptr); +private Q_SLOTS: + void init(); + void cleanup(); + void testChanges(); +private: + KWayland::Server::Display *m_display; + KWayland::Server::OutputInterface *m_serverOutput; + KWayland::Server::XdgOutputManagerInterface *m_serverXdgOutputManager; + KWayland::Server::XdgOutputInterface *m_serverXdgOutput; + KWayland::Client::ConnectionThread *m_connection; + KWayland::Client::EventQueue *m_queue; + QThread *m_thread; +}; + +static const QString s_socketName = QStringLiteral("kwin-test-xdg-output-0"); + +TestXdgOutput::TestXdgOutput(QObject *parent) + : QObject(parent) + , m_display(nullptr) + , m_serverOutput(nullptr) + , m_connection(nullptr) + , m_thread(nullptr) +{ +} + +void TestXdgOutput::init() +{ + using namespace KWayland::Server; + delete m_display; + m_display = new Display(this); + m_display->setSocketName(s_socketName); + m_display->start(); + QVERIFY(m_display->isRunning()); + + m_serverOutput = m_display->createOutput(this); + m_serverOutput->addMode(QSize(1920, 1080), OutputInterface::ModeFlags(OutputInterface::ModeFlag::Preferred)); + m_serverOutput->setCurrentMode(QSize(1920, 1080)); + m_serverOutput->create(); + + m_serverXdgOutputManager = m_display->createXdgOutputManager(this); + m_serverXdgOutputManager->create(); + m_serverXdgOutput = m_serverXdgOutputManager->createXdgOutput(m_serverOutput, this); + m_serverXdgOutput->setLogicalSize(QSize(1280, 720)); //a 1.5 scale factor + m_serverXdgOutput->setLogicalPosition(QPoint(11,12)); //not a sensible value for one monitor, but works for this test + m_serverXdgOutput->done(); + + // setup connection + m_connection = new KWayland::Client::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(); + + 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()); +} + +void TestXdgOutput::cleanup() +{ + if (m_queue) { + delete m_queue; + m_queue = nullptr; + } + if (m_thread) { + m_thread->quit(); + m_thread->wait(); + delete m_thread; + m_thread = nullptr; + } + delete m_connection; + m_connection = nullptr; + + delete m_serverOutput; + m_serverOutput = nullptr; + + delete m_display; + m_display = nullptr; +} + +void TestXdgOutput::testChanges() +{ + // verify the server modes + using namespace KWayland::Server; + using namespace KWayland::Client; + KWayland::Client::Registry registry; + QSignalSpy announced(®istry, SIGNAL(outputAnnounced(quint32,quint32))); + QSignalSpy xdgOutputAnnounced(®istry, SIGNAL(xdgOutputAnnounced(quint32,quint32))); + + registry.setEventQueue(m_queue); + registry.create(m_connection->display()); + QVERIFY(registry.isValid()); + registry.setup(); + QVERIFY(announced.wait()); + if (xdgOutputAnnounced.count() != 1) { + QVERIFY(xdgOutputAnnounced.wait()); + } + + KWayland::Client::Output output; + QSignalSpy outputChanged(&output, SIGNAL(changed())); + + output.setup(registry.bindOutput(announced.first().first().value(), announced.first().last().value())); + QVERIFY(outputChanged.wait()); + + QScopedPointer xdgOutputManager(registry.createXdgOutputManager(xdgOutputAnnounced.first().first().value(), xdgOutputAnnounced.first().last().value(), this)); + + QScopedPointer xdgOutput(xdgOutputManager->getXdgOutput(&output, this)); + QSignalSpy xdgOutputChanged(xdgOutput.data(), SIGNAL(changed())); + + //check details are sent on client bind + QVERIFY(xdgOutputChanged.wait()); + xdgOutputChanged.clear(); + QCOMPARE(xdgOutput->logicalPosition(), QPoint(11,12)); + QCOMPARE(xdgOutput->logicalSize(), QSize(1280,720)); + + //dynamic updates + m_serverXdgOutput->setLogicalPosition(QPoint(1000, 2000)); + m_serverXdgOutput->setLogicalSize(QSize(100,200)); + m_serverXdgOutput->done(); + + QVERIFY(xdgOutputChanged.wait()); + QCOMPARE(xdgOutputChanged.count(), 1); + QCOMPARE(xdgOutput->logicalPosition(), QPoint(1000, 2000)); + QCOMPARE(xdgOutput->logicalSize(), QSize(100,200)); +} + +QTEST_GUILESS_MAIN(TestXdgOutput) +#include "test_xdg_output.moc" diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt --- a/src/client/CMakeLists.txt +++ b/src/client/CMakeLists.txt @@ -57,6 +57,7 @@ xdgforeign_v2.cpp xdgforeign.cpp xdgshell_v6.cpp + xdgoutput.cpp ) ecm_add_wayland_client_protocol(CLIENT_LIB_SRCS @@ -166,6 +167,11 @@ BASENAME server-decoration-palette ) +ecm_add_wayland_client_protocol(CLIENT_LIB_SRCS + PROTOCOL ${KWayland_SOURCE_DIR}/src/client/protocols/xdg-output-unstable-v1.xml + BASENAME xdg-output-unstable-v1 +) + set(CLIENT_GENERATED_FILES ${CMAKE_CURRENT_BINARY_DIR}/wayland-fullscreen-shell-client-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-output-management-client-protocol.h @@ -190,6 +196,7 @@ ${CMAKE_CURRENT_BINARY_DIR}/wayland-pointer-constraints-unstable-v1-client-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-xdg-foreign-unstable-v2-client-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-idle-inhibit-unstable-v1-client-protocol.h + ${CMAKE_CURRENT_BINARY_DIR}/wayland-xdg-output-unstable-v1-client-protocol.h ) set_source_files_properties(${CLIENT_GENERATED_FILES} PROPERTIES SKIP_AUTOMOC ON) @@ -270,6 +277,7 @@ textinput.h xdgshell.h xdgforeign_v2.h + xdgoutput.h ) install(FILES diff --git a/src/client/protocols/xdg-output-unstable-v1.xml b/src/client/protocols/xdg-output-unstable-v1.xml new file mode 100644 --- /dev/null +++ b/src/client/protocols/xdg-output-unstable-v1.xml @@ -0,0 +1,161 @@ + + + + + Copyright © 2017 Red Hat Inc. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + This protocol aims at describing outputs in a way which is more in line + with the concept of an output on desktop oriented systems. + + Some information are more specific to the concept of an output for + a desktop oriented system and may not make sense in other applications, + such as IVI systems for example. + + Typically, the global compositor space on a desktop system is made of + a contiguous or overlapping set of rectangular regions. + + Some of the information provided in this protocol might be identical + to their counterparts already available from wl_output, in which case + the information provided by this protocol should be preferred to their + equivalent in wl_output. The goal is to move the desktop specific + concepts (such as output location within the global compositor space, + the connector name and types, etc.) out of the core wl_output protocol. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible + changes may be added together with the corresponding interface + version bump. + Backward incompatible changes are done by bumping the version + number in the protocol and interface names and resetting the + interface version. Once the protocol is to be declared stable, + the 'z' prefix and the version number in the protocol and + interface names are removed and the interface version number is + reset. + + + + + A global factory interface for xdg_output objects. + + + + + Using this request a client can tell the server that it is not + going to use the xdg_output_manager object anymore. + + Any objects already created through this instance are not affected. + + + + + + This creates a new xdg_output object for the given wl_output. + + + + + + + + + An xdg_output describes part of the compositor geometry. + + This typically corresponds to a monitor that displays part of the + compositor space. + + + + + Using this request a client can tell the server that it is not + going to use the xdg_output object anymore. + + + + + + The position event describes the location of the wl_output within + the global compositor space. + + The logical_position event is sent after creating an xdg_output + (see xdg_output_manager.get_xdg_output) and whenever the location + of the output changes within the global compositor space. + + + + + + + + The logical_size event describes the size of the output in the + global compositor space. + + For example, a surface without any buffer scale, transformation + nor rotation set, with the size matching the logical_size will + have the same size as the corresponding output when displayed. + + Most regular Wayland clients should not pay attention to the + logical size and would rather rely on xdg_shell interfaces. + + Some clients such as Xwayland, however, need this to configure + their surfaces in the global compositor space as the compositor + may apply a different scale from what is advertised by the output + scaling property (to achieve fractional scaling, for example). + + For example, for a wl_output mode 3840×2160 and a scale factor 2: + + - A compositor not scaling the surface buffers will advertise a + logical size of 3840×2160, + + - A compositor automatically scaling the surface buffers will + advertise a logical size of 1920×1080, + + - A compositor using a fractional scale of 1.5 will advertise a + logical size to 2560×1620. + + The logical_size event is sent after creating an xdg_output + (see xdg_output_manager.get_xdg_output) and whenever the logical + size of the output changes, either as a result of a change in the + applied scale or because of a change in the corresponding output + mode(see wl_output.mode) or transform (see wl_output.transform). + + + + + + + + This event is sent after all other properties of an xdg_output + have been sent. + + This allows changes to the xdg_output properties to be seen as + atomic, even if they happen via multiple events. + + + + + diff --git a/src/client/registry.h b/src/client/registry.h --- a/src/client/registry.h +++ b/src/client/registry.h @@ -60,6 +60,7 @@ struct zxdg_exporter_v2; struct zxdg_importer_v2; struct zwp_idle_inhibit_manager_v1; +struct zxdg_output_manager_v1; namespace KWayland { @@ -103,6 +104,7 @@ class XdgImporterUnstableV2; class XdgExporter; class XdgImporter; +class XdgOutputManager; /** * @short Wrapper for the wl_registry interface. @@ -172,7 +174,8 @@ IdleInhibitManagerUnstableV1, ///< Refers to zwp_idle_inhibit_manager_v1 (unstable version 1), @since 5.41 AppMenu, ///Refers to org_kde_kwin_appmenu @since 5.42 ServerSideDecorationPalette, ///Refers to org_kde_kwin_server_decoration_palette_manager @since 5.42 - RemoteAccessManager ///< Refers to org_kde_kwin_remote_access_manager interface, @since 5.45 + RemoteAccessManager, ///< Refers to org_kde_kwin_remote_access_manager interface, @since 5.45 + XdgOutputUnstableV1, ///refers to zxdg_output_v1, @since 5.XDGOUTPUTVERSION }; explicit Registry(QObject *parent = nullptr); virtual ~Registry(); @@ -607,10 +610,22 @@ * @c null will be returned. * * Prefer using createServerSideDecorationPaletteManager instead. - * @see createAppMenuManager + * @see createServerSideDecorationPaletteManager * @since 5.42 **/ org_kde_kwin_server_decoration_palette_manager *bindServerSideDecorationPaletteManager(uint32_t name, uint32_t version) const; + + /** + * Binds the zxdg_output_v1 with @p name and @p version. + * If the @p name does not exist, + * @c null will be returned. + * + * Prefer using createXdgOutputManager instead. + * @see createXdgOutputUnstableV1 + * @since 5.XDGOUTPUTVERSION + **/ + zxdg_output_manager_v1 *bindXdgOutputUnstableV1(uint32_t name, uint32_t version) const; + ///@} /** @@ -1116,6 +1131,24 @@ * @since 5.42 **/ ServerSideDecorationPaletteManager *createServerSideDecorationPaletteManager(quint32 name, quint32 version, QObject *parent = nullptr); + + /** + * Creates an XdgOutputManager 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 zxdg_output_manager_v1 interface, + * the returned XdgOutputManager will not be valid. Therefore it's recommended to call + * isValid on the created instance. + * + * @param name The name of the zxdg_output_manager_v1 interface to bind + * @param version The version or the zxdg_output_manager_v1 interface to use + * @param parent The parent for XdgOuptutManager + * + * @returns The created XdgOuptutManager. + * @since 5.XDGOUTPUTVERSION + **/ + XdgOutputManager *createXdgOutputManager(quint32 name, quint32 version, QObject *parent = nullptr); + ///@} @@ -1362,6 +1395,14 @@ */ void serverSideDecorationPaletteManagerAnnounced(quint32 name, quint32 version); + /** + * Emitted whenever a zxdg_output_v1 interface gets announced. + * @param name The name for the announced interface + * @param version The maximum supported version of the announced interface + * @since 5.XDGOUTPUTVERSION + */ + void xdgOutputAnnounced(quint32 name, quint32 version); + ///@} /** @@ -1564,6 +1605,13 @@ **/ void serverSideDecorationPaletteManagerRemoved(quint32 name); + /** + * Emitted whenever a zxdg_output_v1 gets removed. + * @param name The name of the removed interface + * @since 5.XDGOUTPUTVERSION + **/ + void xdgOutputRemoved(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 @@ -54,6 +54,7 @@ #include "xdgforeign_v2.h" #include "appmenu.h" #include "server_decoration_palette.h" +#include "xdgoutput.h" // Qt #include // wayland @@ -83,6 +84,7 @@ #include #include #include +#include /***** * How to add another interface: @@ -341,6 +343,13 @@ &org_kde_kwin_server_decoration_palette_manager_interface, &Registry::serverSideDecorationPaletteManagerAnnounced, &Registry::serverSideDecorationPaletteManagerRemoved + }}, + {Registry::Interface::XdgOutputUnstableV1, { + 1, + QByteArrayLiteral("zxdg_output_manager_v1"), + &zxdg_output_manager_v1_interface, + &Registry::xdgOutputAnnounced, + &Registry::xdgOutputRemoved }} }; @@ -653,6 +662,7 @@ BIND2(DpmsManager, Dpms, org_kde_kwin_dpms_manager) BIND2(AppMenuManager, AppMenu, org_kde_kwin_appmenu_manager) BIND2(ServerSideDecorationPaletteManager, ServerSideDecorationPalette, org_kde_kwin_server_decoration_palette_manager) +BIND(XdgOutputUnstableV1, zxdg_output_manager_v1) #undef BIND #undef BIND2 @@ -785,6 +795,16 @@ } } +XdgOutputManager *Registry::createXdgOutputManager(quint32 name, quint32 version, QObject *parent) +{ + switch(d->interfaceForName(name)) { + case Interface::XdgOutputUnstableV1: + return d->create(name, version, parent, &Registry::bindXdgOutputUnstableV1); + default: + return nullptr; + } +} + namespace { static const wl_interface *wlInterface(Registry::Interface interface) { diff --git a/src/client/xdgoutput.h b/src/client/xdgoutput.h new file mode 100644 --- /dev/null +++ b/src/client/xdgoutput.h @@ -0,0 +1,229 @@ +/**************************************************************************** +Copyright 2018 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_XDGOUTPUT_H +#define KWAYLAND_CLIENT_XDGOUTPUT_H + +#include +#include +#include + +#include + +struct zxdg_output_manager_v1; +struct zxdg_output_v1; + +namespace KWayland +{ +namespace Client +{ + +class EventQueue; +class XdgOutput; +class Output; + +/** + * @short Wrapper for the zxdg_output_manager_v1 interface. + * + * This class provides a convenient wrapper for the zxdg_output_manager_v1 interface. + * + * This provides the logical size of the output. This is useful in case it doesn't match the + * pixelSize / outputScale. + * + * To use this class one needs to interact with the Registry. There are two + * possible ways to create the XdgOutputManager interface: + * @code + * XdgOutputManager *c = registry->createXdgOutputManager(name, version); + * @endcode + * + * This creates the XdgOutputManager and sets it up directly. As an alternative this + * can also be done in a more low level way: + * @code + * XdgOutputManager *c = new XdgOutputManager; + * c->setup(registry->bindXdgOutputManager(name, version)); + * @endcode + * + * The XdgOutputManager can be used as a drop-in replacement for any zxdg_output_manager_v1 + * pointer as it provides matching cast operators. + * + * @since 5.XDGOUTPUTVERSION + * + * @see Registry + **/ +class KWAYLANDCLIENT_EXPORT XdgOutputManager : public QObject +{ + Q_OBJECT +public: + /** + * Creates a new XdgOutputManager. + * Note: after constructing the XdgOutputManager it is not yet valid and one needs + * to call setup. In order to get a ready to use XdgOutputManager prefer using + * Registry::createXdgOutputManager. + **/ + explicit XdgOutputManager(QObject *parent = nullptr); + virtual ~XdgOutputManager(); + + /** + * Setup this XdgOutputManager to manage the @p xdgoutputmanager. + * When using Registry::createXdgOutputManager there is no need to call this + * method. + **/ + void setup(zxdg_output_manager_v1 *xdgoutputmanager); + /** + * @returns @c true if managing a zxdg_output_manager_v1. + **/ + bool isValid() const; + /** + * Releases the zxdg_output_manager_v1 interface. + * After the interface has been released the XdgOutputManager instance is no + * longer valid and can be setup with another zxdg_output_manager_v1 interface. + **/ + void release(); + /** + * Destroys the data held by this XdgOutputManager. + * 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 zxdg_output_manager_v1 interface + * once there is a new connection available. + * + * It is suggested to connect this method to ConnectionThread::connectionDied: + * @code + * connect(connection, &ConnectionThread::connectionDied, xdgoutputmanager, &XdgOutputManager::destroy); + * @endcode + * + * @see release + **/ + void destroy(); + + /** + * Sets the @p queue to use for creating objects with this XdgOutputManager. + **/ + void setEventQueue(EventQueue *queue); + /** + * @returns The event queue to use for creating objects with this XdgOutputManager. + **/ + EventQueue *eventQueue(); + + XdgOutput *getXdgOutput(Output *output, QObject *parent = nullptr); + + operator zxdg_output_manager_v1*(); + operator zxdg_output_manager_v1*() const; + +Q_SIGNALS: + /** + * The corresponding global for this interface on the Registry got removed. + * + * This signal gets only emitted if the XdgOutputManager got created by + * Registry::createXdgOutputManager + **/ + void removed(); + +private: + class Private; + QScopedPointer d; +}; + +/** + * @short Wrapper for the zxdg_output_v1 interface. + * + * This class provides a convenient wrapper for the zxdg_output_v1 interface. + * + * The XdgOutputManager can be used as a drop-in replacement for any zxdg_output_v1 + * pointer as it provides matching cast operators. + * + * This protocol provides a potentially more correct size and position of the screen + * than Output with respect to scaling. + * + * @see Registry + **/ + +class KWAYLANDCLIENT_EXPORT XdgOutput : public QObject +{ + Q_OBJECT +public: + virtual ~XdgOutput(); + + /** + * Setup this XdgOutput to manage the @p xdgoutput. + * When using XdgOutputManager::createXdgOutput there is no need to call this + * method. + **/ + void setup(zxdg_output_v1 *xdgoutput); + /** + * @returns @c true if managing a zxdg_output_v1. + **/ + bool isValid() const; + /** + * Releases the zxdg_output_v1 interface. + * After the interface has been released the XdgOutput instance is no + * longer valid and can be setup with another zxdg_output_v1 interface. + **/ + void release(); + /** + * Destroys the data held by this XdgOutput. + * 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 zxdg_output_v1 interface + * once there is a new connection available. + * + * It is suggested to connect this method to ConnectionThread::connectionDied: + * @code + * connect(connection, &ConnectionThread::connectionDied, xdgoutput, &XdgOutput::destroy); + * @endcode + * + * @see release + **/ + void destroy(); + + operator zxdg_output_v1*(); + operator zxdg_output_v1*() const; + + /** + * The top left position of the output in compositor co-ordinates + */ + QPoint logicalPosition() const; + + /** + * The size of the output in compositor co-ordinates + * (i.e pixel size / output scale) + */ + QSize logicalSize() const; + +Q_SIGNALS: + /** + * Emitted when the logical position or size changes + */ + void changed(); + +private: + friend class XdgOutputManager; + explicit XdgOutput(QObject *parent = nullptr); + class Private; + QScopedPointer d; +}; + + +} +} + +#endif diff --git a/src/client/xdgoutput.cpp b/src/client/xdgoutput.cpp new file mode 100644 --- /dev/null +++ b/src/client/xdgoutput.cpp @@ -0,0 +1,240 @@ +/**************************************************************************** +Copyright 2018 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 "xdgoutput.h" +#include "event_queue.h" +#include "wayland_pointer_p.h" +#include "output.h" + +#include +#include + +#include + +namespace KWayland +{ +namespace Client +{ + +class XdgOutputManager::Private +{ +public: + Private() = default; + + void setup(zxdg_output_manager_v1 *arg); + + WaylandPointer xdgoutputmanager; + EventQueue *queue = nullptr; +}; + +XdgOutputManager::XdgOutputManager(QObject *parent) + : QObject(parent) + , d(new Private) +{ +} + +void XdgOutputManager::Private::setup(zxdg_output_manager_v1 *arg) +{ + Q_ASSERT(arg); + Q_ASSERT(!xdgoutputmanager); + xdgoutputmanager.setup(arg); +} + +XdgOutputManager::~XdgOutputManager() +{ + release(); +} + +void XdgOutputManager::setup(zxdg_output_manager_v1 *xdgoutputmanager) +{ + d->setup(xdgoutputmanager); +} + +void XdgOutputManager::release() +{ + d->xdgoutputmanager.release(); +} + +void XdgOutputManager::destroy() +{ + d->xdgoutputmanager.destroy(); +} + +XdgOutputManager::operator zxdg_output_manager_v1*() { + return d->xdgoutputmanager; +} + +XdgOutputManager::operator zxdg_output_manager_v1*() const { + return d->xdgoutputmanager; +} + +bool XdgOutputManager::isValid() const +{ + return d->xdgoutputmanager.isValid(); +} + +void XdgOutputManager::setEventQueue(EventQueue *queue) +{ + d->queue = queue; +} + +EventQueue *XdgOutputManager::eventQueue() +{ + return d->queue; +} + +XdgOutput *XdgOutputManager::getXdgOutput(Output *output, QObject *parent) +{ + Q_ASSERT(isValid()); + auto p = new XdgOutput(parent); + auto w = zxdg_output_manager_v1_get_xdg_output(d->xdgoutputmanager, *output); + if (d->queue) { + d->queue->addProxy(w); + } + p->setup(w); + return p; +} + +struct XdgOutputBuffer +{ + QPoint logicalPosition; + QSize logicalSize; +}; + +class XdgOutput::Private +{ +public: + Private(XdgOutput *q); + + void setup(zxdg_output_v1 *arg); + + WaylandPointer xdgoutput; + + XdgOutputBuffer current; + XdgOutputBuffer pending; + +private: + XdgOutput *q; + +private: + static void logical_positionCallback(void *data, zxdg_output_v1 *zxdg_output_v1, int32_t x, int32_t y); + static void logical_sizeCallback(void *data, zxdg_output_v1 *zxdg_output_v1, int32_t width, int32_t height); + static void doneCallback(void *data, zxdg_output_v1 *zxdg_output_v1); + + static const zxdg_output_v1_listener s_listener; +}; + +const zxdg_output_v1_listener XdgOutput::Private::s_listener = { + logical_positionCallback, + logical_sizeCallback, + doneCallback +}; + +void XdgOutput::Private::logical_positionCallback(void *data, zxdg_output_v1 *zxdg_output_v1, int32_t x, int32_t y) +{ + auto p = reinterpret_cast(data); + Q_ASSERT(p->xdgoutput == zxdg_output_v1); + p->pending.logicalPosition = QPoint(x,y); +} + +void XdgOutput::Private::logical_sizeCallback(void *data, zxdg_output_v1 *zxdg_output_v1, int32_t width, int32_t height) +{ + auto p = reinterpret_cast(data); + Q_ASSERT(p->xdgoutput == zxdg_output_v1); + p->pending.logicalSize = QSize(width,height); +} + +void XdgOutput::Private::doneCallback(void *data, zxdg_output_v1 *zxdg_output_v1) +{ + auto p = reinterpret_cast(data); + Q_ASSERT(p->xdgoutput == zxdg_output_v1); + std::swap(p->current, p->pending); + + if (p->current.logicalSize != p->pending.logicalSize && + p->current.logicalPosition != p->pending.logicalPosition) { + emit p->q->changed(); + } +} + +XdgOutput::Private::Private(XdgOutput *q) + : q(q) +{ +} + +XdgOutput::XdgOutput(QObject *parent) + : QObject(parent) + , d(new Private(this)) +{ +} + +void XdgOutput::Private::setup(zxdg_output_v1 *arg) +{ + Q_ASSERT(arg); + Q_ASSERT(!xdgoutput); + xdgoutput.setup(arg); + zxdg_output_v1_add_listener(xdgoutput, &s_listener, this); +} + +XdgOutput::~XdgOutput() +{ + release(); +} + +void XdgOutput::setup(zxdg_output_v1 *xdgoutput) +{ + d->setup(xdgoutput); +} + +void XdgOutput::release() +{ + d->xdgoutput.release(); +} + +void XdgOutput::destroy() +{ + d->xdgoutput.destroy(); +} + +QSize XdgOutput::logicalSize() const +{ + return d->current.logicalSize; +} + +QPoint XdgOutput::logicalPosition() const +{ + return d->current.logicalPosition; +} + +XdgOutput::operator zxdg_output_v1*() { + return d->xdgoutput; +} + +XdgOutput::operator zxdg_output_v1*() const { + return d->xdgoutput; +} + +bool XdgOutput::isValid() const +{ + return d->xdgoutput.isValid(); +} + + +} +} + diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt --- a/src/server/CMakeLists.txt +++ b/src/server/CMakeLists.txt @@ -54,6 +54,7 @@ xdgforeign_v2_interface.cpp xdgforeign_interface.cpp xdgshell_v6_interface.cpp + xdgoutput_interface.cpp ) ecm_add_wayland_server_protocol(SERVER_LIB_SRCS @@ -180,6 +181,11 @@ BASENAME remote-access ) +ecm_add_wayland_server_protocol(SERVER_LIB_SRCS + PROTOCOL ${KWayland_SOURCE_DIR}/src/client/protocols/xdg-output-unstable-v1.xml + BASENAME xdg-output +) + set(SERVER_GENERATED_SRCS ${CMAKE_CURRENT_BINARY_DIR}/wayland-output-management-client-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-output-management-server-protocol.h diff --git a/src/server/display.h b/src/server/display.h --- a/src/server/display.h +++ b/src/server/display.h @@ -86,6 +86,7 @@ class XdgForeignInterface; class AppMenuManagerInterface; class ServerSideDecorationPaletteManagerInterface; +class XdgOutputManagerInterface; /** * @brief Class holding the Wayland server display loop. @@ -258,6 +259,14 @@ **/ ServerSideDecorationPaletteManagerInterface *createServerSideDecorationPaletteManager(QObject *parent = nullptr); + /** + * Creates the ServerSideDecorationPaletteManagerInterface + * + * @return the created manager + * @since 5.XDGOUTPUTVERSION + */ + XdgOutputManagerInterface *createXdgOutputManager(QObject *parent = nullptr); + /** * Gets the ClientConnection for the given @p client. diff --git a/src/server/display.cpp b/src/server/display.cpp --- a/src/server/display.cpp +++ b/src/server/display.cpp @@ -50,6 +50,7 @@ #include "xdgshell_v6_interface_p.h" #include "appmenu_interface.h" #include "server_decoration_palette_interface.h" +#include "xdgoutput_interface.h" #include #include @@ -457,6 +458,13 @@ return b; } +XdgOutputManagerInterface *Display::createXdgOutputManager(QObject *parent) +{ + auto b = new XdgOutputManagerInterface(this, parent); + connect(this, &Display::aboutToTerminate, b, [this, b] { delete b; }); + return b; +} + void Display::createShm() { Q_ASSERT(d->display); diff --git a/src/server/xdgoutput_interface.h b/src/server/xdgoutput_interface.h new file mode 100644 --- /dev/null +++ b/src/server/xdgoutput_interface.h @@ -0,0 +1,119 @@ +/**************************************************************************** +Copyright 2018 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_XDGOUTPUT_H +#define KWAYLAND_SERVER_XDGOUTPUT_H + +#include "global.h" +#include "resource.h" + + +#include + + +/* + * In terms of protocol XdgOutputInterface are a resource + * but for the sake of sanity, we should treat XdgOutputs as globals like Output is + * Hence this doesn't match most of kwayland API paradigms. + */ + +namespace KWayland +{ +namespace Server +{ + +class Display; +class OutputInterface; +class XdgOutputInterface; + +/** + * + * @since 5.XDGOUTPUT + */ +class KWAYLANDSERVER_EXPORT XdgOutputManagerInterface : public Global +{ + Q_OBJECT +public: + virtual ~XdgOutputManagerInterface(); + /** + * Creates an XdgOutputInterface object for an existing Output + * which exposes XDG specific properties of outputs + * + * @arg output the wl_output interface this XDG output is for + * @parent the parent of the newly created object + */ + XdgOutputInterface* createXdgOutput(OutputInterface *output, QObject *parent); +private: + explicit XdgOutputManagerInterface(Display *display, QObject *parent = nullptr); + friend class Display; + class Private; + Private *d_func() const; +}; + +/** + * + * @since 5.XDGOUTPUT + */ +class KWAYLANDSERVER_EXPORT XdgOutputInterface : public QObject +{ + Q_OBJECT +public: + virtual ~XdgOutputInterface(); + + /** + * Sets the size of this output in logical co-ordinates. + * Users should call done() after setting all values + */ + void setLogicalSize(const QSize &size); + + /** + * Returns the last set logical size on this output + */ + QSize logicalSize() const; + + /** + * Sets the topleft position of this output in logical co-ordinates. + * Users should call done() after setting all values + * @see OutputInterface::setPosition + */ + void setLogicalPosition(const QPoint &pos); + + /** + * Returns the last set logical position on this output + */ + QPoint logicalPosition() const; + + /** + * Submit changes to all clients + */ + void done(); + +private: + explicit XdgOutputInterface(QObject *parent); + friend class XdgOutputManagerInterface; + + class Private; + QScopedPointer d; +}; + + +} +} + +#endif diff --git a/src/server/xdgoutput_interface.cpp b/src/server/xdgoutput_interface.cpp new file mode 100644 --- /dev/null +++ b/src/server/xdgoutput_interface.cpp @@ -0,0 +1,289 @@ +/**************************************************************************** +Copyright 2018 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 "xdgoutput_interface.h" +#include "display.h" +#include "global_p.h" +#include "resource_p.h" +#include "output_interface.h" + +#include + +namespace KWayland +{ +namespace Server +{ + +class XdgOutputManagerInterface::Private : public Global::Private +{ +public: + Private(XdgOutputManagerInterface *q, Display *d); + QHash outputs; + +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 destroyCallback(wl_client *client, wl_resource *resource); + static void getXdgOutputCallback(wl_client *client, wl_resource *resource, uint32_t id, wl_resource * output); + + XdgOutputManagerInterface *q; + static const struct zxdg_output_manager_v1_interface s_interface; + static const quint32 s_version; +}; + +const quint32 XdgOutputManagerInterface::Private::s_version = 1; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +const struct zxdg_output_manager_v1_interface XdgOutputManagerInterface::Private::s_interface = { + destroyCallback, + getXdgOutputCallback +}; +#endif + +class XdgOutputV1Interface: public Resource +{ +public: + XdgOutputV1Interface(XdgOutputManagerInterface *parent, wl_resource *parentResource); + ~XdgOutputV1Interface(); + void setLogicalSize(const QSize &size); + void setLogicalPosition(const QPoint &pos); + void done(); +private: + class Private; +}; + +class XdgOutputInterface::Private +{ +public: + void resourceConnected(XdgOutputV1Interface *resource); + void resourceDisconnected(XdgOutputV1Interface *resource); + QPoint pos; + QSize size; + bool done = false; + QList resources; +}; + + +XdgOutputManagerInterface::XdgOutputManagerInterface(Display *display, QObject *parent) + : Global(new XdgOutputManagerInterface::Private(this, display)) +{ +} + +XdgOutputManagerInterface::~XdgOutputManagerInterface() +{} + +XdgOutputInterface* XdgOutputManagerInterface::createXdgOutput(OutputInterface *output, QObject *parent) +{ + Q_D(); + if (!d->outputs.contains(output)) { + d->outputs[output] = new XdgOutputInterface(parent); + } + return d->outputs[output]; +} + +XdgOutputManagerInterface::Private* XdgOutputManagerInterface::d_func() const +{ + return reinterpret_cast(d.data()); +} + +void XdgOutputManagerInterface::Private::destroyCallback(wl_client *client, wl_resource *resource) +{ + Q_UNUSED(client) + wl_resource_destroy(resource); +} + +void XdgOutputManagerInterface::Private::getXdgOutputCallback(wl_client *client, wl_resource *resource, uint32_t id, wl_resource * outputResource) +{ + auto d = cast(resource); + auto output = OutputInterface::get(outputResource); + if (!output) { // output client is requesting XdgOutput for doesn't exist + return; + } + if (!d->outputs.contains(output)) { + return; //server hasn't created an XdgOutput for this output yet, give the client nothing + } + auto iface = new XdgOutputV1Interface(d->q, resource); + iface->create(d->display->getConnection(client), wl_resource_get_version(resource), id); + if (!iface->resource()) { + wl_resource_post_no_memory(resource); + delete iface; + return; + } + + auto xdgOutput = d->outputs[output]; + xdgOutput->d->resourceConnected(iface); + connect(iface, &XdgOutputV1Interface::unbound, xdgOutput, [xdgOutput, iface]() { + xdgOutput->d->resourceDisconnected(iface); + }); +} + +XdgOutputManagerInterface::Private::Private(XdgOutputManagerInterface *q, Display *d) + : Global::Private(d, &zxdg_output_manager_v1_interface, s_version) + , q(q) +{ +} + +void XdgOutputManagerInterface::Private::bind(wl_client *client, uint32_t version, uint32_t id) +{ + auto c = display->getConnection(client); + wl_resource *resource = c->createResource(&zxdg_output_manager_v1_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 XdgOutputManagerInterface::Private::unbind(wl_resource *resource) +{ + Q_UNUSED(resource) +} + +XdgOutputInterface::XdgOutputInterface(QObject *parent): + QObject(parent), + d(new XdgOutputInterface::Private) +{ +} + +XdgOutputInterface::~XdgOutputInterface() +{} + +void XdgOutputInterface::setLogicalSize(const QSize &size) +{ + d->size = size; + for(auto resource: d->resources) { + resource->setLogicalSize(size); + } +} + +QSize XdgOutputInterface::logicalSize() const +{ + return d->size; +} + +void XdgOutputInterface::setLogicalPosition(const QPoint &pos) +{ + d->pos = pos; + for(auto resource: d->resources) { + resource->setLogicalPosition(pos); + } +} + +QPoint XdgOutputInterface::logicalPosition() const +{ + return d->pos; +} + +void XdgOutputInterface::done() +{ + d->done = true; + for(auto resource: d->resources) { + resource->done(); + } +} + +void XdgOutputInterface::Private::resourceConnected(XdgOutputV1Interface *resource) +{ + resource->setLogicalPosition(pos); + resource->setLogicalSize(size); + if (done) { + resource->done(); + } + resources << resource; +} + +void XdgOutputInterface::Private::resourceDisconnected(XdgOutputV1Interface *resource) +{ + resources.removeOne(resource); +} + + +class XdgOutputV1Interface::Private : public Resource::Private +{ +public: + Private(XdgOutputV1Interface *q, XdgOutputManagerInterface *c, wl_resource *parentResource); + ~Private(); + +private: + + XdgOutputV1Interface *q_func() { + return reinterpret_cast(q); + } + + static const struct zxdg_output_v1_interface s_interface; +}; + +XdgOutputV1Interface::XdgOutputV1Interface(XdgOutputManagerInterface *parent, wl_resource *parentResource) + :Resource(new XdgOutputV1Interface::Private(this, parent, parentResource)) +{} + +XdgOutputV1Interface::~XdgOutputV1Interface() +{} + +void XdgOutputV1Interface::setLogicalSize(const QSize &size) +{ + if (!d->resource) { + return; + } + zxdg_output_v1_send_logical_size(d->resource, size.width(), size.height()); +} + +void XdgOutputV1Interface::setLogicalPosition(const QPoint &pos) +{ + if (!d->resource) { + return; + } + zxdg_output_v1_send_logical_position(d->resource, pos.x(), pos.y()); +} + +void XdgOutputV1Interface::done() +{ + if (!d->resource) { + return; + } + zxdg_output_v1_send_done(d->resource); +} + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +const struct zxdg_output_v1_interface XdgOutputV1Interface::Private::s_interface = { + resourceDestroyedCallback +}; +#endif + +XdgOutputV1Interface::Private::Private(XdgOutputV1Interface *q, XdgOutputManagerInterface *c, wl_resource *parentResource) + : Resource::Private(q, c, parentResource, &zxdg_output_v1_interface, &s_interface) +{ +} + +XdgOutputV1Interface::Private::~Private() +{ + if (resource) { + wl_resource_destroy(resource); + resource = nullptr; + } +} + +} +} + diff --git a/src/tools/mapping.txt b/src/tools/mapping.txt --- a/src/tools/mapping.txt +++ b/src/tools/mapping.txt @@ -62,3 +62,5 @@ zwp_idle_inhibitor_v1;IdleInhibitor org_kde_kwin_remote_access_manager;RemoteAccessManager org_kde_kwin_remote_buffer;RemoteBuffer +zxdg_output_v1;XdgOutput +zxdg_output_manager_v1;XdgOutputManager