Since we adapted inputmethod to support methods like ibus, the input method can be active but not have a visible panel. This includes an extra property that will indicate us if the panel is visible at any time. This will allow us to properly render the virtual keyboard hide button in Plasma Mobile (or wherever we need it).
281 lines
10 KiB
C++
281 lines
10 KiB
C++
/*
|
|
KWin - the KDE window manager
|
|
This file is part of the KDE project.
|
|
|
|
SPDX-FileCopyrightText: 2020 Marco Martin <mart@kde.org>
|
|
|
|
SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
#include "kwin_wayland_test.h"
|
|
#include "abstract_client.h"
|
|
#include "cursor.h"
|
|
#include "effects.h"
|
|
#include "deleted.h"
|
|
#include "platform.h"
|
|
#include "screens.h"
|
|
#include "wayland_server.h"
|
|
#include "workspace.h"
|
|
#include "inputmethod.h"
|
|
#include "virtualkeyboard_dbus.h"
|
|
#include "qwayland-input-method-unstable-v1.h"
|
|
#include "qwayland-text-input-unstable-v3.h"
|
|
|
|
#include <QTest>
|
|
#include <QSignalSpy>
|
|
#include <QDBusConnection>
|
|
#include <QDBusMessage>
|
|
#include <QDBusPendingReply>
|
|
#include <KWaylandServer/clientconnection.h>
|
|
#include <KWaylandServer/display.h>
|
|
#include <KWaylandServer/seat_interface.h>
|
|
#include <KWaylandServer/surface_interface.h>
|
|
|
|
#include <KWayland/Client/compositor.h>
|
|
#include <KWayland/Client/output.h>
|
|
#include <KWayland/Client/region.h>
|
|
#include <KWayland/Client/surface.h>
|
|
#include <KWayland/Client/textinput.h>
|
|
#include <KWayland/Client/seat.h>
|
|
|
|
using namespace KWin;
|
|
using namespace KWayland::Client;
|
|
using KWin::VirtualKeyboardDBus;
|
|
|
|
static const QString s_socketName = QStringLiteral("wayland_test_kwin_inputmethod-0");
|
|
|
|
class InputMethodTest : public QObject
|
|
{
|
|
Q_OBJECT
|
|
private Q_SLOTS:
|
|
void initTestCase();
|
|
void init();
|
|
void cleanup();
|
|
|
|
void testOpenClose();
|
|
void testEnableDisableV3();
|
|
void testEnableActive();
|
|
void testHidePanel();
|
|
};
|
|
|
|
|
|
void InputMethodTest::initTestCase()
|
|
{
|
|
QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.kwin.testvirtualkeyboard"));
|
|
|
|
qRegisterMetaType<KWin::Deleted *>();
|
|
qRegisterMetaType<KWin::AbstractClient *>();
|
|
qRegisterMetaType<KWayland::Client::Output *>();
|
|
|
|
QSignalSpy applicationStartedSpy(kwinApp(), &Application::started);
|
|
QVERIFY(applicationStartedSpy.isValid());
|
|
kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024));
|
|
QVERIFY(waylandServer()->init(s_socketName));
|
|
QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2));
|
|
|
|
static_cast<WaylandTestApplication *>(kwinApp())->setInputMethodServerToStart("qml " + QFINDTESTDATA("emptywindow.qml"));
|
|
kwinApp()->start();
|
|
QVERIFY(applicationStartedSpy.wait());
|
|
QCOMPARE(screens()->count(), 2);
|
|
QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024));
|
|
QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024));
|
|
Test::initWaylandWorkspace();
|
|
|
|
}
|
|
|
|
void InputMethodTest::init()
|
|
{
|
|
QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat |
|
|
Test::AdditionalWaylandInterface::TextInputManagerV2 |
|
|
Test::AdditionalWaylandInterface::InputMethodV1 |
|
|
Test::AdditionalWaylandInterface::TextInputManagerV3));
|
|
|
|
|
|
screens()->setCurrent(0);
|
|
KWin::Cursors::self()->mouse()->setPos(QPoint(512, 512));
|
|
|
|
InputMethod::self()->setEnabled(true);
|
|
}
|
|
|
|
void InputMethodTest::cleanup()
|
|
{
|
|
Test::destroyWaylandConnection();
|
|
}
|
|
|
|
void InputMethodTest::testOpenClose()
|
|
{
|
|
QSignalSpy clientAddedSpy(workspace(), &Workspace::clientAdded);
|
|
QSignalSpy clientRemovedSpy(workspace(), &Workspace::clientRemoved);
|
|
QVERIFY(clientAddedSpy.isValid());
|
|
|
|
// Create an xdg_toplevel surface and wait for the compositor to catch up.
|
|
QScopedPointer<Surface> surface(Test::createSurface());
|
|
QScopedPointer<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.data()));
|
|
AbstractClient *client = Test::renderAndWaitForShown(surface.data(), QSize(1280, 1024), Qt::red);
|
|
QVERIFY(client);
|
|
QVERIFY(client->isActive());
|
|
QCOMPARE(client->frameGeometry().size(), QSize(1280, 1024));
|
|
QSignalSpy frameGeometryChangedSpy(client, &AbstractClient::frameGeometryChanged);
|
|
QVERIFY(frameGeometryChangedSpy.isValid());
|
|
QSignalSpy toplevelConfigureRequestedSpy(shellSurface.data(), &Test::XdgToplevel::configureRequested);
|
|
QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
|
|
|
|
QScopedPointer<TextInput> textInput(Test::waylandTextInputManager()->createTextInput(Test::waylandSeat()));
|
|
|
|
QVERIFY(!textInput.isNull());
|
|
textInput->enable(surface.data());
|
|
QVERIFY(surfaceConfigureRequestedSpy.wait());
|
|
|
|
// Show the keyboard
|
|
textInput->showInputPanel();
|
|
QVERIFY(clientAddedSpy.wait());
|
|
|
|
AbstractClient *keyboardClient = clientAddedSpy.last().first().value<AbstractClient *>();
|
|
QVERIFY(keyboardClient);
|
|
QVERIFY(keyboardClient->isInputMethod());
|
|
|
|
// Do the actual resize
|
|
QVERIFY(surfaceConfigureRequestedSpy.wait());
|
|
|
|
Test::render(surface.data(), toplevelConfigureRequestedSpy.last().first().value<QSize>(), Qt::red);
|
|
QVERIFY(frameGeometryChangedSpy.wait());
|
|
|
|
QCOMPARE(client->frameGeometry().height(), 1024 - keyboardClient->inputGeometry().height() + 1);
|
|
|
|
// Hide the keyboard
|
|
textInput->hideInputPanel();
|
|
|
|
QVERIFY(surfaceConfigureRequestedSpy.wait());
|
|
Test::render(surface.data(), toplevelConfigureRequestedSpy.last().first().value<QSize>(), Qt::red);
|
|
QVERIFY(frameGeometryChangedSpy.wait());
|
|
|
|
QCOMPARE(client->frameGeometry().height(), 1024);
|
|
|
|
// Destroy the test client.
|
|
shellSurface.reset();
|
|
QVERIFY(Test::waitForWindowDestroyed(client));
|
|
}
|
|
|
|
void InputMethodTest::testEnableDisableV3()
|
|
{
|
|
// Create an xdg_toplevel surface and wait for the compositor to catch up.
|
|
QScopedPointer<Surface> surface(Test::createSurface());
|
|
QScopedPointer<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.data()));
|
|
AbstractClient *client = Test::renderAndWaitForShown(surface.data(), QSize(1280, 1024), Qt::red);
|
|
QVERIFY(client);
|
|
QVERIFY(client->isActive());
|
|
QCOMPARE(client->frameGeometry().size(), QSize(1280, 1024));
|
|
|
|
Test::TextInputV3 *textInputV3 = new Test::TextInputV3();
|
|
textInputV3->init(Test::waylandTextInputManagerV3()->get_text_input(*(Test::waylandSeat())));
|
|
textInputV3->enable();
|
|
|
|
QSignalSpy inputMethodActiveSpy(InputMethod::self(), &InputMethod::activeChanged);
|
|
// just enabling the text-input should not show it but rather on commit
|
|
QVERIFY(!InputMethod::self()->isActive());
|
|
textInputV3->commit();
|
|
QVERIFY(inputMethodActiveSpy.count() || inputMethodActiveSpy.wait());
|
|
QVERIFY(InputMethod::self()->isActive());
|
|
|
|
// disable text input and ensure that it is not hiding input panel without commit
|
|
inputMethodActiveSpy.clear();
|
|
QVERIFY(InputMethod::self()->isActive());
|
|
textInputV3->disable();
|
|
textInputV3->commit();
|
|
QVERIFY(inputMethodActiveSpy.count() || inputMethodActiveSpy.wait());
|
|
QVERIFY(!InputMethod::self()->isActive());
|
|
}
|
|
|
|
void InputMethodTest::testEnableActive()
|
|
{
|
|
QVERIFY(!InputMethod::self()->isActive());
|
|
|
|
QSignalSpy clientAddedSpy(workspace(), &Workspace::clientAdded);
|
|
QSignalSpy clientRemovedSpy(workspace(), &Workspace::clientRemoved);
|
|
|
|
QSignalSpy activateSpy(InputMethod::self(), &InputMethod::activeChanged);
|
|
|
|
// Create an xdg_toplevel surface and wait for the compositor to catch up.
|
|
QScopedPointer<Surface> surface(Test::createSurface());
|
|
QScopedPointer<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.data()));
|
|
AbstractClient *client = Test::renderAndWaitForShown(surface.data(), QSize(1280, 1024), Qt::red);
|
|
QVERIFY(client);
|
|
QVERIFY(client->isActive());
|
|
QCOMPARE(client->frameGeometry().size(), QSize(1280, 1024));
|
|
QSignalSpy frameGeometryChangedSpy(client, &AbstractClient::frameGeometryChanged);
|
|
QVERIFY(frameGeometryChangedSpy.isValid());
|
|
QSignalSpy toplevelConfigureRequestedSpy(shellSurface.data(), &Test::XdgToplevel::configureRequested);
|
|
QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
|
|
|
|
QScopedPointer<TextInput> textInput(Test::waylandTextInputManager()->createTextInput(Test::waylandSeat()));
|
|
|
|
QVERIFY(!textInput.isNull());
|
|
textInput->enable(surface.data());
|
|
QVERIFY(surfaceConfigureRequestedSpy.wait());
|
|
QCOMPARE(clientAddedSpy.count(), 1);
|
|
|
|
// Show the keyboard
|
|
textInput->showInputPanel();
|
|
QVERIFY(clientAddedSpy.wait());
|
|
|
|
QCOMPARE(workspace()->activeClient(), client);
|
|
|
|
activateSpy.clear();
|
|
textInput->enable(surface.get());
|
|
textInput->showInputPanel();
|
|
activateSpy.wait(200);
|
|
QVERIFY(activateSpy.isEmpty());
|
|
QVERIFY(InputMethod::self()->isActive());
|
|
auto keyboardClient = Test::inputPanelClient();
|
|
QVERIFY(keyboardClient);
|
|
textInput->enable(surface.get());
|
|
|
|
QVERIFY(InputMethod::self()->isActive());
|
|
|
|
// Destroy the test client.
|
|
shellSurface.reset();
|
|
QVERIFY(Test::waitForWindowDestroyed(client));
|
|
}
|
|
|
|
void InputMethodTest::testHidePanel()
|
|
{
|
|
QVERIFY(!InputMethod::self()->isActive());
|
|
|
|
QSignalSpy clientAddedSpy(workspace(), &Workspace::clientAdded);
|
|
QSignalSpy clientRemovedSpy(workspace(), &Workspace::clientRemoved);
|
|
QVERIFY(clientAddedSpy.isValid());
|
|
|
|
QSignalSpy activateSpy(InputMethod::self(), &InputMethod::activeChanged);
|
|
QScopedPointer<TextInput> textInput(Test::waylandTextInputManager()->createTextInput(Test::waylandSeat()));
|
|
textInput->showInputPanel();
|
|
QVERIFY(clientAddedSpy.wait());
|
|
|
|
// Create an xdg_toplevel surface and wait for the compositor to catch up.
|
|
QScopedPointer<Surface> surface(Test::createSurface());
|
|
QScopedPointer<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.data()));
|
|
AbstractClient *client = Test::renderAndWaitForShown(surface.data(), QSize(1280, 1024), Qt::red);
|
|
waylandServer()->seat()->setFocusedTextInputSurface(client->surface());
|
|
|
|
QCOMPARE(workspace()->activeClient(), client);
|
|
|
|
QCOMPARE(clientAddedSpy.count(), 2);
|
|
QVERIFY(activateSpy.count() || activateSpy.wait());
|
|
QVERIFY(InputMethod::self()->isActive());
|
|
|
|
auto keyboardClient = Test::inputPanelClient();
|
|
auto ipsurface = Test::inputPanelSurface();
|
|
QVERIFY(keyboardClient);
|
|
clientRemovedSpy.clear();
|
|
delete ipsurface;
|
|
QVERIFY(InputMethod::self()->isVisible());
|
|
QVERIFY(clientRemovedSpy.count() || clientRemovedSpy.wait());
|
|
QVERIFY(!InputMethod::self()->isVisible());
|
|
|
|
// Destroy the test client.
|
|
shellSurface.reset();
|
|
QVERIFY(Test::waitForWindowDestroyed(client));
|
|
|
|
}
|
|
|
|
WAYLANDTEST_MAIN(InputMethodTest)
|
|
|
|
#include "inputmethod_test.moc"
|