Files
RedBear-OS/local/recipes/kde/kwin/autotests/integration/keyboard_input_test.cpp
T

304 lines
12 KiB
C++

/*
SPDX-FileCopyrightText: 2025 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "kwin_wayland_test.h"
#include "config-kwin.h"
#include "utils/socketpair.h"
#include "wayland/display.h"
#include "wayland_server.h"
#include "workspace.h"
#if KWIN_BUILD_GLOBALSHORTCUTS
#include <KGlobalAccel>
#endif
#include <KWayland/Client/compositor.h>
#include <KWayland/Client/connection_thread.h>
#include <KWayland/Client/event_queue.h>
#include <KWayland/Client/keyboard.h>
#include <KWayland/Client/registry.h>
#include <KWayland/Client/seat.h>
#include <KWayland/Client/shm_pool.h>
#include <linux/input-event-codes.h>
namespace KWin
{
static const QString s_socketName = QStringLiteral("wayland_test_kwin_kbd_input-0");
struct Connection
{
static std::unique_ptr<Connection> create()
{
auto socketPair = SocketPair::create(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
if (!socketPair) {
return nullptr;
}
waylandServer()->display()->createClient(socketPair->fds[0].take());
auto connection = std::make_unique<Connection>();
connection->connection = new KWayland::Client::ConnectionThread;
QSignalSpy connectedSpy(connection->connection, &KWayland::Client::ConnectionThread::connected);
connection->connection->setSocketFd(socketPair->fds[1].take());
connection->thread = new QThread(kwinApp());
connection->connection->moveToThread(connection->thread);
connection->thread->start();
connection->connection->initConnection();
if (!connectedSpy.wait()) {
return nullptr;
}
connection->queue = new KWayland::Client::EventQueue;
connection->queue->setup(connection->connection);
if (!connection->queue->isValid()) {
return nullptr;
}
connection->registry = new KWayland::Client::Registry;
connection->registry->setEventQueue(connection->queue);
QObject::connect(connection->registry, &KWayland::Client::Registry::interfaceAnnounced, [&](const QByteArray &interface, quint32 name, quint32 version) {
if (interface == wl_compositor_interface.name) {
connection->compositor = connection->registry->createCompositor(name, version);
} else if (interface == wl_shm_interface.name) {
connection->shm = connection->registry->createShmPool(name, version);
} else if (interface == wl_seat_interface.name) {
connection->seat = connection->registry->createSeat(name, version);
} else if (interface == xdg_wm_base_interface.name) {
connection->xdgShell = new Test::XdgShell();
connection->xdgShell->init(*connection->registry, name, version);
}
});
connection->registry->create(connection->connection);
if (!connection->registry->isValid()) {
return nullptr;
}
connection->registry->setup();
QSignalSpy allAnnounced(connection->registry, &KWayland::Client::Registry::interfacesAnnounced);
if (!allAnnounced.wait()) {
return nullptr;
}
return connection;
}
~Connection()
{
delete compositor;
compositor = nullptr;
delete seat;
seat = nullptr;
delete xdgShell;
xdgShell = nullptr;
delete shm;
shm = nullptr;
delete registry;
registry = nullptr;
delete queue; // Must be destroyed last
if (thread) {
connection->deleteLater();
thread->quit();
thread->wait();
delete thread;
}
}
QThread *thread = nullptr;
KWayland::Client::ConnectionThread *connection = nullptr;
KWayland::Client::EventQueue *queue = nullptr;
KWayland::Client::Registry *registry = nullptr;
Test::XdgShell *xdgShell = nullptr;
KWayland::Client::Compositor *compositor = nullptr;
KWayland::Client::Seat *seat = nullptr;
KWayland::Client::ShmPool *shm = nullptr;
};
class KeyboardInputTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void init();
void cleanup();
void implicitGrab();
void implicitGrabByClosedWindow();
void globalShortcut();
private:
std::unique_ptr<Connection> m_firstConnection;
std::unique_ptr<KWayland::Client::Keyboard> m_firstKeyboard;
std::unique_ptr<KWayland::Client::Surface> m_firstSurface;
std::unique_ptr<Test::XdgToplevel> m_firstShellSurface;
QPointer<Window> m_firstWindow;
std::unique_ptr<Connection> m_secondConnection;
std::unique_ptr<KWayland::Client::Keyboard> m_secondKeyboard;
std::unique_ptr<KWayland::Client::Surface> m_secondSurface;
std::unique_ptr<Test::XdgToplevel> m_secondShellSurface;
QPointer<Window> m_secondWindow;
};
void KeyboardInputTest::initTestCase()
{
QVERIFY(waylandServer()->init(s_socketName));
Test::setOutputConfig({
QRect(0, 0, 1280, 1024),
QRect(1280, 0, 1280, 1024),
});
kwinApp()->start();
}
void KeyboardInputTest::init()
{
m_firstConnection = Connection::create();
QVERIFY(Test::waitForWaylandKeyboard(m_firstConnection->seat));
m_firstKeyboard = std::unique_ptr<KWayland::Client::Keyboard>(m_firstConnection->seat->createKeyboard());
m_firstSurface = Test::createSurface(m_firstConnection->compositor);
m_firstShellSurface = Test::createXdgToplevelSurface(m_firstConnection->xdgShell, m_firstSurface.get());
m_firstWindow = Test::renderAndWaitForShown(m_firstConnection->shm, m_firstSurface.get(), QSize(100, 100), Qt::cyan);
QSignalSpy firstEnteredSpy(m_firstKeyboard.get(), &KWayland::Client::Keyboard::entered);
QVERIFY(firstEnteredSpy.wait());
m_secondConnection = Connection::create();
QVERIFY(Test::waitForWaylandKeyboard(m_secondConnection->seat));
m_secondKeyboard = std::unique_ptr<KWayland::Client::Keyboard>(m_secondConnection->seat->createKeyboard());
m_secondSurface = Test::createSurface(m_secondConnection->compositor);
m_secondShellSurface = Test::createXdgToplevelSurface(m_secondConnection->xdgShell, m_secondSurface.get());
m_secondWindow = Test::renderAndWaitForShown(m_secondConnection->shm, m_secondSurface.get(), QSize(100, 100), Qt::cyan);
QSignalSpy secondEnteredSpy(m_secondKeyboard.get(), &KWayland::Client::Keyboard::entered);
QVERIFY(secondEnteredSpy.wait());
}
void KeyboardInputTest::cleanup()
{
m_firstShellSurface.reset();
m_firstSurface.reset();
m_firstKeyboard.reset();
m_firstConnection.reset();
m_secondShellSurface.reset();
m_secondSurface.reset();
m_secondKeyboard.reset();
m_secondConnection.reset();
}
void KeyboardInputTest::implicitGrab()
{
QSignalSpy firstEnteredSpy(m_firstKeyboard.get(), &KWayland::Client::Keyboard::entered);
QSignalSpy firstKeyChangedSpy(m_firstKeyboard.get(), &KWayland::Client::Keyboard::keyChanged);
QSignalSpy secondEnteredSpy(m_secondKeyboard.get(), &KWayland::Client::Keyboard::entered);
QSignalSpy secondKeyChangedSpy(m_secondKeyboard.get(), &KWayland::Client::Keyboard::keyChanged);
quint32 timestamp = 0;
Test::keyboardKeyPressed(KEY_Q, timestamp++);
QVERIFY(secondKeyChangedSpy.wait());
QCOMPARE(secondKeyChangedSpy.last().at(0).value<quint32>(), KEY_Q);
QCOMPARE(secondKeyChangedSpy.last().at(1).value<KWayland::Client::Keyboard::KeyState>(), KWayland::Client::Keyboard::KeyState::Pressed);
workspace()->activateWindow(m_firstWindow);
QVERIFY(firstEnteredSpy.wait()); // TODO: perhaps we should not receive the enter event until the q key is released
QCOMPARE(m_firstKeyboard->enteredKeys(), (QList<quint32>{KEY_Q}));
Test::keyboardKeyReleased(KEY_Q, timestamp++);
QVERIFY(firstKeyChangedSpy.wait());
QCOMPARE(firstKeyChangedSpy.last().at(0).value<quint32>(), KEY_Q);
QCOMPARE(firstKeyChangedSpy.last().at(1).value<KWayland::Client::Keyboard::KeyState>(), KWayland::Client::Keyboard::KeyState::Released);
}
void KeyboardInputTest::implicitGrabByClosedWindow()
{
// This test verifies that an implicit grab is preserved even after the window is closed. Note:
// currently it is not the case, but it should be.
QSignalSpy firstEnteredSpy(m_firstKeyboard.get(), &KWayland::Client::Keyboard::entered);
QSignalSpy firstKeyChangedSpy(m_firstKeyboard.get(), &KWayland::Client::Keyboard::keyChanged);
QSignalSpy secondEnteredSpy(m_secondKeyboard.get(), &KWayland::Client::Keyboard::entered);
QSignalSpy secondKeyChangedSpy(m_secondKeyboard.get(), &KWayland::Client::Keyboard::keyChanged);
quint32 timestamp = 0;
Test::keyboardKeyPressed(KEY_Q, timestamp++);
QVERIFY(secondKeyChangedSpy.wait());
QCOMPARE(secondKeyChangedSpy.last().at(0).value<quint32>(), KEY_Q);
QCOMPARE(secondKeyChangedSpy.last().at(1).value<KWayland::Client::Keyboard::KeyState>(), KWayland::Client::Keyboard::KeyState::Pressed);
m_secondShellSurface.reset();
m_secondSurface.reset();
QVERIFY(firstEnteredSpy.wait()); // TODO: perhaps we should not receive the enter event until the q key is released
QCOMPARE(m_firstKeyboard->enteredKeys(), (QList<quint32>{KEY_Q}));
Test::keyboardKeyReleased(KEY_Q, timestamp++);
QVERIFY(firstKeyChangedSpy.wait());
QCOMPARE(firstKeyChangedSpy.last().at(0).value<quint32>(), KEY_Q);
QCOMPARE(firstKeyChangedSpy.last().at(1).value<KWayland::Client::Keyboard::KeyState>(), KWayland::Client::Keyboard::KeyState::Released);
}
void KeyboardInputTest::globalShortcut()
{
// This test verifies that keys are not leaked to the clients when pressing a global shortcut.
#if KWIN_BUILD_GLOBALSHORTCUTS
QSignalSpy firstEnteredSpy(m_firstKeyboard.get(), &KWayland::Client::Keyboard::entered);
QSignalSpy firstKeyChangedSpy(m_firstKeyboard.get(), &KWayland::Client::Keyboard::keyChanged);
QSignalSpy secondEnteredSpy(m_secondKeyboard.get(), &KWayland::Client::Keyboard::entered);
QSignalSpy secondKeyChangedSpy(m_secondKeyboard.get(), &KWayland::Client::Keyboard::keyChanged);
auto action = std::make_unique<QAction>();
action->setObjectName(QStringLiteral("test"));
action->setProperty("componentName", QStringLiteral("test"));
KGlobalAccel::self()->setShortcut(action.get(), QList<QKeySequence>{Qt::META | Qt::Key_Space}, KGlobalAccel::NoAutoloading);
QSignalSpy actionTriggeredSpy(action.get(), &QAction::triggered);
// the client should not see the space key being pressed or released
quint32 timestamp = 0;
Test::keyboardKeyPressed(KEY_LEFTMETA, timestamp++);
QVERIFY(secondKeyChangedSpy.wait());
Test::keyboardKeyPressed(KEY_SPACE, timestamp++);
QVERIFY(!secondKeyChangedSpy.wait(10));
QCOMPARE(actionTriggeredSpy.count(), 1);
Test::keyboardKeyReleased(KEY_SPACE, timestamp++);
QVERIFY(!secondKeyChangedSpy.wait(10));
Test::keyboardKeyReleased(KEY_LEFTMETA, timestamp++);
QVERIFY(secondKeyChangedSpy.wait());
// the space key should not be leaked even if the focused surface changes between pressing and releasing the space key
Test::keyboardKeyPressed(KEY_LEFTMETA, timestamp++);
QVERIFY(secondKeyChangedSpy.wait());
Test::keyboardKeyPressed(KEY_SPACE, timestamp++);
QVERIFY(!secondKeyChangedSpy.wait(10));
QCOMPARE(actionTriggeredSpy.count(), 2);
m_secondShellSurface.reset();
m_secondSurface.reset();
QVERIFY(firstEnteredSpy.wait());
QCOMPARE(m_firstKeyboard->enteredKeys(), (QList<quint32>{KEY_LEFTMETA}));
Test::keyboardKeyReleased(KEY_SPACE, timestamp++);
QVERIFY(!firstKeyChangedSpy.wait(10));
Test::keyboardKeyReleased(KEY_LEFTMETA, timestamp++);
QVERIFY(firstKeyChangedSpy.wait());
#endif
}
} // namespace KWin
WAYLANDTEST_MAIN(KWin::KeyboardInputTest)
#include "keyboard_input_test.moc"