diff --git a/autotests/client/CMakeLists.txt b/autotests/client/CMakeLists.txt index 08d852c..cb2d29d 100644 --- a/autotests/client/CMakeLists.txt +++ b/autotests/client/CMakeLists.txt @@ -1,321 +1,332 @@ ######################################################## # Test WaylandConnectionThread ######################################################## set( testWaylandConnectionThread_SRCS test_wayland_connection_thread.cpp ) add_executable(testWaylandConnectionThread ${testWaylandConnectionThread_SRCS}) target_link_libraries( testWaylandConnectionThread Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer Wayland::Client Wayland::Server) add_test(kwayland-testWaylandConnectionThread testWaylandConnectionThread) ecm_mark_as_test(testWaylandConnectionThread) ######################################################## # Test WaylandRegistry ######################################################## set( testWaylandRegistry_SRCS test_wayland_registry.cpp ) add_executable(testWaylandRegistry ${testWaylandRegistry_SRCS}) target_link_libraries( testWaylandRegistry Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer Wayland::Client Wayland::Server) add_test(kwayland-testWaylandRegistry testWaylandRegistry) ecm_mark_as_test(testWaylandRegistry) ######################################################## # Test WaylandFullscreenShell ######################################################## if(Wayland_VERSION VERSION_GREATER "1.4.0") set( testWaylandFullscreenShell_SRCS test_wayland_fullscreen_shell.cpp ) add_executable(testWaylandFullscreenShell ${testWaylandFullscreenShell_SRCS}) target_link_libraries( testWaylandFullscreenShell Qt5::Test KF5::WaylandClient Wayland::Client) add_test(kwayland-testWaylandFullscreenShell testWaylandFullscreenShell) ecm_mark_as_test(testWaylandFullscreenShell) endif() ######################################################## # Test WaylandOutput ######################################################## set( testWaylandOutput_SRCS test_wayland_output.cpp ) add_executable(testWaylandOutput ${testWaylandOutput_SRCS}) target_link_libraries( testWaylandOutput Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer Wayland::Client Wayland::Server) add_test(kwayland-testWaylandOutput testWaylandOutput) ecm_mark_as_test(testWaylandOutput) ######################################################## # Test WaylandShell ######################################################## set( testWaylandShell_SRCS test_wayland_shell.cpp ) add_executable(testWaylandShell ${testWaylandShell_SRCS}) target_link_libraries( testWaylandShell Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer Wayland::Client Wayland::Server) add_test(kwayland-testWaylandShell testWaylandShell) ecm_mark_as_test(testWaylandShell) ######################################################## # Test WaylandSurface ######################################################## set( testWaylandSurface_SRCS test_wayland_surface.cpp ) add_executable(testWaylandSurface ${testWaylandSurface_SRCS}) target_link_libraries( testWaylandSurface Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer Wayland::Client Wayland::Server) add_test(kwayland-testWaylandSurface testWaylandSurface) ecm_mark_as_test(testWaylandSurface) ######################################################## # Test WaylandSeat ######################################################## set( testWaylandSeat_SRCS test_wayland_seat.cpp ) add_executable(testWaylandSeat ${testWaylandSeat_SRCS}) target_link_libraries( testWaylandSeat Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer Wayland::Client Wayland::Server) add_test(kwayland-testWaylandSeat testWaylandSeat) ecm_mark_as_test(testWaylandSeat) ######################################################## # Test ShmPool ######################################################## set( testShmPool_SRCS test_shm_pool.cpp ) add_executable(testShmPool ${testShmPool_SRCS}) target_link_libraries( testShmPool Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer) add_test(kwayland-testShmPool testShmPool) ecm_mark_as_test(testShmPool) ######################################################## # Test KWin OutputManagement ######################################################## set( test_wayland_outputmanagement_SRCS test_wayland_outputmanagement.cpp ) add_executable(testWaylandOutputManagement ${test_wayland_outputmanagement_SRCS}) target_link_libraries( testWaylandOutputManagement Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer Wayland::Client) add_test(kwayland-testWaylandOutputManagement testWaylandOutputManagement) ecm_mark_as_test(testWaylandOutputManagement) ######################################################## # Test KWin OutputDevice ######################################################## set( test_wayland_outputdevice_SRCS test_wayland_outputdevice.cpp ) add_executable(testWaylandOutputDevice ${test_wayland_outputdevice_SRCS}) target_link_libraries( testWaylandOutputDevice Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer Wayland::Client) add_test(kwayland-testWaylandOutputDevice testWaylandOutputDevice) ecm_mark_as_test(testWaylandOutputDevice) ######################################################## # Test Compositor ######################################################## set( testCompositor_SRCS test_compositor.cpp ) add_executable(testCompositor ${testCompositor_SRCS}) target_link_libraries( testCompositor Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer) add_test(kwayland-testCompositor testCompositor) ecm_mark_as_test(testCompositor) ######################################################## # Test SubCompositor ######################################################## set( testSubCompositor_SRCS test_wayland_subcompositor.cpp ) add_executable(testSubCompositor ${testSubCompositor_SRCS}) target_link_libraries( testSubCompositor Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer) add_test(kwayland-testSubCompositor testSubCompositor) ecm_mark_as_test(testSubCompositor) ######################################################## # Test SubSurface ######################################################## set( testSubSurface_SRCS test_wayland_subsurface.cpp ) add_executable(testSubSurface ${testSubSurface_SRCS}) target_link_libraries( testSubSurface Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer Wayland::Client) add_test(kwayland-testSubSurface testSubSurface) ecm_mark_as_test(testSubSurface) ######################################################## # Test Region ######################################################## set( testRegion_SRCS test_wayland_region.cpp ) add_executable(testRegion ${testRegion_SRCS}) target_link_libraries( testRegion Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer) add_test(kwayland-testRegion testRegion) ecm_mark_as_test(testRegion) ######################################################## # Test Blur ######################################################## set( testBlur_SRCS test_wayland_blur.cpp ) add_executable(testBlur ${testBlur_SRCS}) target_link_libraries( testBlur Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer) add_test(kwayland-testBlur testBlur) ecm_mark_as_test(testBlur) ######################################################## # Test Contrast ######################################################## set( testContrast_SRCS test_wayland_contrast.cpp ) add_executable(testContrast ${testContrast_SRCS}) target_link_libraries( testContrast Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer Wayland::Client) add_test(kwayland-testContrast testContrast) ecm_mark_as_test(testContrast) ######################################################## # Test Slide ######################################################## set( testSlide_SRCS test_wayland_slide.cpp ) add_executable(testSlide ${testSlide_SRCS}) target_link_libraries( testSlide Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer Wayland::Client) add_test(kwayland-testSlide testSlide) ecm_mark_as_test(testSlide) ######################################################## # Test Window Management ######################################################## set( testWindowmanagement_SRCS test_wayland_windowmanagement.cpp ) add_executable(testWindowmanagement ${testWindowmanagement_SRCS}) target_link_libraries( testWindowmanagement Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer Wayland::Client) add_test(kwayland-testWindowmanagement testWindowmanagement) ecm_mark_as_test(testWindowmanagement) ######################################################## # Test DataSource ######################################################## set( testDataSource_SRCS test_datasource.cpp ) add_executable(testDataSource ${testDataSource_SRCS}) target_link_libraries( testDataSource Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer Wayland::Client) add_test(kwayland-testDataSource testDataSource) ecm_mark_as_test(testDataSource) ######################################################## # Test DataDevice ######################################################## set( testDataDevice_SRCS test_datadevice.cpp ) add_executable(testDataDevice ${testDataDevice_SRCS}) target_link_libraries( testDataDevice Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer Wayland::Client) add_test(kwayland-testDataDevice testDataDevice) ecm_mark_as_test(testDataDevice) ######################################################## # Test ServerSideDecoration ######################################################## set( testServerSideDecoration_SRCS test_server_side_decoration.cpp ) add_executable(testServerSideDecoration ${testServerSideDecoration_SRCS}) target_link_libraries( testServerSideDecoration Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer Wayland::Client) add_test(kwayland-testServerSideDecoration testServerSideDecoration) ecm_mark_as_test(testServerSideDecoration) ######################################################## # Test Drag'N'Drop ######################################################## set( testDragAndDrop_SRCS test_drag_drop.cpp ) add_executable(testDragAndDrop ${testDragAndDrop_SRCS}) target_link_libraries( testDragAndDrop Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer Wayland::Client) add_test(kwayland-testDragAndDrop testDragAndDrop) ecm_mark_as_test(testDragAndDrop) ######################################################## # Test PlasmaShell ######################################################## set( testPlasmaShell_SRCS test_plasmashell.cpp ) add_executable(testPlasmaShell ${testPlasmaShell_SRCS}) target_link_libraries( testPlasmaShell Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer Wayland::Client) add_test(kwayland-testPlasmaShell testPlasmaShell) ecm_mark_as_test(testPlasmaShell) ######################################################## # Test Idle ######################################################## set( testIdle_SRCS test_idle.cpp ) add_executable(testIdle ${testIdle_SRCS}) target_link_libraries( testIdle Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer) add_test(kwayland-testIdle testIdle) ecm_mark_as_test(testIdle) ######################################################## # Test Shadow ######################################################## set( testShadow_SRCS test_shadow.cpp ) add_executable(testShadow ${testShadow_SRCS}) target_link_libraries( testShadow Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer) add_test(kwayland-testShadow testShadow) ecm_mark_as_test(testShadow) ######################################################## # Test FakeInput ######################################################## set( testFakeInput_SRCS test_fake_input.cpp ) add_executable(testFakeInput ${testFakeInput_SRCS}) target_link_libraries( testFakeInput Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer) add_test(kwayland-testFakeInput testFakeInput) ecm_mark_as_test(testFakeInput) ######################################################## # Test PlasmaWindowModel ######################################################## set( testPlasmaWindowModel_SRCS test_plasma_window_model.cpp ) add_executable(testPlasmaWindowModel ${testPlasmaWindowModel_SRCS}) target_link_libraries( testPlasmaWindowModel Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer) add_test(kwayland-testPlasmaWindowModel testPlasmaWindowModel) ecm_mark_as_test(testPlasmaWindowModel) ######################################################## # Test TextInput ######################################################## set( testTextInput_SRCS test_text_input.cpp ) add_executable(testTextInput ${testTextInput_SRCS}) target_link_libraries( testTextInput Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer) add_test(kwayland-testTextInput testTextInput) ecm_mark_as_test(testTextInput) ######################################################## # Test Error ######################################################## set( testError_SRCS test_error.cpp ) add_executable(testError ${testError_SRCS}) target_link_libraries( testError Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer Wayland::Client) add_test(kwayland-testError testError) ecm_mark_as_test(testError) + +######################################################## +# Test Selection +######################################################## +set( testSelection_SRCS + test_selection.cpp + ) +add_executable(testSelection ${testSelection_SRCS}) +target_link_libraries( testSelection Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer Wayland::Client) +add_test(kwayland-testSelection testSelection) +ecm_mark_as_test(testSelection) diff --git a/autotests/client/test_selection.cpp b/autotests/client/test_selection.cpp new file mode 100644 index 0000000..18dd85b --- /dev/null +++ b/autotests/client/test_selection.cpp @@ -0,0 +1,274 @@ +/******************************************************************** +Copyright 2016 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 +// client +#include "../../src/client/connection_thread.h" +#include "../../src/client/compositor.h" +#include "../../src/client/datadevice.h" +#include "../../src/client/datadevicemanager.h" +#include "../../src/client/datasource.h" +#include "../../src/client/event_queue.h" +#include "../../src/client/keyboard.h" +#include "../../src/client/registry.h" +#include "../../src/client/seat.h" +#include "../../src/client/surface.h" +// server +#include "../../src/server/compositor_interface.h" +#include "../../src/server/datadevicemanager_interface.h" +#include "../../src/server/display.h" +#include "../../src/server/seat_interface.h" + +using namespace KWayland::Client; +using namespace KWayland::Server; + + +class SelectionTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void init(); + void cleanup(); + void testClearOnEnter(); + +private: + Display *m_display = nullptr; + CompositorInterface *m_compositorInterface = nullptr; + SeatInterface *m_seatInterface = nullptr; + DataDeviceManagerInterface *m_ddmInterface = nullptr; + + struct Connection { + ConnectionThread *connection = nullptr; + QThread *thread = nullptr; + EventQueue *queue = nullptr; + Compositor *compositor = nullptr; + Seat *seat = nullptr; + DataDeviceManager *ddm = nullptr; + Keyboard *keyboard = nullptr; + DataDevice *dataDevice = nullptr; + }; + bool setupConnection(Connection *c); + void cleanupConnection(Connection *c); + + Connection m_client1; + Connection m_client2; +}; + +static const QString s_socketName = QStringLiteral("kwayland-test-selection-0"); + +void SelectionTest::init() +{ + delete m_display; + m_display = new Display(this); + m_display->setSocketName(s_socketName); + m_display->start(); + QVERIFY(m_display->isRunning()); + m_display->createShm(); + m_compositorInterface = m_display->createCompositor(m_display); + m_compositorInterface->create(); + m_seatInterface = m_display->createSeat(m_display); + m_seatInterface->setHasKeyboard(true); + m_seatInterface->create(); + m_ddmInterface = m_display->createDataDeviceManager(m_display); + m_ddmInterface->create(); + + // setup connection + setupConnection(&m_client1); + setupConnection(&m_client2); +} + +bool SelectionTest::setupConnection(Connection* c) +{ + c->connection = new ConnectionThread; + QSignalSpy connectedSpy(c->connection, &ConnectionThread::connected); + if (!connectedSpy.isValid()) { + return false; + } + c->connection->setSocketName(s_socketName); + + c->thread = new QThread(this); + c->connection->moveToThread(c->thread); + c->thread->start(); + + c->connection->initConnection(); + if (!connectedSpy.wait()) { + return false; + } + + c->queue = new EventQueue(this); + c->queue->setup(c->connection); + + Registry registry; + QSignalSpy interfacesAnnouncedSpy(®istry, &Registry::interfacesAnnounced); + if (!interfacesAnnouncedSpy.isValid()) { + return false; + } + registry.setEventQueue(c->queue); + registry.create(c->connection); + if (!registry.isValid()) { + return false; + } + registry.setup(); + if (!interfacesAnnouncedSpy.wait()) { + return false; + } + + c->compositor = registry.createCompositor(registry.interface(Registry::Interface::Compositor).name, + registry.interface(Registry::Interface::Compositor).version, + this); + if (!c->compositor->isValid()) { + return false; + } + c->ddm = registry.createDataDeviceManager(registry.interface(Registry::Interface::DataDeviceManager).name, + registry.interface(Registry::Interface::DataDeviceManager).version, + this); + if (!c->ddm->isValid()) { + return false; + } + c->seat = registry.createSeat(registry.interface(Registry::Interface::Seat).name, + registry.interface(Registry::Interface::Seat).version, + this); + if (!c->seat->isValid()) { + return false; + } + QSignalSpy keyboardSpy(c->seat, &Seat::hasKeyboardChanged); + if (!keyboardSpy.isValid()) { + return false; + } + if (!keyboardSpy.wait()) { + return false; + } + if (!c->seat->hasKeyboard()) { + return false; + } + c->keyboard = c->seat->createKeyboard(c->seat); + if (!c->keyboard->isValid()) { + return false; + } + c->dataDevice = c->ddm->getDataDevice(c->seat, this); + if (!c->dataDevice->isValid()) { + return false; + } + + return true; +} + +void SelectionTest::cleanup() +{ + cleanupConnection(&m_client1); + cleanupConnection(&m_client2); +#define CLEANUP(variable) \ + delete variable; \ + variable = nullptr; + + CLEANUP(m_ddmInterface) + CLEANUP(m_seatInterface) + CLEANUP(m_compositorInterface) + CLEANUP(m_display) +#undef CLEANUP +} + +void SelectionTest::cleanupConnection(Connection *c) +{ + delete c->dataDevice; + c->dataDevice = nullptr; + delete c->keyboard; + c->keyboard = nullptr; + delete c->ddm; + c->ddm = nullptr; + delete c->seat; + c->seat = nullptr; + delete c->compositor; + c->compositor = nullptr; + delete c->queue; + c->queue = nullptr; + if (c->connection) { + c->connection->deleteLater(); + c->connection = nullptr; + } + if (c->thread) { + c->thread->quit(); + c->thread->wait(); + delete c->thread; + c->thread = nullptr; + } +} + +void SelectionTest::testClearOnEnter() +{ + // this test verifies that the selection is cleared prior to keyboard enter if there is no current selection + QSignalSpy selectionClearedClient1Spy(m_client1.dataDevice, &DataDevice::selectionCleared); + QVERIFY(selectionClearedClient1Spy.isValid()); + QSignalSpy keyboardEnteredClient1Spy(m_client1.keyboard, &Keyboard::entered); + QVERIFY(keyboardEnteredClient1Spy.isValid()); + + // now create a Surface + QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated); + QVERIFY(surfaceCreatedSpy.isValid()); + QScopedPointer s1(m_client1.compositor->createSurface()); + QVERIFY(surfaceCreatedSpy.wait()); + auto serverSurface1 = surfaceCreatedSpy.first().first().value(); + QVERIFY(serverSurface1); + + // pass this surface keyboard focus + m_seatInterface->setFocusedKeyboardSurface(serverSurface1); + // should get a clear + QVERIFY(selectionClearedClient1Spy.wait()); + + // let's set a selection + QScopedPointer dataSource(m_client1.ddm->createDataSource()); + dataSource->offer(QStringLiteral("text/plain")); + m_client1.dataDevice->setSelection(keyboardEnteredClient1Spy.first().first().value(), dataSource.data()); + + // now let's bring in client 2 + QSignalSpy selectionOfferedClient2Spy(m_client2.dataDevice, &DataDevice::selectionOffered); + QVERIFY(selectionOfferedClient2Spy.isValid()); + QSignalSpy selectionClearedClient2Spy(m_client2.dataDevice, &DataDevice::selectionCleared); + QVERIFY(selectionClearedClient2Spy.isValid()); + QSignalSpy keyboardEnteredClient2Spy(m_client2.keyboard, &Keyboard::entered); + QVERIFY(keyboardEnteredClient2Spy.isValid()); + QScopedPointer s2(m_client2.compositor->createSurface()); + QVERIFY(surfaceCreatedSpy.wait()); + auto serverSurface2 = surfaceCreatedSpy.last().first().value(); + QVERIFY(serverSurface2); + + // entering that surface should give a selection + m_seatInterface->setFocusedKeyboardSurface(serverSurface2); + QVERIFY(selectionOfferedClient2Spy.wait()); + QVERIFY(selectionClearedClient2Spy.isEmpty()); + // now unset the selection + m_client2.dataDevice->clearSelection(keyboardEnteredClient2Spy.first().first().value()); + QVERIFY(selectionClearedClient2Spy.wait()); + // set a data source but without offers + QScopedPointer dataSource2(m_client2.ddm->createDataSource()); + m_client2.dataDevice->setSelection(keyboardEnteredClient2Spy.first().first().value(), dataSource2.data()); + QVERIFY(selectionOfferedClient2Spy.wait()); + // and clear again + m_client2.dataDevice->clearSelection(keyboardEnteredClient2Spy.first().first().value()); + QVERIFY(selectionClearedClient2Spy.wait()); + + // now pass focus to first surface + m_seatInterface->setFocusedKeyboardSurface(serverSurface1); + // we should get a clear + QVERIFY(selectionClearedClient1Spy.wait()); +} + +QTEST_GUILESS_MAIN(SelectionTest) +#include "test_selection.moc" diff --git a/autotests/client/test_wayland_seat.cpp b/autotests/client/test_wayland_seat.cpp index cdae944..8c9cff3 100644 --- a/autotests/client/test_wayland_seat.cpp +++ b/autotests/client/test_wayland_seat.cpp @@ -1,1762 +1,1760 @@ /******************************************************************** 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/datadevice.h" #include "../../src/client/datadevicemanager.h" #include "../../src/client/datasource.h" #include "../../src/client/event_queue.h" #include "../../src/client/keyboard.h" #include "../../src/client/pointer.h" #include "../../src/client/surface.h" #include "../../src/client/registry.h" #include "../../src/client/seat.h" #include "../../src/client/shm_pool.h" #include "../../src/client/subcompositor.h" #include "../../src/client/subsurface.h" #include "../../src/client/touch.h" #include "../../src/server/buffer_interface.h" #include "../../src/server/compositor_interface.h" #include "../../src/server/datadevicemanager_interface.h" #include "../../src/server/display.h" #include "../../src/server/keyboard_interface.h" #include "../../src/server/pointer_interface.h" #include "../../src/server/seat_interface.h" #include "../../src/server/subcompositor_interface.h" #include "../../src/server/surface_interface.h" // Wayland #include #include // System #include #include class TestWaylandSeat : public QObject { Q_OBJECT public: explicit TestWaylandSeat(QObject *parent = nullptr); private Q_SLOTS: void init(); void cleanup(); void testName(); void testCapabilities_data(); void testCapabilities(); void testPointer(); void testPointerTransformation_data(); void testPointerTransformation(); void testPointerButton_data(); void testPointerButton(); void testPointerSubSurfaceTree(); void testKeyboardSubSurfaceTreeFromPointer(); void testCursor(); void testCursorDamage(); void testKeyboard(); void testCast(); void testDestroy(); void testSelection(); void testTouch(); void testDisconnect(); // TODO: add test for keymap private: KWayland::Server::Display *m_display; KWayland::Server::CompositorInterface *m_compositorInterface; KWayland::Server::SeatInterface *m_seatInterface; KWayland::Server::SubCompositorInterface *m_subCompositorInterface; KWayland::Client::ConnectionThread *m_connection; KWayland::Client::Compositor *m_compositor; KWayland::Client::Seat *m_seat; KWayland::Client::ShmPool *m_shm; KWayland::Client::SubCompositor * m_subCompositor; KWayland::Client::EventQueue *m_queue; QThread *m_thread; }; static const QString s_socketName = QStringLiteral("kwin-test-wayland-seat-0"); TestWaylandSeat::TestWaylandSeat(QObject *parent) : QObject(parent) , m_display(nullptr) , m_compositorInterface(nullptr) , m_seatInterface(nullptr) , m_subCompositorInterface(nullptr) , m_connection(nullptr) , m_compositor(nullptr) , m_seat(nullptr) , m_shm(nullptr) , m_subCompositor(nullptr) , m_queue(nullptr) , m_thread(nullptr) { } void TestWaylandSeat::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_display->createShm(); m_compositorInterface = m_display->createCompositor(m_display); QVERIFY(m_compositorInterface); m_compositorInterface->create(); QVERIFY(m_compositorInterface->isValid()); m_subCompositorInterface = m_display->createSubCompositor(m_display); QVERIFY(m_subCompositorInterface); m_subCompositorInterface->create(); QVERIFY(m_subCompositorInterface->isValid()); // 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); m_queue->setup(m_connection); KWayland::Client::Registry registry; QSignalSpy compositorSpy(®istry, SIGNAL(compositorAnnounced(quint32,quint32))); QSignalSpy seatSpy(®istry, SIGNAL(seatAnnounced(quint32,quint32))); QSignalSpy shmSpy(®istry, SIGNAL(shmAnnounced(quint32,quint32))); registry.setEventQueue(m_queue); registry.create(m_connection->display()); QVERIFY(registry.isValid()); registry.setup(); QVERIFY(compositorSpy.wait()); m_seatInterface = m_display->createSeat(); QVERIFY(m_seatInterface); m_seatInterface->setName(QStringLiteral("seat0")); m_seatInterface->create(); QVERIFY(m_seatInterface->isValid()); QVERIFY(seatSpy.wait()); m_compositor = new KWayland::Client::Compositor(this); m_compositor->setup(registry.bindCompositor(compositorSpy.first().first().value(), compositorSpy.first().last().value())); QVERIFY(m_compositor->isValid()); m_seat = registry.createSeat(seatSpy.first().first().value(), seatSpy.first().last().value(), this); QSignalSpy nameSpy(m_seat, SIGNAL(nameChanged(QString))); QVERIFY(nameSpy.wait()); m_shm = new KWayland::Client::ShmPool(this); m_shm->setup(registry.bindShm(shmSpy.first().first().value(), shmSpy.first().last().value())); QVERIFY(m_shm->isValid()); m_subCompositor = registry.createSubCompositor(registry.interface(KWayland::Client::Registry::Interface::SubCompositor).name, registry.interface(KWayland::Client::Registry::Interface::SubCompositor).version, this); QVERIFY(m_subCompositor->isValid()); } void TestWaylandSeat::cleanup() { if (m_subCompositor) { delete m_subCompositor; m_subCompositor = nullptr; } if (m_shm) { delete m_shm; m_shm = nullptr; } if (m_seat) { delete m_seat; m_seat = nullptr; } if (m_compositor) { delete m_compositor; m_compositor = nullptr; } if (m_queue) { delete m_queue; m_queue = nullptr; } if (m_connection) { m_connection->deleteLater(); m_connection = nullptr; } if (m_thread) { m_thread->quit(); m_thread->wait(); delete m_thread; m_thread = nullptr; } delete m_compositorInterface; m_compositorInterface = nullptr; delete m_seatInterface; m_seatInterface = nullptr; delete m_subCompositorInterface; m_subCompositorInterface = nullptr; delete m_display; m_display = nullptr; } void TestWaylandSeat::testName() { // no name set yet QCOMPARE(m_seat->name(), QStringLiteral("seat0")); QSignalSpy spy(m_seat, SIGNAL(nameChanged(QString))); QVERIFY(spy.isValid()); const QString name = QStringLiteral("foobar"); m_seatInterface->setName(name); QVERIFY(spy.wait()); QCOMPARE(m_seat->name(), name); QCOMPARE(spy.count(), 1); QCOMPARE(spy.first().first().toString(), name); } void TestWaylandSeat::testCapabilities_data() { QTest::addColumn("pointer"); QTest::addColumn("keyboard"); QTest::addColumn("touch"); QTest::newRow("none") << false << false << false; QTest::newRow("pointer") << true << false << false; QTest::newRow("keyboard") << false << true << false; QTest::newRow("touch") << false << false << true; QTest::newRow("pointer/keyboard") << true << true << false; QTest::newRow("pointer/touch") << true << false << true; QTest::newRow("keyboard/touch") << false << true << true; QTest::newRow("all") << true << true << true; } void TestWaylandSeat::testCapabilities() { QVERIFY(!m_seat->hasPointer()); QVERIFY(!m_seat->hasKeyboard()); QVERIFY(!m_seat->hasTouch()); QFETCH(bool, pointer); QFETCH(bool, keyboard); QFETCH(bool, touch); QSignalSpy pointerSpy(m_seat, SIGNAL(hasPointerChanged(bool))); QVERIFY(pointerSpy.isValid()); QSignalSpy keyboardSpy(m_seat, SIGNAL(hasKeyboardChanged(bool))); QVERIFY(keyboardSpy.isValid()); QSignalSpy touchSpy(m_seat, SIGNAL(hasTouchChanged(bool))); QVERIFY(touchSpy.isValid()); m_seatInterface->setHasPointer(pointer); m_seatInterface->setHasKeyboard(keyboard); m_seatInterface->setHasTouch(touch); // do processing QCOMPARE(pointerSpy.wait(1000), pointer); QCOMPARE(pointerSpy.isEmpty(), !pointer); if (!pointerSpy.isEmpty()) { QCOMPARE(pointerSpy.first().first().toBool(), pointer); } if (keyboardSpy.isEmpty()) { QCOMPARE(keyboardSpy.wait(1000), keyboard); } QCOMPARE(keyboardSpy.isEmpty(), !keyboard); if (!keyboardSpy.isEmpty()) { QCOMPARE(keyboardSpy.first().first().toBool(), keyboard); } if (touchSpy.isEmpty()) { QCOMPARE(touchSpy.wait(1000), touch); } QCOMPARE(touchSpy.isEmpty(), !touch); if (!touchSpy.isEmpty()) { QCOMPARE(touchSpy.first().first().toBool(), touch); } QCOMPARE(m_seat->hasPointer(), pointer); QCOMPARE(m_seat->hasKeyboard(), keyboard); QCOMPARE(m_seat->hasTouch(), touch); } void TestWaylandSeat::testPointer() { using namespace KWayland::Client; using namespace KWayland::Server; QSignalSpy pointerSpy(m_seat, SIGNAL(hasPointerChanged(bool))); QVERIFY(pointerSpy.isValid()); m_seatInterface->setHasPointer(true); QVERIFY(pointerSpy.wait()); QSignalSpy surfaceCreatedSpy(m_compositorInterface, SIGNAL(surfaceCreated(KWayland::Server::SurfaceInterface*))); QVERIFY(surfaceCreatedSpy.isValid()); Surface *s = m_compositor->createSurface(m_compositor); QVERIFY(surfaceCreatedSpy.wait()); SurfaceInterface *serverSurface = surfaceCreatedSpy.first().first().value(); QVERIFY(serverSurface); QSignalSpy focusedPointerChangedSpy(m_seatInterface, &SeatInterface::focusedPointerChanged); QVERIFY(focusedPointerChangedSpy.isValid()); m_seatInterface->setPointerPos(QPoint(20, 18)); m_seatInterface->setFocusedPointerSurface(serverSurface, QPoint(10, 15)); QCOMPARE(focusedPointerChangedSpy.count(), 1); QVERIFY(!focusedPointerChangedSpy.first().first().value()); // no pointer yet QVERIFY(m_seatInterface->focusedPointerSurface()); QVERIFY(!m_seatInterface->focusedPointer()); Pointer *p = m_seat->createPointer(m_seat); const Pointer &cp = *p; QVERIFY(p->isValid()); QSignalSpy pointerCreatedSpy(m_seatInterface, SIGNAL(pointerCreated(KWayland::Server::PointerInterface*))); QVERIFY(pointerCreatedSpy.isValid()); // once the pointer is created it should be set as the focused pointer QVERIFY(pointerCreatedSpy.wait()); QVERIFY(m_seatInterface->focusedPointer()); QCOMPARE(pointerCreatedSpy.first().first().value(), m_seatInterface->focusedPointer()); QCOMPARE(focusedPointerChangedSpy.count(), 2); QCOMPARE(focusedPointerChangedSpy.last().first().value(), m_seatInterface->focusedPointer()); m_seatInterface->setFocusedPointerSurface(nullptr); QCOMPARE(focusedPointerChangedSpy.count(), 3); QVERIFY(!focusedPointerChangedSpy.last().first().value()); serverSurface->client()->flush(); QTest::qWait(100); QSignalSpy enteredSpy(p, SIGNAL(entered(quint32,QPointF))); QVERIFY(enteredSpy.isValid()); QSignalSpy leftSpy(p, SIGNAL(left(quint32))); QVERIFY(leftSpy.isValid()); QSignalSpy motionSpy(p, SIGNAL(motion(QPointF,quint32))); QVERIFY(motionSpy.isValid()); QSignalSpy axisSpy(p, SIGNAL(axisChanged(quint32,KWayland::Client::Pointer::Axis,qreal))); QVERIFY(axisSpy.isValid()); QSignalSpy buttonSpy(p, SIGNAL(buttonStateChanged(quint32,quint32,quint32,KWayland::Client::Pointer::ButtonState))); QVERIFY(buttonSpy.isValid()); QVERIFY(!p->enteredSurface()); QVERIFY(!cp.enteredSurface()); m_seatInterface->setFocusedPointerSurface(serverSurface, QPoint(10, 15)); QCOMPARE(m_seatInterface->focusedPointerSurface(), serverSurface); QVERIFY(enteredSpy.wait()); QCOMPARE(enteredSpy.first().first().value(), m_display->serial()); QCOMPARE(enteredSpy.first().last().toPoint(), QPoint(10, 3)); PointerInterface *serverPointer = m_seatInterface->focusedPointer(); QVERIFY(serverPointer); QCOMPARE(p->enteredSurface(), s); QCOMPARE(cp.enteredSurface(), s); QCOMPARE(focusedPointerChangedSpy.count(), 4); QCOMPARE(focusedPointerChangedSpy.last().first().value(), serverPointer); // test motion m_seatInterface->setTimestamp(1); m_seatInterface->setPointerPos(QPoint(10, 16)); QVERIFY(motionSpy.wait()); QCOMPARE(motionSpy.first().first().toPoint(), QPoint(0, 1)); QCOMPARE(motionSpy.first().last().value(), quint32(1)); // test axis m_seatInterface->setTimestamp(2); m_seatInterface->pointerAxis(Qt::Horizontal, 10); QVERIFY(axisSpy.wait()); m_seatInterface->setTimestamp(3); m_seatInterface->pointerAxis(Qt::Vertical, 20); QVERIFY(axisSpy.wait()); QCOMPARE(axisSpy.first().at(0).value(), quint32(2)); QCOMPARE(axisSpy.first().at(1).value(), Pointer::Axis::Horizontal); QCOMPARE(axisSpy.first().at(2).value(), qreal(10)); QCOMPARE(axisSpy.last().at(0).value(), quint32(3)); QCOMPARE(axisSpy.last().at(1).value(), Pointer::Axis::Vertical); QCOMPARE(axisSpy.last().at(2).value(), qreal(20)); // test button m_seatInterface->setTimestamp(4); m_seatInterface->pointerButtonPressed(1); QVERIFY(buttonSpy.wait()); QCOMPARE(buttonSpy.at(0).at(0).value(), m_display->serial()); m_seatInterface->setTimestamp(5); m_seatInterface->pointerButtonPressed(2); QVERIFY(buttonSpy.wait()); QCOMPARE(buttonSpy.at(1).at(0).value(), m_display->serial()); m_seatInterface->setTimestamp(6); m_seatInterface->pointerButtonReleased(2); QVERIFY(buttonSpy.wait()); QCOMPARE(buttonSpy.at(2).at(0).value(), m_display->serial()); m_seatInterface->setTimestamp(7); m_seatInterface->pointerButtonReleased(1); QVERIFY(buttonSpy.wait()); QCOMPARE(buttonSpy.count(), 4); // timestamp QCOMPARE(buttonSpy.at(0).at(1).value(), quint32(4)); // button QCOMPARE(buttonSpy.at(0).at(2).value(), quint32(1)); QCOMPARE(buttonSpy.at(0).at(3).value(), KWayland::Client::Pointer::ButtonState::Pressed); // timestamp QCOMPARE(buttonSpy.at(1).at(1).value(), quint32(5)); // button QCOMPARE(buttonSpy.at(1).at(2).value(), quint32(2)); QCOMPARE(buttonSpy.at(1).at(3).value(), KWayland::Client::Pointer::ButtonState::Pressed); QCOMPARE(buttonSpy.at(2).at(0).value(), m_seatInterface->pointerButtonSerial(2)); // timestamp QCOMPARE(buttonSpy.at(2).at(1).value(), quint32(6)); // button QCOMPARE(buttonSpy.at(2).at(2).value(), quint32(2)); QCOMPARE(buttonSpy.at(2).at(3).value(), KWayland::Client::Pointer::ButtonState::Released); QCOMPARE(buttonSpy.at(3).at(0).value(), m_seatInterface->pointerButtonSerial(1)); // timestamp QCOMPARE(buttonSpy.at(3).at(1).value(), quint32(7)); // button QCOMPARE(buttonSpy.at(3).at(2).value(), quint32(1)); QCOMPARE(buttonSpy.at(3).at(3).value(), KWayland::Client::Pointer::ButtonState::Released); // leave the surface m_seatInterface->setFocusedPointerSurface(nullptr); QCOMPARE(focusedPointerChangedSpy.count(), 5); QVERIFY(leftSpy.wait()); QCOMPARE(leftSpy.first().first().value(), m_display->serial()); QVERIFY(!p->enteredSurface()); QVERIFY(!cp.enteredSurface()); // enter it again m_seatInterface->setFocusedPointerSurface(serverSurface, QPoint(0, 0)); QCOMPARE(focusedPointerChangedSpy.count(), 6); QVERIFY(enteredSpy.wait()); QCOMPARE(p->enteredSurface(), s); QCOMPARE(cp.enteredSurface(), s); // destroy the focused pointer QSignalSpy unboundSpy(serverPointer, &Resource::unbound); QVERIFY(unboundSpy.isValid()); QSignalSpy destroyedSpy(serverPointer, &Resource::destroyed); QVERIFY(destroyedSpy.isValid()); delete p; QVERIFY(unboundSpy.wait()); QCOMPARE(unboundSpy.count(), 1); QCOMPARE(destroyedSpy.count(), 0); // now test that calling into the methods in Seat does not crash QCOMPARE(m_seatInterface->focusedPointer(), serverPointer); QCOMPARE(m_seatInterface->focusedPointerSurface(), serverSurface); m_seatInterface->setTimestamp(8); m_seatInterface->setPointerPos(QPoint(10, 15)); m_seatInterface->setTimestamp(9); m_seatInterface->pointerButtonPressed(1); m_seatInterface->setTimestamp(10); m_seatInterface->pointerButtonReleased(1); m_seatInterface->setTimestamp(11); m_seatInterface->pointerAxis(Qt::Horizontal, 10); m_seatInterface->setTimestamp(12); m_seatInterface->pointerAxis(Qt::Vertical, 20); m_seatInterface->setFocusedPointerSurface(nullptr); QCOMPARE(focusedPointerChangedSpy.count(), 7); m_seatInterface->setFocusedPointerSurface(serverSurface); QCOMPARE(focusedPointerChangedSpy.count(), 8); QCOMPARE(m_seatInterface->focusedPointerSurface(), serverSurface); QVERIFY(!m_seatInterface->focusedPointer()); // and now destroy QVERIFY(destroyedSpy.wait()); QCOMPARE(unboundSpy.count(), 1); QCOMPARE(destroyedSpy.count(), 1); QCOMPARE(m_seatInterface->focusedPointerSurface(), serverSurface); QVERIFY(!m_seatInterface->focusedPointer()); // create a pointer again p = m_seat->createPointer(m_seat); QVERIFY(focusedPointerChangedSpy.wait()); QCOMPARE(focusedPointerChangedSpy.count(), 9); QCOMPARE(m_seatInterface->focusedPointerSurface(), serverSurface); serverPointer = m_seatInterface->focusedPointer(); QVERIFY(serverPointer); delete s; wl_display_flush(m_connection->display()); QVERIFY(focusedPointerChangedSpy.wait()); QCOMPARE(focusedPointerChangedSpy.count(), 10); QVERIFY(!m_seatInterface->focusedPointerSurface()); QVERIFY(!m_seatInterface->focusedPointer()); } void TestWaylandSeat::testPointerTransformation_data() { QTest::addColumn("enterTransformation"); // global position at 20/18 QTest::addColumn("expectedEnterPoint"); // global position at 10/16 QTest::addColumn("expectedMovePoint"); QMatrix4x4 tm; tm.translate(-10, -15); QTest::newRow("translation") << tm << QPointF(10, 3) << QPointF(0, 1); QMatrix4x4 sm; sm.scale(2, 2); QTest::newRow("scale") << sm << QPointF(40, 36) << QPointF(20, 32); QMatrix4x4 rotate; rotate.rotate(90, 0, 0, 1); QTest::newRow("rotate") << rotate << QPointF(-18, 20) << QPointF(-16, 10); } void TestWaylandSeat::testPointerTransformation() { using namespace KWayland::Client; using namespace KWayland::Server; QSignalSpy pointerSpy(m_seat, &Seat::hasPointerChanged); QVERIFY(pointerSpy.isValid()); m_seatInterface->setHasPointer(true); QVERIFY(pointerSpy.wait()); QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated); QVERIFY(surfaceCreatedSpy.isValid()); Surface *s = m_compositor->createSurface(m_compositor); QVERIFY(surfaceCreatedSpy.wait()); SurfaceInterface *serverSurface = surfaceCreatedSpy.first().first().value(); QVERIFY(serverSurface); m_seatInterface->setPointerPos(QPoint(20, 18)); QFETCH(QMatrix4x4, enterTransformation); m_seatInterface->setFocusedPointerSurface(serverSurface, enterTransformation); QCOMPARE(m_seatInterface->focusedPointerSurfaceTransformation(), enterTransformation); // no pointer yet QVERIFY(m_seatInterface->focusedPointerSurface()); QVERIFY(!m_seatInterface->focusedPointer()); Pointer *p = m_seat->createPointer(m_seat); const Pointer &cp = *p; QVERIFY(p->isValid()); QSignalSpy pointerCreatedSpy(m_seatInterface, &SeatInterface::pointerCreated); QVERIFY(pointerCreatedSpy.isValid()); // once the pointer is created it should be set as the focused pointer QVERIFY(pointerCreatedSpy.wait()); QVERIFY(m_seatInterface->focusedPointer()); QCOMPARE(pointerCreatedSpy.first().first().value(), m_seatInterface->focusedPointer()); m_seatInterface->setFocusedPointerSurface(nullptr); serverSurface->client()->flush(); QTest::qWait(100); QSignalSpy enteredSpy(p, &Pointer::entered); QVERIFY(enteredSpy.isValid()); QSignalSpy leftSpy(p, &Pointer::left); QVERIFY(leftSpy.isValid()); QSignalSpy motionSpy(p, &Pointer::motion); QVERIFY(motionSpy.isValid()); QVERIFY(!p->enteredSurface()); QVERIFY(!cp.enteredSurface()); m_seatInterface->setFocusedPointerSurface(serverSurface, enterTransformation); QCOMPARE(m_seatInterface->focusedPointerSurface(), serverSurface); QVERIFY(enteredSpy.wait()); QCOMPARE(enteredSpy.first().first().value(), m_display->serial()); QTEST(enteredSpy.first().last().toPointF(), "expectedEnterPoint"); PointerInterface *serverPointer = m_seatInterface->focusedPointer(); QVERIFY(serverPointer); QCOMPARE(p->enteredSurface(), s); QCOMPARE(cp.enteredSurface(), s); // test motion m_seatInterface->setTimestamp(1); m_seatInterface->setPointerPos(QPoint(10, 16)); QVERIFY(motionSpy.wait()); QTEST(motionSpy.first().first().toPointF(), "expectedMovePoint"); QCOMPARE(motionSpy.first().last().value(), quint32(1)); // leave the surface m_seatInterface->setFocusedPointerSurface(nullptr); QVERIFY(leftSpy.wait()); QCOMPARE(leftSpy.first().first().value(), m_display->serial()); QVERIFY(!p->enteredSurface()); QVERIFY(!cp.enteredSurface()); // enter it again m_seatInterface->setFocusedPointerSurface(serverSurface); QVERIFY(enteredSpy.wait()); QCOMPARE(p->enteredSurface(), s); QCOMPARE(cp.enteredSurface(), s); delete s; wl_display_flush(m_connection->display()); QTest::qWait(100); QVERIFY(!m_seatInterface->focusedPointerSurface()); } Q_DECLARE_METATYPE(Qt::MouseButton) void TestWaylandSeat::testPointerButton_data() { QTest::addColumn("qtButton"); QTest::addColumn("waylandButton"); QTest::newRow("left") << Qt::LeftButton << quint32(BTN_LEFT); QTest::newRow("right") << Qt::RightButton << quint32(BTN_RIGHT); QTest::newRow("mid") << Qt::MidButton << quint32(BTN_MIDDLE); QTest::newRow("middle") << Qt::MiddleButton << quint32(BTN_MIDDLE); QTest::newRow("back") << Qt::BackButton << quint32(BTN_BACK); QTest::newRow("x1") << Qt::XButton1 << quint32(BTN_BACK); QTest::newRow("extra1") << Qt::ExtraButton1 << quint32(BTN_BACK); QTest::newRow("forward") << Qt::ForwardButton << quint32(BTN_FORWARD); QTest::newRow("x2") << Qt::XButton2 << quint32(BTN_FORWARD); QTest::newRow("extra2") << Qt::ExtraButton2 << quint32(BTN_FORWARD); QTest::newRow("task") << Qt::TaskButton << quint32(BTN_TASK); QTest::newRow("extra3") << Qt::ExtraButton3 << quint32(BTN_TASK); QTest::newRow("extra4") << Qt::ExtraButton4 << quint32(BTN_EXTRA); QTest::newRow("extra5") << Qt::ExtraButton5 << quint32(BTN_SIDE); QTest::newRow("extra6") << Qt::ExtraButton6 << quint32(0x118); QTest::newRow("extra7") << Qt::ExtraButton7 << quint32(0x119); QTest::newRow("extra8") << Qt::ExtraButton8 << quint32(0x11a); QTest::newRow("extra9") << Qt::ExtraButton9 << quint32(0x11b); QTest::newRow("extra10") << Qt::ExtraButton10 << quint32(0x11c); QTest::newRow("extra11") << Qt::ExtraButton11 << quint32(0x11d); QTest::newRow("extra12") << Qt::ExtraButton12 << quint32(0x11e); QTest::newRow("extra13") << Qt::ExtraButton13 << quint32(0x11f); } void TestWaylandSeat::testPointerButton() { using namespace KWayland::Client; using namespace KWayland::Server; QSignalSpy pointerSpy(m_seat, SIGNAL(hasPointerChanged(bool))); QVERIFY(pointerSpy.isValid()); m_seatInterface->setHasPointer(true); QVERIFY(pointerSpy.wait()); QSignalSpy surfaceCreatedSpy(m_compositorInterface, SIGNAL(surfaceCreated(KWayland::Server::SurfaceInterface*))); QVERIFY(surfaceCreatedSpy.isValid()); m_compositor->createSurface(m_compositor); QVERIFY(surfaceCreatedSpy.wait()); SurfaceInterface *serverSurface = surfaceCreatedSpy.first().first().value(); QVERIFY(serverSurface); QScopedPointer p(m_seat->createPointer()); QVERIFY(p->isValid()); QSignalSpy buttonChangedSpy(p.data(), SIGNAL(buttonStateChanged(quint32,quint32,quint32,KWayland::Client::Pointer::ButtonState))); QVERIFY(buttonChangedSpy.isValid()); wl_display_flush(m_connection->display()); QCoreApplication::processEvents(); m_seatInterface->setPointerPos(QPoint(20, 18)); m_seatInterface->setFocusedPointerSurface(serverSurface, QPoint(10, 15)); QVERIFY(m_seatInterface->focusedPointerSurface()); QVERIFY(m_seatInterface->focusedPointer()); QCoreApplication::processEvents(); m_seatInterface->setFocusedPointerSurface(serverSurface, QPoint(10, 15)); PointerInterface *serverPointer = m_seatInterface->focusedPointer(); QVERIFY(serverPointer); QFETCH(Qt::MouseButton, qtButton); QFETCH(quint32, waylandButton); quint32 msec = QDateTime::currentMSecsSinceEpoch(); QCOMPARE(m_seatInterface->isPointerButtonPressed(waylandButton), false); QCOMPARE(m_seatInterface->isPointerButtonPressed(qtButton), false); m_seatInterface->setTimestamp(msec); m_seatInterface->pointerButtonPressed(qtButton); QCOMPARE(m_seatInterface->isPointerButtonPressed(waylandButton), true); QCOMPARE(m_seatInterface->isPointerButtonPressed(qtButton), true); QVERIFY(buttonChangedSpy.wait()); QCOMPARE(buttonChangedSpy.count(), 1); QCOMPARE(buttonChangedSpy.last().at(0).value(), m_seatInterface->pointerButtonSerial(waylandButton)); QCOMPARE(buttonChangedSpy.last().at(0).value(), m_seatInterface->pointerButtonSerial(qtButton)); QCOMPARE(buttonChangedSpy.last().at(1).value(), msec); QCOMPARE(buttonChangedSpy.last().at(2).value(), waylandButton); QCOMPARE(buttonChangedSpy.last().at(3).value(), Pointer::ButtonState::Pressed); msec = QDateTime::currentMSecsSinceEpoch(); m_seatInterface->setTimestamp(QDateTime::currentMSecsSinceEpoch()); m_seatInterface->pointerButtonReleased(qtButton); QCOMPARE(m_seatInterface->isPointerButtonPressed(waylandButton), false); QCOMPARE(m_seatInterface->isPointerButtonPressed(qtButton), false); QVERIFY(buttonChangedSpy.wait()); QCOMPARE(buttonChangedSpy.count(), 2); QCOMPARE(buttonChangedSpy.last().at(0).value(), m_seatInterface->pointerButtonSerial(waylandButton)); QCOMPARE(buttonChangedSpy.last().at(0).value(), m_seatInterface->pointerButtonSerial(qtButton)); QCOMPARE(buttonChangedSpy.last().at(1).value(), msec); QCOMPARE(buttonChangedSpy.last().at(2).value(), waylandButton); QCOMPARE(buttonChangedSpy.last().at(3).value(), Pointer::ButtonState::Released); } void TestWaylandSeat::testPointerSubSurfaceTree() { // this test verifies that pointer motion on a surface with sub-surfaces sends motion enter/leave to the sub-surface using namespace KWayland::Client; using namespace KWayland::Server; // first create the pointer QSignalSpy hasPointerChangedSpy(m_seat, &Seat::hasPointerChanged); QVERIFY(hasPointerChangedSpy.isValid()); m_seatInterface->setHasPointer(true); QVERIFY(hasPointerChangedSpy.wait()); QScopedPointer pointer(m_seat->createPointer()); // create a sub surface tree // parent surface (100, 100) with one sub surface taking the half of it's size (50, 100) // which has two further children (50, 50) which are overlapping QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated); QVERIFY(surfaceCreatedSpy.isValid()); QScopedPointer parentSurface(m_compositor->createSurface()); QScopedPointer childSurface(m_compositor->createSurface()); QScopedPointer grandChild1Surface(m_compositor->createSurface()); QScopedPointer grandChild2Surface(m_compositor->createSurface()); QScopedPointer childSubSurface(m_subCompositor->createSubSurface(childSurface.data(), parentSurface.data())); QScopedPointer grandChild1SubSurface(m_subCompositor->createSubSurface(grandChild1Surface.data(), childSurface.data())); QScopedPointer grandChild2SubSurface(m_subCompositor->createSubSurface(grandChild2Surface.data(), childSurface.data())); grandChild2SubSurface->setPosition(QPoint(0, 25)); // let's map the surfaces auto render = [this] (Surface *s, const QSize &size) { QImage image(size, QImage::Format_ARGB32); image.fill(Qt::black); s->attachBuffer(m_shm->createBuffer(image)); s->damage(QRect(QPoint(0, 0), size)); s->commit(Surface::CommitFlag::None); }; render(grandChild2Surface.data(), QSize(50, 50)); render(grandChild1Surface.data(), QSize(50, 50)); render(childSurface.data(), QSize(50, 100)); render(parentSurface.data(), QSize(100, 100)); QVERIFY(surfaceCreatedSpy.wait()); auto serverSurface = surfaceCreatedSpy.first().first().value(); QVERIFY(serverSurface->isMapped()); // send in pointer events QSignalSpy enteredSpy(pointer.data(), &Pointer::entered); QVERIFY(enteredSpy.isValid()); QSignalSpy leftSpy(pointer.data(), &Pointer::left); QVERIFY(leftSpy.isValid()); QSignalSpy motionSpy(pointer.data(), &Pointer::motion); QVERIFY(motionSpy.isValid()); // first to the grandChild2 in the overlapped area quint32 timestamp = 1; m_seatInterface->setTimestamp(timestamp++); m_seatInterface->setPointerPos(QPointF(25, 50)); m_seatInterface->setFocusedPointerSurface(serverSurface); QVERIFY(enteredSpy.wait()); QCOMPARE(enteredSpy.count(), 1); QCOMPARE(leftSpy.count(), 0); QCOMPARE(motionSpy.count(), 0); QCOMPARE(enteredSpy.last().last().toPointF(), QPointF(25, 25)); QCOMPARE(pointer->enteredSurface(), grandChild2Surface.data()); // a motion on grandchild2 m_seatInterface->setTimestamp(timestamp++); m_seatInterface->setPointerPos(QPointF(25, 60)); QVERIFY(motionSpy.wait()); QCOMPARE(enteredSpy.count(), 1); QCOMPARE(leftSpy.count(), 0); QCOMPARE(motionSpy.count(), 1); QCOMPARE(motionSpy.last().first().toPointF(), QPointF(25, 35)); // motion which changes to childSurface m_seatInterface->setTimestamp(timestamp++); m_seatInterface->setPointerPos(QPointF(25, 80)); QVERIFY(enteredSpy.wait()); QCOMPARE(enteredSpy.count(), 2); QCOMPARE(leftSpy.count(), 1); QCOMPARE(motionSpy.count(), 1); QCOMPARE(enteredSpy.last().last().toPointF(), QPointF(25, 80)); QCOMPARE(pointer->enteredSurface(), childSurface.data()); // a leave for the whole surface m_seatInterface->setTimestamp(timestamp++); m_seatInterface->setFocusedPointerSurface(nullptr); QVERIFY(leftSpy.wait()); QCOMPARE(enteredSpy.count(), 2); QCOMPARE(leftSpy.count(), 2); QCOMPARE(motionSpy.count(), 1); // a new enter on the main surface m_seatInterface->setTimestamp(timestamp++); m_seatInterface->setPointerPos(QPointF(75, 50)); m_seatInterface->setFocusedPointerSurface(serverSurface); QVERIFY(enteredSpy.wait()); QCOMPARE(enteredSpy.count(), 3); QCOMPARE(leftSpy.count(), 2); QCOMPARE(motionSpy.count(), 1); QCOMPARE(enteredSpy.last().last().toPointF(), QPointF(75, 50)); QCOMPARE(pointer->enteredSurface(), parentSurface.data()); } void TestWaylandSeat::testKeyboardSubSurfaceTreeFromPointer() { // this test verifies that when clicking on a sub-surface the keyboard focus passes to it using namespace KWayland::Client; using namespace KWayland::Server; // first create the pointer QSignalSpy hasPointerChangedSpy(m_seat, &Seat::hasPointerChanged); QVERIFY(hasPointerChangedSpy.isValid()); m_seatInterface->setHasPointer(true); QVERIFY(hasPointerChangedSpy.wait()); QScopedPointer pointer(m_seat->createPointer()); // and create keyboard QSignalSpy hasKeyboardChangedSpy(m_seat, &Seat::hasKeyboardChanged); QVERIFY(hasKeyboardChangedSpy.isValid()); m_seatInterface->setHasKeyboard(true); QVERIFY(hasKeyboardChangedSpy.wait()); QScopedPointer keyboard(m_seat->createKeyboard()); // create a sub surface tree // parent surface (100, 100) with one sub surface taking the half of it's size (50, 100) // which has two further children (50, 50) which are overlapping QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated); QVERIFY(surfaceCreatedSpy.isValid()); QScopedPointer parentSurface(m_compositor->createSurface()); QScopedPointer childSurface(m_compositor->createSurface()); QScopedPointer grandChild1Surface(m_compositor->createSurface()); QScopedPointer grandChild2Surface(m_compositor->createSurface()); QScopedPointer childSubSurface(m_subCompositor->createSubSurface(childSurface.data(), parentSurface.data())); QScopedPointer grandChild1SubSurface(m_subCompositor->createSubSurface(grandChild1Surface.data(), childSurface.data())); QScopedPointer grandChild2SubSurface(m_subCompositor->createSubSurface(grandChild2Surface.data(), childSurface.data())); grandChild2SubSurface->setPosition(QPoint(0, 25)); // let's map the surfaces auto render = [this] (Surface *s, const QSize &size) { QImage image(size, QImage::Format_ARGB32); image.fill(Qt::black); s->attachBuffer(m_shm->createBuffer(image)); s->damage(QRect(QPoint(0, 0), size)); s->commit(Surface::CommitFlag::None); }; render(grandChild2Surface.data(), QSize(50, 50)); render(grandChild1Surface.data(), QSize(50, 50)); render(childSurface.data(), QSize(50, 100)); render(parentSurface.data(), QSize(100, 100)); QVERIFY(surfaceCreatedSpy.wait()); auto serverSurface = surfaceCreatedSpy.first().first().value(); QVERIFY(serverSurface->isMapped()); // pass keyboard focus to the main surface QSignalSpy enterSpy(keyboard.data(), &Keyboard::entered); QVERIFY(enterSpy.isValid()); QSignalSpy leftSpy(keyboard.data(), &Keyboard::left); QVERIFY(leftSpy.isValid()); m_seatInterface->setFocusedKeyboardSurface(serverSurface); QVERIFY(enterSpy.wait()); QCOMPARE(enterSpy.count(), 1); QCOMPARE(leftSpy.count(), 0); QCOMPARE(keyboard->enteredSurface(), parentSurface.data()); // now pass also pointer focus to the surface QSignalSpy pointerEnterSpy(pointer.data(), &Pointer::entered); QVERIFY(pointerEnterSpy.isValid()); quint32 timestamp = 1; m_seatInterface->setTimestamp(timestamp++); m_seatInterface->setPointerPos(QPointF(25, 50)); m_seatInterface->setFocusedPointerSurface(serverSurface); QVERIFY(pointerEnterSpy.wait()); QCOMPARE(pointerEnterSpy.count(), 1); // should not have affected the keyboard QCOMPARE(enterSpy.count(), 1); QCOMPARE(leftSpy.count(), 0); // let's click m_seatInterface->setTimestamp(timestamp++); m_seatInterface->pointerButtonPressed(Qt::LeftButton); m_seatInterface->setTimestamp(timestamp++); m_seatInterface->pointerButtonReleased(Qt::LeftButton); QVERIFY(enterSpy.wait()); QCOMPARE(enterSpy.count(), 2); QCOMPARE(leftSpy.count(), 1); QCOMPARE(keyboard->enteredSurface(), grandChild2Surface.data()); // click on same surface should not trigger another enter m_seatInterface->setTimestamp(timestamp++); m_seatInterface->pointerButtonPressed(Qt::LeftButton); m_seatInterface->setTimestamp(timestamp++); m_seatInterface->pointerButtonReleased(Qt::LeftButton); QVERIFY(!enterSpy.wait(200)); QCOMPARE(enterSpy.count(), 2); QCOMPARE(leftSpy.count(), 1); QCOMPARE(keyboard->enteredSurface(), grandChild2Surface.data()); // unfocus keyboard m_seatInterface->setFocusedKeyboardSurface(nullptr); QVERIFY(leftSpy.wait()); QCOMPARE(enterSpy.count(), 2); QCOMPARE(leftSpy.count(), 2); } void TestWaylandSeat::testCursor() { using namespace KWayland::Client; using namespace KWayland::Server; QSignalSpy pointerSpy(m_seat, SIGNAL(hasPointerChanged(bool))); QVERIFY(pointerSpy.isValid()); m_seatInterface->setHasPointer(true); QVERIFY(pointerSpy.wait()); QSignalSpy surfaceCreatedSpy(m_compositorInterface, SIGNAL(surfaceCreated(KWayland::Server::SurfaceInterface*))); QVERIFY(surfaceCreatedSpy.isValid()); m_compositor->createSurface(m_compositor); QVERIFY(surfaceCreatedSpy.wait()); SurfaceInterface *serverSurface = surfaceCreatedSpy.first().first().value(); QVERIFY(serverSurface); QScopedPointer p(m_seat->createPointer()); QVERIFY(p->isValid()); wl_display_flush(m_connection->display()); QCoreApplication::processEvents(); QSignalSpy enteredSpy(p.data(), SIGNAL(entered(quint32,QPointF))); QVERIFY(enteredSpy.isValid()); m_seatInterface->setPointerPos(QPoint(20, 18)); m_seatInterface->setFocusedPointerSurface(serverSurface, QPoint(10, 15)); quint32 serial = m_seatInterface->display()->serial(); QVERIFY(enteredSpy.wait()); QCOMPARE(enteredSpy.first().first().value(), serial); QVERIFY(m_seatInterface->focusedPointerSurface()); QVERIFY(m_seatInterface->focusedPointer()); QVERIFY(!m_seatInterface->focusedPointer()->cursor()); QSignalSpy cursorChangedSpy(m_seatInterface->focusedPointer(), SIGNAL(cursorChanged())); QVERIFY(cursorChangedSpy.isValid()); // just remove the pointer p->setCursor(nullptr); QVERIFY(cursorChangedSpy.wait()); QCOMPARE(cursorChangedSpy.count(), 1); auto cursor = m_seatInterface->focusedPointer()->cursor(); QVERIFY(cursor); QVERIFY(!cursor->surface()); QCOMPARE(cursor->hotspot(), QPoint()); QCOMPARE(cursor->enteredSerial(), serial); QCOMPARE(cursor->pointer(), m_seatInterface->focusedPointer()); QSignalSpy hotspotChangedSpy(cursor, SIGNAL(hotspotChanged())); QVERIFY(hotspotChangedSpy.isValid()); QSignalSpy surfaceChangedSpy(cursor, SIGNAL(surfaceChanged())); QVERIFY(surfaceChangedSpy.isValid()); QSignalSpy enteredSerialChangedSpy(cursor, SIGNAL(enteredSerialChanged())); QVERIFY(enteredSerialChangedSpy.isValid()); QSignalSpy changedSpy(cursor, SIGNAL(changed())); QVERIFY(changedSpy.isValid()); // test changing hotspot p->setCursor(nullptr, QPoint(1, 2)); QVERIFY(hotspotChangedSpy.wait()); QCOMPARE(hotspotChangedSpy.count(), 1); QCOMPARE(changedSpy.count(), 1); QCOMPARE(cursorChangedSpy.count(), 2); QCOMPARE(cursor->hotspot(), QPoint(1, 2)); QVERIFY(enteredSerialChangedSpy.isEmpty()); QVERIFY(surfaceChangedSpy.isEmpty()); // set surface auto cursorSurface = m_compositor->createSurface(m_compositor); QVERIFY(cursorSurface->isValid()); p->setCursor(cursorSurface, QPoint(1, 2)); QVERIFY(surfaceChangedSpy.wait()); QCOMPARE(surfaceChangedSpy.count(), 1); QCOMPARE(changedSpy.count(), 2); QCOMPARE(cursorChangedSpy.count(), 3); QVERIFY(enteredSerialChangedSpy.isEmpty()); QCOMPARE(cursor->hotspot(), QPoint(1, 2)); QVERIFY(cursor->surface()); // and add an image to the surface QImage img(QSize(10, 20), QImage::Format_RGB32); img.fill(Qt::red); cursorSurface->attachBuffer(m_shm->createBuffer(img)); cursorSurface->damage(QRect(0, 0, 10, 20)); cursorSurface->commit(Surface::CommitFlag::None); QVERIFY(changedSpy.wait()); QCOMPARE(changedSpy.count(), 3); QCOMPARE(cursorChangedSpy.count(), 4); QCOMPARE(surfaceChangedSpy.count(), 1); QCOMPARE(cursor->surface()->buffer()->data(), img); // and add another image to the surface QImage blue(QSize(10, 20), QImage::Format_ARGB32); blue.fill(Qt::blue); cursorSurface->attachBuffer(m_shm->createBuffer(blue)); cursorSurface->damage(QRect(0, 0, 10, 20)); cursorSurface->commit(Surface::CommitFlag::None); QVERIFY(changedSpy.wait()); QCOMPARE(changedSpy.count(), 4); QCOMPARE(cursorChangedSpy.count(), 5); QCOMPARE(cursor->surface()->buffer()->data(), blue); p->hideCursor(); QVERIFY(surfaceChangedSpy.wait()); QCOMPARE(changedSpy.count(), 5); QCOMPARE(cursorChangedSpy.count(), 6); QCOMPARE(surfaceChangedSpy.count(), 2); QVERIFY(!cursor->surface()); } void TestWaylandSeat::testCursorDamage() { // this test verifies that damaging a cursor surface triggers a cursor changed on the server using namespace KWayland::Client; using namespace KWayland::Server; QSignalSpy pointerSpy(m_seat, &Seat::hasPointerChanged); QVERIFY(pointerSpy.isValid()); m_seatInterface->setHasPointer(true); QVERIFY(pointerSpy.wait()); // create pointer QScopedPointer p(m_seat->createPointer()); QVERIFY(p->isValid()); QSignalSpy enteredSpy(p.data(), &Pointer::entered); QVERIFY(enteredSpy.isValid()); // create surface QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated); QVERIFY(surfaceCreatedSpy.isValid()); m_compositor->createSurface(m_compositor); QVERIFY(surfaceCreatedSpy.wait()); SurfaceInterface *serverSurface = surfaceCreatedSpy.first().first().value(); QVERIFY(serverSurface); // send enter to the surface m_seatInterface->setFocusedPointerSurface(serverSurface); QVERIFY(enteredSpy.wait()); // create a signal spy for the cursor changed signal auto pointer = m_seatInterface->focusedPointer(); QSignalSpy cursorChangedSpy(pointer, &PointerInterface::cursorChanged); QVERIFY(cursorChangedSpy.isValid()); // now let's set the cursor Surface *cursorSurface = m_compositor->createSurface(m_compositor); QVERIFY(cursorSurface); QImage red(QSize(10, 10), QImage::Format_ARGB32); red.fill(Qt::red); cursorSurface->attachBuffer(m_shm->createBuffer(red)); cursorSurface->damage(QRect(0, 0, 10, 10)); cursorSurface->commit(Surface::CommitFlag::None); p->setCursor(cursorSurface, QPoint(0, 0)); QVERIFY(cursorChangedSpy.wait()); QCOMPARE(pointer->cursor()->surface()->buffer()->data(), red); // and damage the surface QImage blue(QSize(10, 10), QImage::Format_ARGB32); blue.fill(Qt::blue); cursorSurface->attachBuffer(m_shm->createBuffer(blue)); cursorSurface->damage(QRect(0, 0, 10, 10)); cursorSurface->commit(Surface::CommitFlag::None); QVERIFY(cursorChangedSpy.wait()); QCOMPARE(pointer->cursor()->surface()->buffer()->data(), blue); } void TestWaylandSeat::testKeyboard() { using namespace KWayland::Client; using namespace KWayland::Server; QSignalSpy keyboardSpy(m_seat, SIGNAL(hasKeyboardChanged(bool))); QVERIFY(keyboardSpy.isValid()); m_seatInterface->setHasKeyboard(true); QVERIFY(keyboardSpy.wait()); // create the surface QSignalSpy surfaceCreatedSpy(m_compositorInterface, SIGNAL(surfaceCreated(KWayland::Server::SurfaceInterface*))); QVERIFY(surfaceCreatedSpy.isValid()); Surface *s = m_compositor->createSurface(m_compositor); QVERIFY(surfaceCreatedSpy.wait()); SurfaceInterface *serverSurface = surfaceCreatedSpy.first().first().value(); QVERIFY(serverSurface); m_seatInterface->setFocusedKeyboardSurface(serverSurface); // no keyboard yet QCOMPARE(m_seatInterface->focusedKeyboardSurface(), serverSurface); QVERIFY(!m_seatInterface->focusedKeyboard()); Keyboard *keyboard = m_seat->createKeyboard(m_seat); QSignalSpy repeatInfoSpy(keyboard, &Keyboard::keyRepeatChanged); QVERIFY(repeatInfoSpy.isValid()); const Keyboard &ckeyboard = *keyboard; QVERIFY(keyboard->isValid()); QCOMPARE(keyboard->isKeyRepeatEnabled(), false); QCOMPARE(keyboard->keyRepeatDelay(), 0); QCOMPARE(keyboard->keyRepeatRate(), 0); wl_display_flush(m_connection->display()); QTest::qWait(100); auto serverKeyboard = m_seatInterface->focusedKeyboard(); QVERIFY(serverKeyboard); // we should get the repeat info announced QCOMPARE(repeatInfoSpy.count(), 1); QCOMPARE(keyboard->isKeyRepeatEnabled(), false); QCOMPARE(keyboard->keyRepeatDelay(), 0); QCOMPARE(keyboard->keyRepeatRate(), 0); // let's change repeat in server m_seatInterface->setKeyRepeatInfo(25, 660); m_seatInterface->focusedKeyboard()->client()->flush(); QVERIFY(repeatInfoSpy.wait()); QCOMPARE(repeatInfoSpy.count(), 2); QCOMPARE(keyboard->isKeyRepeatEnabled(), true); QCOMPARE(keyboard->keyRepeatRate(), 25); QCOMPARE(keyboard->keyRepeatDelay(), 660); m_seatInterface->setTimestamp(1); m_seatInterface->keyPressed(KEY_K); m_seatInterface->setTimestamp(2); m_seatInterface->keyPressed(KEY_D); m_seatInterface->setTimestamp(3); m_seatInterface->keyPressed(KEY_E); QSignalSpy modifierSpy(keyboard, SIGNAL(modifiersChanged(quint32,quint32,quint32,quint32))); QVERIFY(modifierSpy.isValid()); QSignalSpy enteredSpy(keyboard, SIGNAL(entered(quint32))); QVERIFY(enteredSpy.isValid()); m_seatInterface->setFocusedKeyboardSurface(serverSurface); QCOMPARE(m_seatInterface->focusedKeyboardSurface(), serverSurface); QCOMPARE(m_seatInterface->focusedKeyboard()->focusedSurface(), serverSurface); // we get the modifiers sent after the enter QVERIFY(modifierSpy.wait()); QCOMPARE(modifierSpy.count(), 1); QCOMPARE(modifierSpy.first().at(0).value(), quint32(0)); QCOMPARE(modifierSpy.first().at(1).value(), quint32(0)); QCOMPARE(modifierSpy.first().at(2).value(), quint32(0)); QCOMPARE(modifierSpy.first().at(3).value(), quint32(0)); QCOMPARE(enteredSpy.count(), 1); // TODO: get through API QCOMPARE(enteredSpy.first().first().value(), m_display->serial() - 1); QSignalSpy keyChangedSpy(keyboard, SIGNAL(keyChanged(quint32,KWayland::Client::Keyboard::KeyState,quint32))); QVERIFY(keyChangedSpy.isValid()); m_seatInterface->setTimestamp(4); m_seatInterface->keyReleased(KEY_E); QVERIFY(keyChangedSpy.wait()); m_seatInterface->setTimestamp(5); m_seatInterface->keyReleased(KEY_D); QVERIFY(keyChangedSpy.wait()); m_seatInterface->setTimestamp(6); m_seatInterface->keyReleased(KEY_K); QVERIFY(keyChangedSpy.wait()); m_seatInterface->setTimestamp(7); m_seatInterface->keyPressed(KEY_F1); QVERIFY(keyChangedSpy.wait()); m_seatInterface->setTimestamp(8); m_seatInterface->keyReleased(KEY_F1); QVERIFY(keyChangedSpy.wait()); QCOMPARE(keyChangedSpy.count(), 5); QCOMPARE(keyChangedSpy.at(0).at(0).value(), quint32(KEY_E)); QCOMPARE(keyChangedSpy.at(0).at(1).value(), Keyboard::KeyState::Released); QCOMPARE(keyChangedSpy.at(0).at(2).value(), quint32(4)); QCOMPARE(keyChangedSpy.at(1).at(0).value(), quint32(KEY_D)); QCOMPARE(keyChangedSpy.at(1).at(1).value(), Keyboard::KeyState::Released); QCOMPARE(keyChangedSpy.at(1).at(2).value(), quint32(5)); QCOMPARE(keyChangedSpy.at(2).at(0).value(), quint32(KEY_K)); QCOMPARE(keyChangedSpy.at(2).at(1).value(), Keyboard::KeyState::Released); QCOMPARE(keyChangedSpy.at(2).at(2).value(), quint32(6)); QCOMPARE(keyChangedSpy.at(3).at(0).value(), quint32(KEY_F1)); QCOMPARE(keyChangedSpy.at(3).at(1).value(), Keyboard::KeyState::Pressed); QCOMPARE(keyChangedSpy.at(3).at(2).value(), quint32(7)); QCOMPARE(keyChangedSpy.at(4).at(0).value(), quint32(KEY_F1)); QCOMPARE(keyChangedSpy.at(4).at(1).value(), Keyboard::KeyState::Released); QCOMPARE(keyChangedSpy.at(4).at(2).value(), quint32(8)); m_seatInterface->updateKeyboardModifiers(1, 2, 3, 4); QVERIFY(modifierSpy.wait()); QCOMPARE(modifierSpy.count(), 2); QCOMPARE(modifierSpy.last().at(0).value(), quint32(1)); QCOMPARE(modifierSpy.last().at(1).value(), quint32(2)); QCOMPARE(modifierSpy.last().at(2).value(), quint32(3)); QCOMPARE(modifierSpy.last().at(3).value(), quint32(4)); QSignalSpy leftSpy(keyboard, SIGNAL(left(quint32))); QVERIFY(leftSpy.isValid()); m_seatInterface->setFocusedKeyboardSurface(nullptr); QVERIFY(!m_seatInterface->focusedKeyboardSurface()); QVERIFY(!m_seatInterface->focusedKeyboard()); QVERIFY(leftSpy.wait()); QCOMPARE(leftSpy.count(), 1); // TODO: get through API QCOMPARE(leftSpy.first().first().value(), m_display->serial() -1 ); QVERIFY(!keyboard->enteredSurface()); QVERIFY(!ckeyboard.enteredSurface()); // enter it again m_seatInterface->setFocusedKeyboardSurface(serverSurface); QVERIFY(modifierSpy.wait()); QCOMPARE(m_seatInterface->focusedKeyboardSurface(), serverSurface); QCOMPARE(m_seatInterface->focusedKeyboard()->focusedSurface(), serverSurface); QCOMPARE(enteredSpy.count(), 2); QCOMPARE(keyboard->enteredSurface(), s); QCOMPARE(ckeyboard.enteredSurface(), s); QSignalSpy serverSurfaceDestroyedSpy(serverSurface, &QObject::destroyed); QVERIFY(serverSurfaceDestroyedSpy.isValid()); delete s; QVERIFY(serverSurfaceDestroyedSpy.wait()); QVERIFY(!m_seatInterface->focusedKeyboardSurface()); QVERIFY(!m_seatInterface->focusedKeyboard()); QVERIFY(!serverKeyboard->focusedSurface()); // let's create a Surface again QScopedPointer s2(m_compositor->createSurface()); QVERIFY(surfaceCreatedSpy.wait()); QCOMPARE(surfaceCreatedSpy.count(), 2); serverSurface = surfaceCreatedSpy.last().first().value(); QVERIFY(serverSurface); m_seatInterface->setFocusedKeyboardSurface(serverSurface); QCOMPARE(m_seatInterface->focusedKeyboardSurface(), serverSurface); QCOMPARE(m_seatInterface->focusedKeyboard(), serverKeyboard); // delete the Keyboard QSignalSpy unboundSpy(serverKeyboard, &Resource::unbound); QVERIFY(unboundSpy.isValid()); QSignalSpy destroyedSpy(serverKeyboard, &Resource::destroyed); QVERIFY(destroyedSpy.isValid()); delete keyboard; QVERIFY(unboundSpy.wait()); QCOMPARE(unboundSpy.count(), 1); QCOMPARE(destroyedSpy.count(), 0); // verify that calling into the Keyboard related functionality doesn't crash m_seatInterface->setTimestamp(9); m_seatInterface->keyPressed(KEY_F2); m_seatInterface->setTimestamp(10); m_seatInterface->keyReleased(KEY_F2); m_seatInterface->setKeyRepeatInfo(30, 560); m_seatInterface->setKeyRepeatInfo(25, 660); m_seatInterface->updateKeyboardModifiers(5, 6, 7, 8); m_seatInterface->setKeymap(open("/dev/null", O_RDONLY), 0); m_seatInterface->setFocusedKeyboardSurface(nullptr); m_seatInterface->setFocusedKeyboardSurface(serverSurface); QCOMPARE(m_seatInterface->focusedKeyboardSurface(), serverSurface); QVERIFY(!m_seatInterface->focusedKeyboard()); QVERIFY(destroyedSpy.wait()); QCOMPARE(destroyedSpy.count(), 1); // create a second Keyboard to verify that repeat info is announced properly Keyboard *keyboard2 = m_seat->createKeyboard(m_seat); QSignalSpy repeatInfoSpy2(keyboard2, &Keyboard::keyRepeatChanged); QVERIFY(repeatInfoSpy2.isValid()); QVERIFY(keyboard2->isValid()); QCOMPARE(keyboard2->isKeyRepeatEnabled(), false); QCOMPARE(keyboard2->keyRepeatDelay(), 0); QCOMPARE(keyboard2->keyRepeatRate(), 0); wl_display_flush(m_connection->display()); QVERIFY(repeatInfoSpy2.wait()); QCOMPARE(keyboard2->isKeyRepeatEnabled(), true); QCOMPARE(keyboard2->keyRepeatRate(), 25); QCOMPARE(keyboard2->keyRepeatDelay(), 660); QCOMPARE(m_seatInterface->focusedKeyboardSurface(), serverSurface); serverKeyboard = m_seatInterface->focusedKeyboard(); QVERIFY(serverKeyboard); QSignalSpy keyboard2DestroyedSpy(serverKeyboard, &QObject::destroyed); QVERIFY(keyboard2DestroyedSpy.isValid()); delete keyboard2; QVERIFY(keyboard2DestroyedSpy.wait()); // this should have unset it on the server QVERIFY(!m_seatInterface->focusedKeyboard()); // but not the surface QCOMPARE(m_seatInterface->focusedKeyboardSurface(), serverSurface); } void TestWaylandSeat::testCast() { using namespace KWayland::Client; Registry registry; QSignalSpy seatSpy(®istry, SIGNAL(seatAnnounced(quint32,quint32))); registry.create(m_connection->display()); QVERIFY(registry.isValid()); registry.setup(); QVERIFY(seatSpy.wait()); Seat s; QVERIFY(!s.isValid()); auto wlSeat = registry.bindSeat(seatSpy.first().first().value(), seatSpy.first().last().value()); QVERIFY(wlSeat); s.setup(wlSeat); QVERIFY(s.isValid()); QCOMPARE((wl_seat*)s, wlSeat); const Seat &s2(s); QCOMPARE((wl_seat*)s2, wlSeat); } void TestWaylandSeat::testDestroy() { using namespace KWayland::Client; QSignalSpy keyboardSpy(m_seat, SIGNAL(hasKeyboardChanged(bool))); QVERIFY(keyboardSpy.isValid()); m_seatInterface->setHasKeyboard(true); QVERIFY(keyboardSpy.wait()); Keyboard *k = m_seat->createKeyboard(m_seat); QVERIFY(k->isValid()); QSignalSpy pointerSpy(m_seat, SIGNAL(hasPointerChanged(bool))); QVERIFY(pointerSpy.isValid()); m_seatInterface->setHasPointer(true); QVERIFY(pointerSpy.wait()); Pointer *p = m_seat->createPointer(m_seat); QVERIFY(p->isValid()); QSignalSpy touchSpy(m_seat, SIGNAL(hasTouchChanged(bool))); QVERIFY(touchSpy.isValid()); m_seatInterface->setHasTouch(true); QVERIFY(touchSpy.wait()); Touch *t = m_seat->createTouch(m_seat); QVERIFY(t->isValid()); delete m_compositor; m_compositor = nullptr; connect(m_connection, &ConnectionThread::connectionDied, m_seat, &Seat::destroy); connect(m_connection, &ConnectionThread::connectionDied, m_shm, &ShmPool::destroy); connect(m_connection, &ConnectionThread::connectionDied, m_subCompositor, &SubCompositor::destroy); connect(m_connection, &ConnectionThread::connectionDied, m_queue, &EventQueue::destroy); QVERIFY(m_seat->isValid()); QSignalSpy connectionDiedSpy(m_connection, SIGNAL(connectionDied())); QVERIFY(connectionDiedSpy.isValid()); delete m_display; m_display = nullptr; m_compositorInterface = nullptr; m_seatInterface = nullptr; m_subCompositorInterface = nullptr; QVERIFY(connectionDiedSpy.wait()); // now the seat should be destroyed; QVERIFY(!m_seat->isValid()); QVERIFY(!k->isValid()); QVERIFY(!p->isValid()); QVERIFY(!t->isValid()); // calling destroy again should not fail m_seat->destroy(); k->destroy(); p->destroy(); t->destroy(); } void TestWaylandSeat::testSelection() { using namespace KWayland::Client; using namespace KWayland::Server; QScopedPointer ddmi(m_display->createDataDeviceManager()); ddmi->create(); Registry registry; QSignalSpy dataDeviceManagerSpy(®istry, SIGNAL(dataDeviceManagerAnnounced(quint32,quint32))); QVERIFY(dataDeviceManagerSpy.isValid()); registry.setEventQueue(m_queue); registry.create(m_connection->display()); QVERIFY(registry.isValid()); registry.setup(); QVERIFY(dataDeviceManagerSpy.wait()); QScopedPointer ddm(registry.createDataDeviceManager(dataDeviceManagerSpy.first().first().value(), dataDeviceManagerSpy.first().last().value())); QVERIFY(ddm->isValid()); QScopedPointer dd1(ddm->getDataDevice(m_seat)); QVERIFY(dd1->isValid()); QSignalSpy selectionSpy(dd1.data(), SIGNAL(selectionOffered(KWayland::Client::DataOffer*))); QVERIFY(selectionSpy.isValid()); QSignalSpy selectionClearedSpy(dd1.data(), SIGNAL(selectionCleared())); QVERIFY(selectionClearedSpy.isValid()); QSignalSpy surfaceCreatedSpy(m_compositorInterface, SIGNAL(surfaceCreated(KWayland::Server::SurfaceInterface*))); QVERIFY(surfaceCreatedSpy.isValid()); QScopedPointer surface(m_compositor->createSurface()); QVERIFY(surface->isValid()); QVERIFY(surfaceCreatedSpy.wait()); auto serverSurface = surfaceCreatedSpy.first().first().value(); QVERIFY(!m_seatInterface->selection()); m_seatInterface->setFocusedKeyboardSurface(serverSurface); QCOMPARE(m_seatInterface->focusedKeyboardSurface(), serverSurface); QVERIFY(!m_seatInterface->focusedKeyboard()); - serverSurface->client()->flush(); - QCoreApplication::processEvents(); - QCoreApplication::processEvents(); + QVERIFY(selectionClearedSpy.wait()); QVERIFY(selectionSpy.isEmpty()); - QVERIFY(selectionClearedSpy.isEmpty()); + QVERIFY(!selectionClearedSpy.isEmpty()); + selectionClearedSpy.clear(); QVERIFY(!m_seatInterface->selection()); // now let's try to set a selection - we have keyboard focus, so it should be sent to us QScopedPointer ds(ddm->createDataSource()); QVERIFY(ds->isValid()); ds->offer(QStringLiteral("text/plain")); dd1->setSelection(0, ds.data()); QVERIFY(selectionSpy.wait()); QCOMPARE(selectionSpy.count(), 1); auto ddi = m_seatInterface->selection(); QVERIFY(ddi); - QVERIFY(selectionClearedSpy.isEmpty()); auto df = selectionSpy.first().first().value(); QCOMPARE(df->offeredMimeTypes().count(), 1); QCOMPARE(df->offeredMimeTypes().first().name(), QStringLiteral("text/plain")); // try to clear dd1->setSelection(0); QVERIFY(selectionClearedSpy.wait()); QCOMPARE(selectionClearedSpy.count(), 1); QCOMPARE(selectionSpy.count(), 1); // unset the keyboard focus m_seatInterface->setFocusedKeyboardSurface(nullptr); QVERIFY(!m_seatInterface->focusedKeyboardSurface()); QVERIFY(!m_seatInterface->focusedKeyboard()); serverSurface->client()->flush(); QCoreApplication::processEvents(); QCoreApplication::processEvents(); // try to set Selection dd1->setSelection(0, ds.data()); wl_display_flush(m_connection->display()); QCoreApplication::processEvents(); QCoreApplication::processEvents(); QCOMPARE(selectionSpy.count(), 1); // let's unset the selection on the seat m_seatInterface->setSelection(nullptr); // and pass focus back on our surface m_seatInterface->setFocusedKeyboardSurface(serverSurface); // we don't have a selection, so it should not send a selection QVERIFY(!selectionSpy.wait(100)); // now let's set it manually m_seatInterface->setSelection(ddi); QCOMPARE(m_seatInterface->selection(), ddi); QVERIFY(selectionSpy.wait()); QCOMPARE(selectionSpy.count(), 2); // setting the same again should not change m_seatInterface->setSelection(ddi); QVERIFY(!selectionSpy.wait(100)); // now clear it manully m_seatInterface->setSelection(nullptr); QVERIFY(selectionClearedSpy.wait()); QCOMPARE(selectionSpy.count(), 2); } void TestWaylandSeat::testTouch() { using namespace KWayland::Client; using namespace KWayland::Server; QSignalSpy touchSpy(m_seat, SIGNAL(hasTouchChanged(bool))); QVERIFY(touchSpy.isValid()); m_seatInterface->setHasTouch(true); QVERIFY(touchSpy.wait()); // create the surface QSignalSpy surfaceCreatedSpy(m_compositorInterface, SIGNAL(surfaceCreated(KWayland::Server::SurfaceInterface*))); QVERIFY(surfaceCreatedSpy.isValid()); Surface *s = m_compositor->createSurface(m_compositor); QVERIFY(surfaceCreatedSpy.wait()); SurfaceInterface *serverSurface = surfaceCreatedSpy.first().first().value(); QVERIFY(serverSurface); m_seatInterface->setFocusedTouchSurface(serverSurface); // no keyboard yet QCOMPARE(m_seatInterface->focusedTouchSurface(), serverSurface); QVERIFY(!m_seatInterface->focusedTouch()); QSignalSpy touchCreatedSpy(m_seatInterface, SIGNAL(touchCreated(KWayland::Server::TouchInterface*))); QVERIFY(touchCreatedSpy.isValid()); Touch *touch = m_seat->createTouch(m_seat); QVERIFY(touch->isValid()); QVERIFY(touchCreatedSpy.wait()); auto serverTouch = m_seatInterface->focusedTouch(); QVERIFY(serverTouch); QCOMPARE(touchCreatedSpy.first().first().value(), m_seatInterface->focusedTouch()); QSignalSpy sequenceStartedSpy(touch, SIGNAL(sequenceStarted(KWayland::Client::TouchPoint*))); QVERIFY(sequenceStartedSpy.isValid()); QSignalSpy sequenceEndedSpy(touch, SIGNAL(sequenceEnded())); QVERIFY(sequenceEndedSpy.isValid()); QSignalSpy sequenceCanceledSpy(touch, SIGNAL(sequenceCanceled())); QVERIFY(sequenceCanceledSpy.isValid()); QSignalSpy frameEndedSpy(touch, SIGNAL(frameEnded())); QVERIFY(frameEndedSpy.isValid()); QSignalSpy pointAddedSpy(touch, SIGNAL(pointAdded(KWayland::Client::TouchPoint*))); QVERIFY(pointAddedSpy.isValid()); QSignalSpy pointMovedSpy(touch, SIGNAL(pointMoved(KWayland::Client::TouchPoint*))); QVERIFY(pointMovedSpy.isValid()); QSignalSpy pointRemovedSpy(touch, SIGNAL(pointRemoved(KWayland::Client::TouchPoint*))); QVERIFY(pointRemovedSpy.isValid()); // try a few things m_seatInterface->setFocusedTouchSurfacePosition(QPointF(10, 20)); QCOMPARE(m_seatInterface->focusedTouchSurfacePosition(), QPointF(10, 20)); m_seatInterface->setTimestamp(1); QCOMPARE(m_seatInterface->touchDown(QPointF(15, 26)), 0); QVERIFY(sequenceStartedSpy.wait()); QCOMPARE(sequenceStartedSpy.count(), 1); QCOMPARE(sequenceEndedSpy.count(), 0); QCOMPARE(sequenceCanceledSpy.count(), 0); QCOMPARE(frameEndedSpy.count(), 0); QCOMPARE(pointAddedSpy.count(), 0); QCOMPARE(pointMovedSpy.count(), 0); QCOMPARE(pointRemovedSpy.count(), 0); TouchPoint *tp = sequenceStartedSpy.first().first().value(); QVERIFY(tp); QCOMPARE(tp->downSerial(), m_seatInterface->display()->serial()); QCOMPARE(tp->id(), 0); QVERIFY(tp->isDown()); QCOMPARE(tp->position(), QPointF(5, 6)); QCOMPARE(tp->positions().size(), 1); QCOMPARE(tp->time(), 1u); QCOMPARE(tp->timestamps().count(), 1); QCOMPARE(tp->upSerial(), 0u); QCOMPARE(tp->surface().data(), s); QCOMPARE(touch->sequence().count(), 1); QCOMPARE(touch->sequence().first(), tp); // let's end the frame m_seatInterface->touchFrame(); QVERIFY(frameEndedSpy.wait()); QCOMPARE(frameEndedSpy.count(), 1); // move the one point m_seatInterface->setTimestamp(2); m_seatInterface->touchMove(0, QPointF(10, 20)); m_seatInterface->touchFrame(); QVERIFY(frameEndedSpy.wait()); QCOMPARE(sequenceStartedSpy.count(), 1); QCOMPARE(sequenceEndedSpy.count(), 0); QCOMPARE(sequenceCanceledSpy.count(), 0); QCOMPARE(frameEndedSpy.count(), 2); QCOMPARE(pointAddedSpy.count(), 0); QCOMPARE(pointMovedSpy.count(), 1); QCOMPARE(pointRemovedSpy.count(), 0); QCOMPARE(pointMovedSpy.first().first().value(), tp); QCOMPARE(tp->id(), 0); QVERIFY(tp->isDown()); QCOMPARE(tp->position(), QPointF(0, 0)); QCOMPARE(tp->positions().size(), 2); QCOMPARE(tp->time(), 2u); QCOMPARE(tp->timestamps().count(), 2); QCOMPARE(tp->upSerial(), 0u); QCOMPARE(tp->surface().data(), s); // add onther point m_seatInterface->setTimestamp(3); QCOMPARE(m_seatInterface->touchDown(QPointF(15, 26)), 1); m_seatInterface->touchFrame(); QVERIFY(frameEndedSpy.wait()); QCOMPARE(sequenceStartedSpy.count(), 1); QCOMPARE(sequenceEndedSpy.count(), 0); QCOMPARE(sequenceCanceledSpy.count(), 0); QCOMPARE(frameEndedSpy.count(), 3); QCOMPARE(pointAddedSpy.count(), 1); QCOMPARE(pointMovedSpy.count(), 1); QCOMPARE(pointRemovedSpy.count(), 0); QCOMPARE(touch->sequence().count(), 2); QCOMPARE(touch->sequence().first(), tp); TouchPoint *tp2 = pointAddedSpy.first().first().value(); QVERIFY(tp2); QCOMPARE(touch->sequence().last(), tp2); QCOMPARE(tp2->id(), 1); QVERIFY(tp2->isDown()); QCOMPARE(tp2->position(), QPointF(5, 6)); QCOMPARE(tp2->positions().size(), 1); QCOMPARE(tp2->time(), 3u); QCOMPARE(tp2->timestamps().count(), 1); QCOMPARE(tp2->upSerial(), 0u); QCOMPARE(tp2->surface().data(), s); // send it an up m_seatInterface->setTimestamp(4); m_seatInterface->touchUp(1); m_seatInterface->touchFrame(); QVERIFY(frameEndedSpy.wait()); QCOMPARE(sequenceStartedSpy.count(), 1); QCOMPARE(sequenceEndedSpy.count(), 0); QCOMPARE(sequenceCanceledSpy.count(), 0); QCOMPARE(frameEndedSpy.count(), 4); QCOMPARE(pointAddedSpy.count(), 1); QCOMPARE(pointMovedSpy.count(), 1); QCOMPARE(pointRemovedSpy.count(), 1); QCOMPARE(pointRemovedSpy.first().first().value(), tp2); QCOMPARE(tp2->id(), 1); QVERIFY(!tp2->isDown()); QCOMPARE(tp2->position(), QPointF(5, 6)); QCOMPARE(tp2->positions().size(), 1); QCOMPARE(tp2->time(), 4u); QCOMPARE(tp2->timestamps().count(), 2); QCOMPARE(tp2->upSerial(), m_seatInterface->display()->serial()); QCOMPARE(tp2->surface().data(), s); // send another down and up m_seatInterface->setTimestamp(5); QCOMPARE(m_seatInterface->touchDown(QPointF(15, 26)), 1); m_seatInterface->touchFrame(); m_seatInterface->setTimestamp(6); m_seatInterface->touchUp(1); // and send an up for the first point m_seatInterface->touchUp(0); m_seatInterface->touchFrame(); QVERIFY(frameEndedSpy.wait()); QCOMPARE(sequenceStartedSpy.count(), 1); QCOMPARE(sequenceEndedSpy.count(), 1); QCOMPARE(sequenceCanceledSpy.count(), 0); QCOMPARE(frameEndedSpy.count(), 6); QCOMPARE(pointAddedSpy.count(), 2); QCOMPARE(pointMovedSpy.count(), 1); QCOMPARE(pointRemovedSpy.count(), 3); QCOMPARE(touch->sequence().count(), 3); QVERIFY(!touch->sequence().at(0)->isDown()); QVERIFY(!touch->sequence().at(1)->isDown()); QVERIFY(!touch->sequence().at(2)->isDown()); QVERIFY(!m_seatInterface->isTouchSequence()); // try cancel m_seatInterface->setFocusedTouchSurface(serverSurface, QPointF(15, 26)); m_seatInterface->setTimestamp(7); QCOMPARE(m_seatInterface->touchDown(QPointF(15, 26)), 0); m_seatInterface->touchFrame(); m_seatInterface->cancelTouchSequence(); QVERIFY(sequenceCanceledSpy.wait()); QCOMPARE(sequenceStartedSpy.count(), 2); QCOMPARE(sequenceEndedSpy.count(), 1); QCOMPARE(sequenceCanceledSpy.count(), 1); QCOMPARE(frameEndedSpy.count(), 7); QCOMPARE(pointAddedSpy.count(), 2); QCOMPARE(pointMovedSpy.count(), 1); QCOMPARE(pointRemovedSpy.count(), 3); QCOMPARE(touch->sequence().first()->position(), QPointF(0, 0)); // destroy touch on client side QSignalSpy unboundSpy(serverTouch, &TouchInterface::unbound); QVERIFY(unboundSpy.isValid()); QSignalSpy destroyedSpy(serverTouch, &TouchInterface::destroyed); QVERIFY(destroyedSpy.isValid()); delete touch; QVERIFY(unboundSpy.wait()); QCOMPARE(unboundSpy.count(), 1); QCOMPARE(destroyedSpy.count(), 0); QVERIFY(!serverTouch->resource()); // try to call into all the the methods of the touch interface, should not crash QCOMPARE(m_seatInterface->focusedTouch(), serverTouch); m_seatInterface->setTimestamp(8); QCOMPARE(m_seatInterface->touchDown(QPointF(15, 26)), 0); m_seatInterface->touchFrame(); m_seatInterface->touchMove(0, QPointF(0, 0)); QCOMPARE(m_seatInterface->touchDown(QPointF(15, 26)), 1); m_seatInterface->cancelTouchSequence(); QVERIFY(destroyedSpy.wait()); QCOMPARE(destroyedSpy.count(), 1); // should have unset the focused touch QVERIFY(!m_seatInterface->focusedTouch()); // but not the focused touch surface QCOMPARE(m_seatInterface->focusedTouchSurface(), serverSurface); } void TestWaylandSeat::testDisconnect() { // this test verifies that disconnecting the client cleans up correctly using namespace KWayland::Client; using namespace KWayland::Server; QSignalSpy keyboardCreatedSpy(m_seatInterface, &SeatInterface::keyboardCreated); QVERIFY(keyboardCreatedSpy.isValid()); QSignalSpy pointerCreatedSpy(m_seatInterface, &SeatInterface::pointerCreated); QVERIFY(pointerCreatedSpy.isValid()); QSignalSpy touchCreatedSpy(m_seatInterface, &SeatInterface::touchCreated); QVERIFY(touchCreatedSpy.isValid()); // create the things we need m_seatInterface->setHasKeyboard(true); m_seatInterface->setHasPointer(true); m_seatInterface->setHasTouch(true); QSignalSpy touchSpy(m_seat, &Seat::hasTouchChanged); QVERIFY(touchSpy.isValid()); QVERIFY(touchSpy.wait()); QScopedPointer keyboard(m_seat->createKeyboard()); QVERIFY(!keyboard.isNull()); QVERIFY(keyboardCreatedSpy.wait()); auto serverKeyboard = keyboardCreatedSpy.first().first().value(); QVERIFY(serverKeyboard); QScopedPointer pointer(m_seat->createPointer()); QVERIFY(!pointer.isNull()); QVERIFY(pointerCreatedSpy.wait()); auto serverPointer = pointerCreatedSpy.first().first().value(); QVERIFY(serverPointer); QScopedPointer touch(m_seat->createTouch()); QVERIFY(!touch.isNull()); QVERIFY(touchCreatedSpy.wait()); auto serverTouch = touchCreatedSpy.first().first().value(); QVERIFY(serverTouch); // setup destroys QSignalSpy keyboardDestroyedSpy(serverKeyboard, &QObject::destroyed); QVERIFY(keyboardDestroyedSpy.isValid()); QSignalSpy pointerDestroyedSpy(serverPointer, &QObject::destroyed); QVERIFY(pointerDestroyedSpy.isValid()); QSignalSpy touchDestroyedSpy(serverTouch, &QObject::destroyed); QVERIFY(touchDestroyedSpy.isValid()); QSignalSpy clientDisconnectedSpy(serverKeyboard->client(), &ClientConnection::disconnected); QVERIFY(clientDisconnectedSpy.isValid()); if (m_connection) { m_connection->deleteLater(); m_connection = nullptr; } QVERIFY(clientDisconnectedSpy.wait()); QCOMPARE(clientDisconnectedSpy.count(), 1); QCOMPARE(keyboardDestroyedSpy.count(), 0); QCOMPARE(pointerDestroyedSpy.count(), 0); QCOMPARE(touchDestroyedSpy.count(), 0); QVERIFY(keyboardDestroyedSpy.wait()); QCOMPARE(keyboardDestroyedSpy.count(), 1); QCOMPARE(pointerDestroyedSpy.count(), 1); QCOMPARE(touchDestroyedSpy.count(), 1); keyboard->destroy(); pointer->destroy(); touch->destroy(); m_compositor->destroy(); m_seat->destroy(); m_shm->destroy(); m_subCompositor->destroy(); m_queue->destroy(); } QTEST_GUILESS_MAIN(TestWaylandSeat) #include "test_wayland_seat.moc" diff --git a/src/server/seat_interface.cpp b/src/server/seat_interface.cpp index ca0c53e..4eefde4 100644 --- a/src/server/seat_interface.cpp +++ b/src/server/seat_interface.cpp @@ -1,1265 +1,1268 @@ /******************************************************************** 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 . *********************************************************************/ #include "seat_interface.h" #include "seat_interface_p.h" #include "display.h" #include "datadevice_interface.h" #include "datasource_interface.h" #include "keyboard_interface.h" #include "keyboard_interface_p.h" #include "pointer_interface.h" #include "pointer_interface_p.h" #include "surface_interface.h" #include "textinput_interface_p.h" // Wayland #ifndef WL_SEAT_NAME_SINCE_VERSION #define WL_SEAT_NAME_SINCE_VERSION 2 #endif // linux #include namespace KWayland { namespace Server { const quint32 SeatInterface::Private::s_version = 4; const qint32 SeatInterface::Private::s_pointerVersion = 3; const qint32 SeatInterface::Private::s_touchVersion = 3; const qint32 SeatInterface::Private::s_keyboardVersion = 4; SeatInterface::Private::Private(SeatInterface *q, Display *display) : Global::Private(display, &wl_seat_interface, s_version) , q(q) { } #ifndef DOXYGEN_SHOULD_SKIP_THIS const struct wl_seat_interface SeatInterface::Private::s_interface = { getPointerCallback, getKeyboardCallback, getTouchCallback }; #endif SeatInterface::SeatInterface(Display *display, QObject *parent) : Global(new Private(this, display), parent) { Q_D(); connect(this, &SeatInterface::nameChanged, this, [this, d] { for (auto it = d->resources.constBegin(); it != d->resources.constEnd(); ++it) { d->sendName(*it); } } ); auto sendCapabilitiesAll = [this, d] { for (auto it = d->resources.constBegin(); it != d->resources.constEnd(); ++it) { d->sendCapabilities(*it); } }; connect(this, &SeatInterface::hasPointerChanged, this, sendCapabilitiesAll); connect(this, &SeatInterface::hasKeyboardChanged, this, sendCapabilitiesAll); connect(this, &SeatInterface::hasTouchChanged, this, sendCapabilitiesAll); } SeatInterface::~SeatInterface() { Q_D(); while (!d->resources.isEmpty()) { wl_resource_destroy(d->resources.takeLast()); } } void SeatInterface::Private::bind(wl_client *client, uint32_t version, uint32_t id) { wl_resource *r = wl_resource_create(client, &wl_seat_interface, qMin(s_version, version), id); if (!r) { wl_client_post_no_memory(client); return; } resources << r; wl_resource_set_implementation(r, &s_interface, this, unbind); sendCapabilities(r); sendName(r); } void SeatInterface::Private::unbind(wl_resource *r) { cast(r)->resources.removeAll(r); } void SeatInterface::Private::updatePointerButtonSerial(quint32 button, quint32 serial) { auto it = globalPointer.buttonSerials.find(button); if (it == globalPointer.buttonSerials.end()) { globalPointer.buttonSerials.insert(button, serial); return; } it.value() = serial; } void SeatInterface::Private::updatePointerButtonState(quint32 button, Pointer::State state) { auto it = globalPointer.buttonStates.find(button); if (it == globalPointer.buttonStates.end()) { globalPointer.buttonStates.insert(button, state); return; } it.value() = state; } void SeatInterface::Private::updateKey(quint32 key, Keyboard::State state) { auto it = keys.states.find(key); if (it == keys.states.end()) { keys.states.insert(key, state); return; } it.value() = state; } void SeatInterface::Private::sendName(wl_resource *r) { if (wl_resource_get_version(r) < WL_SEAT_NAME_SINCE_VERSION) { return; } wl_seat_send_name(r, name.toUtf8().constData()); } void SeatInterface::Private::sendCapabilities(wl_resource *r) { uint32_t capabilities = 0; if (pointer) { capabilities |= WL_SEAT_CAPABILITY_POINTER; } if (keyboard) { capabilities |= WL_SEAT_CAPABILITY_KEYBOARD; } if (touch) { capabilities |= WL_SEAT_CAPABILITY_TOUCH; } wl_seat_send_capabilities(r, capabilities); } SeatInterface::Private *SeatInterface::Private::cast(wl_resource *r) { return r ? reinterpret_cast(wl_resource_get_user_data(r)) : nullptr; } namespace { template static T *interfaceForSurface(SurfaceInterface *surface, const QVector &interfaces) { if (!surface) { return nullptr; } for (auto it = interfaces.begin(); it != interfaces.end(); ++it) { if ((*it)->client() == surface->client()) { return (*it); } } return nullptr; } } PointerInterface *SeatInterface::Private::pointerForSurface(SurfaceInterface *surface) const { return interfaceForSurface(surface, pointers); } KeyboardInterface *SeatInterface::Private::keyboardForSurface(SurfaceInterface *surface) const { return interfaceForSurface(surface, keyboards); } TouchInterface *SeatInterface::Private::touchForSurface(SurfaceInterface *surface) const { return interfaceForSurface(surface, touchs); } DataDeviceInterface *SeatInterface::Private::dataDeviceForSurface(SurfaceInterface *surface) const { return interfaceForSurface(surface, dataDevices); } TextInputInterface *SeatInterface::Private::textInputForSurface(SurfaceInterface *surface) const { return interfaceForSurface(surface, textInputs); } void SeatInterface::Private::registerDataDevice(DataDeviceInterface *dataDevice) { Q_ASSERT(dataDevice->seat() == q); dataDevices << dataDevice; auto dataDeviceCleanup = [this, dataDevice] { dataDevices.removeOne(dataDevice); if (keys.focus.selection == dataDevice) { keys.focus.selection = nullptr; } if (currentSelection == dataDevice) { // current selection is cleared currentSelection = nullptr; if (keys.focus.selection) { keys.focus.selection->sendClearSelection(); } } }; QObject::connect(dataDevice, &QObject::destroyed, q, dataDeviceCleanup); QObject::connect(dataDevice, &Resource::unbound, q, dataDeviceCleanup); QObject::connect(dataDevice, &DataDeviceInterface::selectionChanged, q, [this, dataDevice] { updateSelection(dataDevice, true); } ); QObject::connect(dataDevice, &DataDeviceInterface::selectionCleared, q, [this, dataDevice] { updateSelection(dataDevice, false); } ); QObject::connect(dataDevice, &DataDeviceInterface::dragStarted, q, [this, dataDevice] { if (q->hasImplicitPointerGrab(dataDevice->dragImplicitGrabSerial())) { drag.mode = Drag::Mode::Pointer; } else { // TODO: touch return; } drag.source = dataDevice; drag.target = dataDevice; drag.surface = dataDevice->origin(); drag.sourcePointer = interfaceForSurface(drag.surface, pointers); // TODO: transformation needs to be either pointer or touch drag.transformation = globalPointer.focus.transformation; drag.destroyConnection = QObject::connect(dataDevice, &QObject::destroyed, q, [this] { endDrag(display->nextSerial()); } ); dataDevice->updateDragTarget(dataDevice->origin(), dataDevice->dragImplicitGrabSerial()); emit q->dragStarted(); emit q->dragSurfaceChanged(); } ); // is the new DataDevice for the current keyoard focus? if (keys.focus.surface && !keys.focus.selection) { // same client? if (keys.focus.surface->client() == dataDevice->client()) { keys.focus.selection = dataDevice; if (currentSelection) { dataDevice->sendSelection(currentSelection); } } } } void SeatInterface::Private::registerTextInput(TextInputInterface *ti) { // text input version 0 might call this multiple times if (textInputs.contains(ti)) { return; } textInputs << ti; if (textInput.focus.surface && textInput.focus.surface->client() == ti->client()) { // this is a text input for the currently focused text input surface if (!textInput.focus.textInput) { textInput.focus.textInput = ti; ti->d_func()->sendEnter(textInput.focus.surface, textInput.focus.serial); emit q->focusedTextInputChanged(); } } QObject::connect(ti, &QObject::destroyed, q, [this, ti] { textInputs.removeAt(textInputs.indexOf(ti)); if (textInput.focus.textInput == ti) { textInput.focus.textInput = nullptr; emit q->focusedTextInputChanged(); } } ); } void SeatInterface::Private::endDrag(quint32 serial) { auto target = drag.target; QObject::disconnect(drag.destroyConnection); if (target) { target->drop(); target->updateDragTarget(nullptr, serial); } drag = Drag(); emit q->dragSurfaceChanged(); emit q->dragEnded(); } void SeatInterface::Private::updateSelection(DataDeviceInterface *dataDevice, bool set) { if (keys.focus.surface && (keys.focus.surface->client() == dataDevice->client())) { // new selection on a data device belonging to current keyboard focus currentSelection = dataDevice; } if (dataDevice == currentSelection) { // need to send out the selection if (keys.focus.selection) { if (set) { keys.focus.selection->sendSelection(dataDevice); } else { keys.focus.selection->sendClearSelection(); + currentSelection = nullptr; } } } } void SeatInterface::setHasKeyboard(bool has) { Q_D(); if (d->keyboard == has) { return; } d->keyboard = has; emit hasKeyboardChanged(d->keyboard); } void SeatInterface::setHasPointer(bool has) { Q_D(); if (d->pointer == has) { return; } d->pointer = has; emit hasPointerChanged(d->pointer); } void SeatInterface::setHasTouch(bool has) { Q_D(); if (d->touch == has) { return; } d->touch = has; emit hasTouchChanged(d->touch); } void SeatInterface::setName(const QString &name) { Q_D(); if (d->name == name) { return; } d->name = name; emit nameChanged(d->name); } void SeatInterface::Private::getPointerCallback(wl_client *client, wl_resource *resource, uint32_t id) { cast(resource)->getPointer(client, resource, id); } void SeatInterface::Private::getPointer(wl_client *client, wl_resource *resource, uint32_t id) { // TODO: only create if seat has pointer? PointerInterface *pointer = new PointerInterface(q, resource); auto clientConnection = display->getConnection(client); pointer->create(clientConnection, qMin(wl_resource_get_version(resource), s_pointerVersion), id); if (!pointer->resource()) { wl_resource_post_no_memory(resource); delete pointer; return; } pointers << pointer; if (globalPointer.focus.surface && globalPointer.focus.surface->client() == clientConnection) { // this is a pointer for the currently focused pointer surface if (!globalPointer.focus.pointer) { globalPointer.focus.pointer = pointer; pointer->setFocusedSurface(globalPointer.focus.surface, globalPointer.focus.serial); emit q->focusedPointerChanged(pointer); } } QObject::connect(pointer, &QObject::destroyed, q, [pointer,this] { pointers.removeAt(pointers.indexOf(pointer)); if (globalPointer.focus.pointer == pointer) { globalPointer.focus.pointer = nullptr; emit q->focusedPointerChanged(nullptr); } } ); emit q->pointerCreated(pointer); } void SeatInterface::Private::getKeyboardCallback(wl_client *client, wl_resource *resource, uint32_t id) { cast(resource)->getKeyboard(client, resource, id); } void SeatInterface::Private::getKeyboard(wl_client *client, wl_resource *resource, uint32_t id) { // TODO: only create if seat has keyboard? KeyboardInterface *keyboard = new KeyboardInterface(q, resource); auto clientConnection = display->getConnection(client); keyboard->create(clientConnection, qMin(wl_resource_get_version(resource), s_keyboardVersion) , id); if (!keyboard->resource()) { wl_resource_post_no_memory(resource); delete keyboard; return; } keyboard->repeatInfo(keys.keyRepeat.charactersPerSecond, keys.keyRepeat.delay); if (keys.keymap.xkbcommonCompatible) { keyboard->setKeymap(keys.keymap.fd, keys.keymap.size); } keyboards << keyboard; if (keys.focus.surface && keys.focus.surface->client() == clientConnection) { // this is a keyboard for the currently focused keyboard surface if (!keys.focus.keyboard) { keys.focus.keyboard = keyboard; keyboard->setFocusedSurface(keys.focus.surface, keys.focus.serial); } } QObject::connect(keyboard, &QObject::destroyed, q, [keyboard,this] { keyboards.removeAt(keyboards.indexOf(keyboard)); if (keys.focus.keyboard == keyboard) { keys.focus.keyboard = nullptr; } } ); emit q->keyboardCreated(keyboard); } void SeatInterface::Private::getTouchCallback(wl_client *client, wl_resource *resource, uint32_t id) { cast(resource)->getTouch(client, resource, id); } void SeatInterface::Private::getTouch(wl_client *client, wl_resource *resource, uint32_t id) { // TODO: only create if seat has touch? TouchInterface *touch = new TouchInterface(q, resource); auto clientConnection = display->getConnection(client); touch->create(clientConnection, qMin(wl_resource_get_version(resource), s_touchVersion), id); if (!touch->resource()) { wl_resource_post_no_memory(resource); delete touch; return; } touchs << touch; if (touchInterface.focus.surface && touchInterface.focus.surface->client() == clientConnection) { // this is a touch for the currently focused touch surface if (!touchInterface.focus.touch) { touchInterface.focus.touch = touch; if (!touchInterface.ids.isEmpty()) { // TODO: send out all the points } } } QObject::connect(touch, &QObject::destroyed, q, [touch,this] { touchs.removeAt(touchs.indexOf(touch)); if (touchInterface.focus.touch == touch) { touchInterface.focus.touch = nullptr; } } ); emit q->touchCreated(touch); } QString SeatInterface::name() const { Q_D(); return d->name; } bool SeatInterface::hasPointer() const { Q_D(); return d->pointer; } bool SeatInterface::hasKeyboard() const { Q_D(); return d->keyboard; } bool SeatInterface::hasTouch() const { Q_D(); return d->touch; } SeatInterface *SeatInterface::get(wl_resource *native) { return Private::get(native); } SeatInterface::Private *SeatInterface::d_func() const { return reinterpret_cast(d.data()); } QPointF SeatInterface::pointerPos() const { Q_D(); return d->globalPointer.pos; } void SeatInterface::setPointerPos(const QPointF &pos) { Q_D(); if (d->globalPointer.pos == pos) { return; } d->globalPointer.pos = pos; emit pointerPosChanged(pos); } quint32 SeatInterface::timestamp() const { Q_D(); return d->timestamp; } void SeatInterface::setTimestamp(quint32 time) { Q_D(); if (d->timestamp == time) { return; } d->timestamp = time; emit timestampChanged(time); } void SeatInterface::setDragTarget(SurfaceInterface *surface, const QPointF &globalPosition, const QMatrix4x4 &inputTransformation) { Q_D(); if (surface == d->drag.surface) { // no change return; } const quint32 serial = d->display->nextSerial(); if (d->drag.target) { d->drag.target->updateDragTarget(nullptr, serial); } d->drag.target = d->dataDeviceForSurface(surface); // TODO: update touch if (d->drag.mode == Private::Drag::Mode::Pointer) { setPointerPos(globalPosition); } if (d->drag.target) { d->drag.surface = surface; d->drag.transformation = inputTransformation; d->drag.target->updateDragTarget(surface, serial); } else { d->drag.surface = nullptr; } emit dragSurfaceChanged(); return; } void SeatInterface::setDragTarget(SurfaceInterface *surface, const QMatrix4x4 &inputTransformation) { // TODO: handle touch setDragTarget(surface, pointerPos(), inputTransformation); } SurfaceInterface *SeatInterface::focusedPointerSurface() const { Q_D(); return d->globalPointer.focus.surface; } void SeatInterface::setFocusedPointerSurface(SurfaceInterface *surface, const QPointF &surfacePosition) { QMatrix4x4 m; m.translate(-surfacePosition.x(), -surfacePosition.y()); setFocusedPointerSurface(surface, m); Q_D(); if (d->globalPointer.focus.surface) { d->globalPointer.focus.offset = surfacePosition; } } void SeatInterface::setFocusedPointerSurface(SurfaceInterface *surface, const QMatrix4x4 &transformation) { Q_D(); if (d->drag.mode == Private::Drag::Mode::Pointer) { // ignore return; } const quint32 serial = d->display->nextSerial(); if (d->globalPointer.focus.pointer) { d->globalPointer.focus.pointer->setFocusedSurface(nullptr, serial); } if (d->globalPointer.focus.surface) { disconnect(d->globalPointer.focus.destroyConnection); } d->globalPointer.focus = Private::Pointer::Focus(); d->globalPointer.focus.surface = surface; PointerInterface *p = d->pointerForSurface(surface); if (p && !p->resource()) { p = nullptr; } d->globalPointer.focus.pointer = p; if (d->globalPointer.focus.surface) { d->globalPointer.focus.destroyConnection = connect(surface, &QObject::destroyed, this, [this] { Q_D(); d->globalPointer.focus = Private::Pointer::Focus(); emit focusedPointerChanged(nullptr); } ); d->globalPointer.focus.offset = QPointF(); d->globalPointer.focus.transformation = transformation; d->globalPointer.focus.serial = serial; } emit focusedPointerChanged(p); if (!p) { return; } p->setFocusedSurface(surface, serial); } PointerInterface *SeatInterface::focusedPointer() const { Q_D(); return d->globalPointer.focus.pointer; } void SeatInterface::setFocusedPointerSurfacePosition(const QPointF &surfacePosition) { Q_D(); if (d->globalPointer.focus.surface) { d->globalPointer.focus.offset = surfacePosition; d->globalPointer.focus.transformation = QMatrix4x4(); d->globalPointer.focus.transformation.translate(-surfacePosition.x(), -surfacePosition.y()); } } QPointF SeatInterface::focusedPointerSurfacePosition() const { Q_D(); return d->globalPointer.focus.offset; } void SeatInterface::setFocusedPointerSurfaceTransformation(const QMatrix4x4 &transformation) { Q_D(); if (d->globalPointer.focus.surface) { d->globalPointer.focus.transformation = transformation; } } QMatrix4x4 SeatInterface::focusedPointerSurfaceTransformation() const { Q_D(); return d->globalPointer.focus.transformation; } namespace { static quint32 qtToWaylandButton(Qt::MouseButton button) { static const QHash s_buttons({ {Qt::LeftButton, BTN_LEFT}, {Qt::RightButton, BTN_RIGHT}, {Qt::MiddleButton, BTN_MIDDLE}, {Qt::ExtraButton1, BTN_BACK}, // note: QtWayland maps BTN_SIDE {Qt::ExtraButton2, BTN_FORWARD}, // note: QtWayland maps BTN_EXTRA {Qt::ExtraButton3, BTN_TASK}, // note: QtWayland maps BTN_FORWARD {Qt::ExtraButton4, BTN_EXTRA}, // note: QtWayland maps BTN_BACK {Qt::ExtraButton5, BTN_SIDE}, // note: QtWayland maps BTN_TASK {Qt::ExtraButton6, BTN_TASK + 1}, {Qt::ExtraButton7, BTN_TASK + 2}, {Qt::ExtraButton8, BTN_TASK + 3}, {Qt::ExtraButton9, BTN_TASK + 4}, {Qt::ExtraButton10, BTN_TASK + 5}, {Qt::ExtraButton11, BTN_TASK + 6}, {Qt::ExtraButton12, BTN_TASK + 7}, {Qt::ExtraButton13, BTN_TASK + 8} // further mapping not possible, 0x120 is BTN_JOYSTICK }); return s_buttons.value(button, 0); } } bool SeatInterface::isPointerButtonPressed(Qt::MouseButton button) const { return isPointerButtonPressed(qtToWaylandButton(button)); } bool SeatInterface::isPointerButtonPressed(quint32 button) const { Q_D(); auto it = d->globalPointer.buttonStates.constFind(button); if (it == d->globalPointer.buttonStates.constEnd()) { return false; } return it.value() == Private::Pointer::State::Pressed ? true : false; } void SeatInterface::pointerAxis(Qt::Orientation orientation, quint32 delta) { Q_D(); if (d->drag.mode == Private::Drag::Mode::Pointer) { // ignore return; } if (d->globalPointer.focus.pointer && d->globalPointer.focus.surface) { d->globalPointer.focus.pointer->axis(orientation, delta); } } void SeatInterface::pointerButtonPressed(Qt::MouseButton button) { const quint32 nativeButton = qtToWaylandButton(button); if (nativeButton == 0) { return; } pointerButtonPressed(nativeButton); } void SeatInterface::pointerButtonPressed(quint32 button) { Q_D(); const quint32 serial = d->display->nextSerial(); d->updatePointerButtonSerial(button, serial); d->updatePointerButtonState(button, Private::Pointer::State::Pressed); if (d->drag.mode == Private::Drag::Mode::Pointer) { // ignore return; } if (d->globalPointer.focus.pointer && d->globalPointer.focus.surface) { d->globalPointer.focus.pointer->buttonPressed(button, serial); if (d->globalPointer.focus.surface == d->keys.focus.surface && d->keys.focus.keyboard) { // update the focused child surface d->keys.focus.keyboard->d_func()->focusChildSurface(d->globalPointer.focus.pointer->d_func()->focusedChildSurface, serial); } } } void SeatInterface::pointerButtonReleased(Qt::MouseButton button) { const quint32 nativeButton = qtToWaylandButton(button); if (nativeButton == 0) { return; } pointerButtonReleased(nativeButton); } void SeatInterface::pointerButtonReleased(quint32 button) { Q_D(); const quint32 serial = d->display->nextSerial(); const quint32 currentButtonSerial = pointerButtonSerial(button); d->updatePointerButtonSerial(button, serial); d->updatePointerButtonState(button, Private::Pointer::State::Released); if (d->drag.mode == Private::Drag::Mode::Pointer) { if (d->drag.source->dragImplicitGrabSerial() != currentButtonSerial) { // not our drag button - ignore return; } d->endDrag(serial); return; } if (d->globalPointer.focus.pointer && d->globalPointer.focus.surface) { d->globalPointer.focus.pointer->buttonReleased(button, serial); } } quint32 SeatInterface::pointerButtonSerial(Qt::MouseButton button) const { return pointerButtonSerial(qtToWaylandButton(button)); } quint32 SeatInterface::pointerButtonSerial(quint32 button) const { Q_D(); auto it = d->globalPointer.buttonSerials.constFind(button); if (it == d->globalPointer.buttonSerials.constEnd()) { return 0; } return it.value(); } void SeatInterface::keyPressed(quint32 key) { Q_D(); d->keys.lastStateSerial = d->display->nextSerial(); d->updateKey(key, Private::Keyboard::State::Pressed); if (d->keys.focus.keyboard && d->keys.focus.surface) { d->keys.focus.keyboard->keyPressed(key, d->keys.lastStateSerial); } } void SeatInterface::keyReleased(quint32 key) { Q_D(); d->keys.lastStateSerial = d->display->nextSerial(); d->updateKey(key, Private::Keyboard::State::Released); if (d->keys.focus.keyboard && d->keys.focus.surface) { d->keys.focus.keyboard->keyReleased(key, d->keys.lastStateSerial); } } SurfaceInterface *SeatInterface::focusedKeyboardSurface() const { Q_D(); return d->keys.focus.surface; } void SeatInterface::setFocusedKeyboardSurface(SurfaceInterface *surface) { Q_D(); const quint32 serial = d->display->nextSerial(); if (d->keys.focus.keyboard) { d->keys.focus.keyboard->setFocusedSurface(nullptr, serial); } if (d->keys.focus.surface) { disconnect(d->keys.focus.destroyConnection); } d->keys.focus = Private::Keyboard::Focus(); d->keys.focus.surface = surface; KeyboardInterface *k = d->keyboardForSurface(surface); if (k && !k->resource()) { k = nullptr; } d->keys.focus.keyboard = k; if (d->keys.focus.surface) { d->keys.focus.destroyConnection = connect(surface, &QObject::destroyed, this, [this] { Q_D(); d->keys.focus = Private::Keyboard::Focus(); } ); d->keys.focus.serial = serial; // selection? d->keys.focus.selection = d->dataDeviceForSurface(surface); if (d->keys.focus.selection) { if (d->currentSelection) { d->keys.focus.selection->sendSelection(d->currentSelection); + } else { + d->keys.focus.selection->sendClearSelection(); } } } if (k) { k->setFocusedSurface(surface, serial); } // focused text input surface follows keyboard if (hasKeyboard()) { setFocusedTextInputSurface(surface); } } void SeatInterface::setKeymap(int fd, quint32 size) { Q_D(); d->keys.keymap.xkbcommonCompatible = true; d->keys.keymap.fd = fd; d->keys.keymap.size = size; for (auto it = d->keyboards.constBegin(); it != d->keyboards.constEnd(); ++it) { (*it)->setKeymap(fd, size); } } void SeatInterface::updateKeyboardModifiers(quint32 depressed, quint32 latched, quint32 locked, quint32 group) { Q_D(); bool changed = false; #define UPDATE( value ) \ if (d->keys.modifiers.value != value) { \ d->keys.modifiers.value = value; \ changed = true; \ } UPDATE(depressed) UPDATE(latched) UPDATE(locked) UPDATE(group) if (!changed) { return; } const quint32 serial = d->display->nextSerial(); d->keys.modifiers.serial = serial; if (d->keys.focus.keyboard && d->keys.focus.surface) { d->keys.focus.keyboard->updateModifiers(depressed, latched, locked, group, serial); } } void SeatInterface::setKeyRepeatInfo(qint32 charactersPerSecond, qint32 delay) { Q_D(); d->keys.keyRepeat.charactersPerSecond = qMax(charactersPerSecond, 0); d->keys.keyRepeat.delay = qMax(delay, 0); for (auto it = d->keyboards.constBegin(); it != d->keyboards.constEnd(); ++it) { (*it)->repeatInfo(d->keys.keyRepeat.charactersPerSecond, d->keys.keyRepeat.delay); } } qint32 SeatInterface::keyRepeatDelay() const { Q_D(); return d->keys.keyRepeat.delay; } qint32 SeatInterface::keyRepeatRate() const { Q_D(); return d->keys.keyRepeat.charactersPerSecond; } bool SeatInterface::isKeymapXkbCompatible() const { Q_D(); return d->keys.keymap.xkbcommonCompatible; } int SeatInterface::keymapFileDescriptor() const { Q_D(); return d->keys.keymap.fd; } quint32 SeatInterface::keymapSize() const { Q_D(); return d->keys.keymap.size; } quint32 SeatInterface::depressedModifiers() const { Q_D(); return d->keys.modifiers.depressed; } quint32 SeatInterface::groupModifiers() const { Q_D(); return d->keys.modifiers.group; } quint32 SeatInterface::latchedModifiers() const { Q_D(); return d->keys.modifiers.latched; } quint32 SeatInterface::lockedModifiers() const { Q_D(); return d->keys.modifiers.locked; } quint32 SeatInterface::lastModifiersSerial() const { Q_D(); return d->keys.modifiers.serial; } QVector< quint32 > SeatInterface::pressedKeys() const { Q_D(); QVector keys; for (auto it = d->keys.states.begin(); it != d->keys.states.end(); ++it) { if (it.value() == Private::Keyboard::State::Pressed) { keys << it.key(); } } return keys; } KeyboardInterface *SeatInterface::focusedKeyboard() const { Q_D(); return d->keys.focus.keyboard; } void SeatInterface::cancelTouchSequence() { Q_D(); if (d->touchInterface.focus.touch) { d->touchInterface.focus.touch->cancel(); } d->touchInterface.ids.clear(); } TouchInterface *SeatInterface::focusedTouch() const { Q_D(); return d->touchInterface.focus.touch; } SurfaceInterface *SeatInterface::focusedTouchSurface() const { Q_D(); return d->touchInterface.focus.surface; } QPointF SeatInterface::focusedTouchSurfacePosition() const { Q_D(); return d->touchInterface.focus.offset; } bool SeatInterface::isTouchSequence() const { Q_D(); return !d->touchInterface.ids.isEmpty(); } void SeatInterface::setFocusedTouchSurface(SurfaceInterface *surface, const QPointF &surfacePosition) { if (isTouchSequence()) { // changing surface not allowed during a touch sequence return; } Q_D(); if (d->touchInterface.focus.surface) { disconnect(d->touchInterface.focus.destroyConnection); } d->touchInterface.focus = Private::Touch::Focus(); d->touchInterface.focus.surface = surface; d->touchInterface.focus.offset = surfacePosition; TouchInterface *t = d->touchForSurface(surface); if (t && !t->resource()) { t = nullptr; } d->touchInterface.focus.touch = t; if (d->touchInterface.focus.surface) { d->touchInterface.focus.destroyConnection = connect(surface, &QObject::destroyed, this, [this] { Q_D(); if (isTouchSequence() && d->touchInterface.focus.touch) { // Surface destroyed during touch sequence - send a cancel d->touchInterface.focus.touch->cancel(); } d->touchInterface.focus = Private::Touch::Focus(); } ); } } void SeatInterface::setFocusedTouchSurfacePosition(const QPointF &surfacePosition) { Q_D(); d->touchInterface.focus.offset = surfacePosition; } qint32 SeatInterface::touchDown(const QPointF &globalPosition) { Q_D(); const qint32 id = d->touchInterface.ids.isEmpty() ? 0 : d->touchInterface.ids.last() + 1; const quint32 serial = display()->nextSerial(); if (d->touchInterface.focus.touch && d->touchInterface.focus.surface) { d->touchInterface.focus.touch->down(id, serial, globalPosition - d->touchInterface.focus.offset); } else if (id == 0 && focusedTouchSurface()) { auto p = d->pointerForSurface(focusedTouchSurface()); if (!p) { return id; } const QPointF pos = globalPosition - d->touchInterface.focus.offset; wl_pointer_send_enter(p->resource(), serial, focusedTouchSurface()->resource(), wl_fixed_from_double(pos.x()), wl_fixed_from_double(pos.y())); wl_pointer_send_motion(p->resource(), timestamp(), wl_fixed_from_double(pos.x()), wl_fixed_from_double(pos.y())); wl_pointer_send_button(p->resource(), serial, timestamp(), BTN_LEFT, WL_POINTER_BUTTON_STATE_PRESSED); } d->touchInterface.ids << id; return id; } void SeatInterface::touchMove(qint32 id, const QPointF &globalPosition) { Q_D(); if (d->touchInterface.focus.touch && d->touchInterface.focus.surface) { d->touchInterface.focus.touch->move(id, globalPosition - d->touchInterface.focus.offset); } else if (id == 0 && focusedTouchSurface()) { auto p = d->pointerForSurface(focusedTouchSurface()); if (!p) { return; } const QPointF pos = globalPosition - d->touchInterface.focus.offset; wl_pointer_send_motion(p->resource(), timestamp(), wl_fixed_from_double(pos.x()), wl_fixed_from_double(pos.y())); } } void SeatInterface::touchUp(qint32 id) { Q_D(); Q_ASSERT(d->touchInterface.ids.contains(id)); if (d->touchInterface.focus.touch && d->touchInterface.focus.surface) { d->touchInterface.focus.touch->up(id, display()->nextSerial()); } else if (id == 0 && focusedTouchSurface()) { const quint32 serial = display()->nextSerial(); auto p = d->pointerForSurface(focusedTouchSurface()); if (!p) { return; } wl_pointer_send_button(p->resource(), serial, timestamp(), BTN_LEFT, WL_POINTER_BUTTON_STATE_RELEASED); } d->touchInterface.ids.removeAll(id); } void SeatInterface::touchFrame() { Q_D(); if (d->touchInterface.focus.touch && d->touchInterface.focus.surface) { d->touchInterface.focus.touch->frame(); } } bool SeatInterface::isDrag() const { Q_D(); return d->drag.mode != Private::Drag::Mode::None; } bool SeatInterface::isDragPointer() const { Q_D(); return d->drag.mode == Private::Drag::Mode::Pointer; } bool SeatInterface::isDragTouch() const { Q_D(); return d->drag.mode == Private::Drag::Mode::Touch; } bool SeatInterface::hasImplicitPointerGrab(quint32 serial) const { Q_D(); const auto &serials = d->globalPointer.buttonSerials; for (auto it = serials.begin(), end = serials.end(); it != end; it++) { if (it.value() == serial) { return isPointerButtonPressed(it.key()); } } return false; } QMatrix4x4 SeatInterface::dragSurfaceTransformation() const { Q_D(); return d->drag.transformation; } SurfaceInterface *SeatInterface::dragSurface() const { Q_D(); return d->drag.surface; } PointerInterface *SeatInterface::dragPointer() const { Q_D(); if (d->drag.mode != Private::Drag::Mode::Pointer) { return nullptr; } return d->drag.sourcePointer; } DataDeviceInterface *SeatInterface::dragSource() const { Q_D(); return d->drag.source; } void SeatInterface::setFocusedTextInputSurface(SurfaceInterface *surface) { Q_D(); const quint32 serial = d->display->nextSerial(); const auto old = d->textInput.focus.textInput; if (d->textInput.focus.textInput) { // TODO: setFocusedSurface like in other interfaces d->textInput.focus.textInput->d_func()->sendLeave(serial, d->textInput.focus.surface); } if (d->textInput.focus.surface) { disconnect(d->textInput.focus.destroyConnection); } d->textInput.focus = Private::TextInput::Focus(); d->textInput.focus.surface = surface; TextInputInterface *t = d->textInputForSurface(surface); if (t && !t->resource()) { t = nullptr; } d->textInput.focus.textInput = t; if (d->textInput.focus.surface) { d->textInput.focus.destroyConnection = connect(surface, &QObject::destroyed, this, [this] { setFocusedTextInputSurface(nullptr); } ); d->textInput.focus.serial = serial; } if (t) { // TODO: setFocusedSurface like in other interfaces t->d_func()->sendEnter(surface, serial); } if (old != t) { emit focusedTextInputChanged(); } } SurfaceInterface *SeatInterface::focusedTextInputSurface() const { Q_D(); return d->textInput.focus.surface; } TextInputInterface *SeatInterface::focusedTextInput() const { Q_D(); return d->textInput.focus.textInput; } DataDeviceInterface *SeatInterface::selection() const { Q_D(); return d->currentSelection; } void SeatInterface::setSelection(DataDeviceInterface *dataDevice) { Q_D(); if (d->currentSelection == dataDevice) { return; } d->currentSelection = dataDevice; if (d->keys.focus.selection) { if (dataDevice) { d->keys.focus.selection->sendSelection(dataDevice); } else { d->keys.focus.selection->sendClearSelection(); } } } } }