virtualkeyboard: add text-input-v3 support

master
Bhushan Shah 2020-09-25 12:21:04 +05:30 committed by Bhushan Shah
parent a433fb08a3
commit 6d433bdfc9
6 changed files with 245 additions and 56 deletions

View File

@ -13,6 +13,10 @@ ecm_add_qtwayland_client_protocol(KWinIntegrationTestFramework_SOURCES
PROTOCOL ${WaylandProtocols_DATADIR}/unstable/input-method/input-method-unstable-v1.xml
BASENAME input-method-unstable-v1
)
ecm_add_qtwayland_client_protocol(KWinIntegrationTestFramework_SOURCES
PROTOCOL ${WaylandProtocols_DATADIR}/unstable/text-input/text-input-unstable-v3.xml
BASENAME text-input-unstable-v3
)
ecm_add_qtwayland_client_protocol(KWinIntegrationTestFramework_SOURCES
PROTOCOL protocols/wlr-layer-shell-unstable-v1.xml
BASENAME wlr-layer-shell-unstable-v1

View File

@ -18,6 +18,7 @@
#include <KWayland/Client/xdgshell.h>
#include "qwayland-wlr-layer-shell-unstable-v1.h"
#include "qwayland-text-input-unstable-v3.h"
#include "qwayland-xdg-shell.h"
namespace KWayland
@ -47,6 +48,8 @@ class TextInputManager;
namespace QtWayland
{
class zwp_input_panel_surface_v1;
class zwp_text_input_v3;
class zwp_text_input_manager_v3;
}
namespace KWin
@ -86,6 +89,17 @@ namespace Test
class MockInputMethod;
class TextInputManagerV3 : public QtWayland::zwp_text_input_manager_v3
{
public:
~TextInputManagerV3() override { destroy(); }
};
class TextInputV3 : public QtWayland::zwp_text_input_v3
{
~TextInputV3() override { destroy(); }
};
class LayerShellV1 : public QtWayland::zwlr_layer_shell_v1
{
public:
@ -222,6 +236,7 @@ enum class AdditionalWaylandInterface {
TextInputManagerV2 = 1 << 10,
InputMethodV1 = 1 << 11,
LayerShellV1 = 1 << 12,
TextInputManagerV3 = 1 << 13
};
Q_DECLARE_FLAGS(AdditionalWaylandInterfaces, AdditionalWaylandInterface)
/**
@ -272,6 +287,8 @@ LayerSurfaceV1 *createLayerSurfaceV1(KWayland::Client::Surface *surface,
KWayland::Client::Output *output = nullptr,
LayerShellV1::layer layer = LayerShellV1::layer_top);
TextInputManagerV3 *waylandTextInputManagerV3();
enum class CreationSetup {
CreateOnly,
CreateAndConfigure, /// commit and wait for the configure event, making this surface ready to commit buffers

View File

@ -208,6 +208,7 @@ static struct {
MockInputMethod *inputMethodV1 = nullptr;
QtWayland::zwp_input_method_context_v1 *inputMethodContextV1 = nullptr;
LayerShellV1 *layerShellV1 = nullptr;
TextInputManagerV3 *textInputManagerV3 = nullptr;
} s_waylandConnection;
class MockInputMethod : public QtWayland::zwp_input_method_v1
@ -323,6 +324,13 @@ bool setupWaylandConnection(AdditionalWaylandInterfaces flags)
s_waylandConnection.layerShellV1->init(*registry, name, version);
}
}
if (flags & AdditionalWaylandInterface::TextInputManagerV3) {
// do something
if (interface == QByteArrayLiteral("zwp_text_input_manager_v3")) {
s_waylandConnection.textInputManagerV3 = new TextInputManagerV3();
s_waylandConnection.textInputManagerV3->init(*registry, name, version);
}
}
if (interface == QByteArrayLiteral("xdg_wm_base")) {
s_waylandConnection.xdgShell = new XdgShell();
s_waylandConnection.xdgShell->init(*registry, name, version);
@ -566,6 +574,11 @@ TextInputManager *waylandTextInputManager()
return s_waylandConnection.textInputManager;
}
TextInputManagerV3 *waylandTextInputManagerV3()
{
return s_waylandConnection.textInputManagerV3;
}
QVector<KWayland::Client::Output *> waylandOutputs()
{
return s_waylandConnection.outputs;

View File

@ -18,6 +18,7 @@
#include "virtualkeyboard.h"
#include "virtualkeyboard_dbus.h"
#include "qwayland-input-method-unstable-v1.h"
#include "qwayland-text-input-unstable-v3.h"
#include <QTest>
#include <QSignalSpy>
@ -33,6 +34,7 @@
#include <KWayland/Client/surface.h>
#include <KWayland/Client/xdgshell.h>
#include <KWayland/Client/textinput.h>
#include <KWayland/Client/seat.h>
using namespace KWin;
using namespace KWayland::Client;
@ -49,6 +51,7 @@ private Q_SLOTS:
void cleanup();
void testOpenClose();
void testEnableDisableV3();
};
@ -77,7 +80,9 @@ void VirtualKeyboardTest::initTestCase()
void VirtualKeyboardTest::init()
{
QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat |
Test::AdditionalWaylandInterface::TextInputManagerV2 | Test::AdditionalWaylandInterface::InputMethodV1));
Test::AdditionalWaylandInterface::TextInputManagerV2 |
Test::AdditionalWaylandInterface::InputMethodV1 |
Test::AdditionalWaylandInterface::TextInputManagerV3));
screens()->setCurrent(0);
@ -149,6 +154,40 @@ void VirtualKeyboardTest::testOpenClose()
QVERIFY(Test::waitForWindowDestroyed(client));
}
void VirtualKeyboardTest::testEnableDisableV3()
{
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<XdgShellSurface> shellSurface(Test::createXdgShellStableSurface(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();
// just enabling the text-input should not show it but rather on commit
QVERIFY(!clientAddedSpy.wait(100));
textInputV3->commit();
QVERIFY(clientAddedSpy.wait());
AbstractClient *keyboardClient = clientAddedSpy.last().first().value<AbstractClient *>();
QVERIFY(keyboardClient);
QVERIFY(keyboardClient->isInputMethod());
// disable text input and ensure that it is not hiding input panel without commit
textInputV3->disable();
QVERIFY(!clientRemovedSpy.wait(100));
textInputV3->commit();
QVERIFY(clientRemovedSpy.wait());
}
WAYLANDTEST_MAIN(VirtualKeyboardTest)
#include "virtualkeyboard_test.moc"

View File

@ -19,6 +19,7 @@
#include <KWaylandServer/display.h>
#include <KWaylandServer/seat_interface.h>
#include <KWaylandServer/textinput_v2_interface.h>
#include <KWaylandServer/textinput_v3_interface.h>
#include <KWaylandServer/surface_interface.h>
#include <KWaylandServer/inputmethod_v1_interface.h>
@ -29,6 +30,9 @@
#include <QDBusPendingCall>
#include <QDBusMessage>
#include <linux/input-event-codes.h>
#include <xkbcommon/xkbcommon-keysyms.h>
using namespace KWaylandServer;
namespace KWin
@ -85,6 +89,8 @@ void VirtualKeyboard::init()
if (waylandServer()) {
waylandServer()->display()->createTextInputManagerV2();
waylandServer()->display()->createTextInputManagerV3();
connect(workspace(), &Workspace::clientAdded, this, &VirtualKeyboard::clientAdded);
connect(waylandServer()->seat(), &SeatInterface::focusedTextInputSurfaceChanged, this, &VirtualKeyboard::handleFocusedSurfaceChanged);
@ -94,8 +100,14 @@ void VirtualKeyboard::init()
connect(textInputV2, &TextInputV2Interface::surroundingTextChanged, this, &VirtualKeyboard::surroundingTextChanged);
connect(textInputV2, &TextInputV2Interface::contentTypeChanged, this, &VirtualKeyboard::contentTypeChanged);
connect(textInputV2, &TextInputV2Interface::requestReset, this, &VirtualKeyboard::requestReset);
connect(textInputV2, &TextInputV2Interface::enabledChanged, this, &VirtualKeyboard::textInputInterfaceEnabledChanged);
connect(textInputV2, &TextInputV2Interface::enabledChanged, this, &VirtualKeyboard::textInputInterfaceV2EnabledChanged);
connect(textInputV2, &TextInputV2Interface::stateCommitted, this, &VirtualKeyboard::stateCommitted);
TextInputV3Interface *textInputV3 = waylandServer()->seat()->textInputV3();
connect(textInputV3, &TextInputV3Interface::enabledChanged, this, &VirtualKeyboard::textInputInterfaceV3EnabledChanged);
connect(textInputV3, &TextInputV3Interface::surroundingTextChanged, this, &VirtualKeyboard::surroundingTextChanged);
connect(textInputV3, &TextInputV3Interface::contentTypeChanged, this, &VirtualKeyboard::contentTypeChanged);
connect(textInputV3, &TextInputV3Interface::stateCommitted, this, &VirtualKeyboard::stateCommitted);
}
}
@ -159,38 +171,54 @@ void VirtualKeyboard::handleFocusedSurfaceChanged()
void VirtualKeyboard::surroundingTextChanged()
{
auto t = waylandServer()->seat()->textInputV2();
auto t2 = waylandServer()->seat()->textInputV2();
auto t3 = waylandServer()->seat()->textInputV3();
auto inputContext = waylandServer()->inputMethod()->context();
if (!inputContext) {
return;
}
inputContext->sendSurroundingText(t->surroundingText(), t->surroundingTextCursorPosition(), t->surroundingTextSelectionAnchor());
if (t2 && t2->isEnabled()) {
inputContext->sendSurroundingText(t2->surroundingText(), t2->surroundingTextCursorPosition(), t2->surroundingTextSelectionAnchor());
return;
}
if (t3 && t3->isEnabled()) {
inputContext->sendSurroundingText(t3->surroundingText(), t3->surroundingTextCursorPosition(), t3->surroundingTextSelectionAnchor());
return;
}
}
void VirtualKeyboard::contentTypeChanged()
{
auto t = waylandServer()->seat()->textInputV2();
auto t2 = waylandServer()->seat()->textInputV2();
auto t3 = waylandServer()->seat()->textInputV3();
auto inputContext = waylandServer()->inputMethod()->context();
if (!inputContext) {
return;
}
inputContext->sendContentType(t->contentHints(), t->contentPurpose());
if (t2 && t2->isEnabled()) {
inputContext->sendContentType(t2->contentHints(), t2->contentPurpose());
}
if (t3 && t3->isEnabled()) {
inputContext->sendContentType(t3->contentHints(), t3->contentPurpose());
}
}
void VirtualKeyboard::requestReset()
{
auto t = waylandServer()->seat()->textInputV2();
auto t2 = waylandServer()->seat()->textInputV2();
auto inputContext = waylandServer()->inputMethod()->context();
if (!inputContext) {
return;
}
inputContext->sendReset();
inputContext->sendSurroundingText(t->surroundingText(), t->surroundingTextCursorPosition(), t->surroundingTextSelectionAnchor());
inputContext->sendPreferredLanguage(t->preferredLanguage());
inputContext->sendContentType(t->contentHints(), t->contentPurpose());
if (t2 && t2->isEnabled()) {
inputContext->sendSurroundingText(t2->surroundingText(), t2->surroundingTextCursorPosition(), t2->surroundingTextSelectionAnchor());
inputContext->sendPreferredLanguage(t2->preferredLanguage());
inputContext->sendContentType(t2->contentHints(), t2->contentPurpose());
}
}
void VirtualKeyboard::textInputInterfaceEnabledChanged()
void VirtualKeyboard::textInputInterfaceV2EnabledChanged()
{
auto t = waylandServer()->seat()->textInputV2();
if (t->isEnabled()) {
@ -204,6 +232,21 @@ void VirtualKeyboard::textInputInterfaceEnabledChanged()
}
}
void VirtualKeyboard::textInputInterfaceV3EnabledChanged()
{
auto t3 = waylandServer()->seat()->textInputV3();
if (t3->isEnabled()) {
waylandServer()->inputMethod()->sendActivate();
adoptInputMethodContext();
} else {
waylandServer()->inputMethod()->sendDeactivate();
// reset value of preedit when textinput is disabled
preedit.text = QString();
preedit.begin = 0;
preedit.end = 0;
}
}
void VirtualKeyboard::stateCommitted(uint32_t serial)
{
auto inputContext = waylandServer()->inputMethod()->context();
@ -232,98 +275,160 @@ void VirtualKeyboard::setEnabled(bool enabled)
QDBusConnection::sessionBus().asyncCall(msg);
}
static quint32 keysymToKeycode(quint32 sym)
{
switch(sym) {
case XKB_KEY_BackSpace:
return KEY_BACKSPACE;
case XKB_KEY_Return:
return KEY_ENTER;
case XKB_KEY_Left:
return KEY_LEFT;
case XKB_KEY_Right:
return KEY_RIGHT;
case XKB_KEY_Up:
return KEY_UP;
case XKB_KEY_Down:
return KEY_DOWN;
default:
return KEY_UNKNOWN;
}
}
static void keysymReceived(quint32 serial, quint32 time, quint32 sym, bool pressed, Qt::KeyboardModifiers modifiers)
{
Q_UNUSED(serial)
Q_UNUSED(time)
auto t = waylandServer()->seat()->textInputV2();
if (t && t->isEnabled()) {
auto t2 = waylandServer()->seat()->textInputV2();
if (t2 && t2->isEnabled()) {
if (pressed) {
t->keysymPressed(sym, modifiers);
t2->keysymPressed(sym, modifiers);
} else {
t->keysymReleased(sym, modifiers);
t2->keysymReleased(sym, modifiers);
}
return;
}
auto t3 = waylandServer()->seat()->textInputV3();
if (t3 && t3->isEnabled()) {
if (pressed) {
waylandServer()->seat()->keyPressed(keysymToKeycode(sym));
} else {
waylandServer()->seat()->keyReleased(keysymToKeycode(sym));
}
return;
}
}
static void commitString(qint32 serial, const QString &text)
{
Q_UNUSED(serial)
auto t = waylandServer()->seat()->textInputV2();
if (t && t->isEnabled()) {
t->commit(text.toUtf8());
t->preEdit({}, {});
auto t2 = waylandServer()->seat()->textInputV2();
if (t2 && t2->isEnabled()) {
t2->commitString(text.toUtf8());
t2->preEdit({}, {});
return;
}
}
static void setPreeditCursor(qint32 index)
{
auto t = waylandServer()->seat()->textInputV2();
if (t && t->isEnabled()) {
t->setPreEditCursor(index);
}
}
static void setPreeditString(uint32_t serial, const QString &text, const QString &commit)
{
Q_UNUSED(serial)
auto t = waylandServer()->seat()->textInputV2();
if (t && t->isEnabled()) {
t->preEdit(text.toUtf8(), commit.toUtf8());
auto t3 = waylandServer()->seat()->textInputV3();
if (t3 && t3->isEnabled()) {
t3->commitString(text.toUtf8());
t3->done();
return;
}
}
static void deleteSurroundingText(int32_t index, uint32_t length)
{
auto t = waylandServer()->seat()->textInputV2();
if (t && t->isEnabled()) {
t->deleteSurroundingText(index, length);
auto t2 = waylandServer()->seat()->textInputV2();
if (t2 && t2->isEnabled()) {
t2->deleteSurroundingText(index, length);
}
auto t3 = waylandServer()->seat()->textInputV3();
if (t3 && t3->isEnabled()) {
t3->deleteSurroundingText(index, length);
}
}
static void setCursorPosition(qint32 index, qint32 anchor)
{
auto t = waylandServer()->seat()->textInputV2();
if (t && t->isEnabled()) {
t->setCursorPosition(index, anchor);
auto t2 = waylandServer()->seat()->textInputV2();
if (t2 && t2->isEnabled()) {
t2->setCursorPosition(index, anchor);
}
}
static void setLanguage(uint32_t serial, const QString &language)
{
Q_UNUSED(serial)
auto t = waylandServer()->seat()->textInputV2();
if (t && t->isEnabled()) {
t->setLanguage(language.toUtf8());
auto t2 = waylandServer()->seat()->textInputV2();
if (t2 && t2->isEnabled()) {
t2->setLanguage(language.toUtf8());
}
}
static void setTextDirection(uint32_t serial, Qt::LayoutDirection direction)
{
Q_UNUSED(serial)
auto t = waylandServer()->seat()->textInputV2();
if (t && t->isEnabled()) {
t->setTextDirection(direction);
auto t2 = waylandServer()->seat()->textInputV2();
if (t2 && t2->isEnabled()) {
t2->setTextDirection(direction);
}
}
void VirtualKeyboard::setPreeditCursor(qint32 index)
{
auto t2 = waylandServer()->seat()->textInputV2();
if (t2 && t2->isEnabled()) {
t2->setPreEditCursor(index);
}
auto t3 = waylandServer()->seat()->textInputV3();
if (t3 && t3->isEnabled()) {
preedit.begin = index;
preedit.end = index;
t3->sendPreEditString(preedit.text, preedit.begin, preedit.end);
}
}
void VirtualKeyboard::setPreeditString(uint32_t serial, const QString &text, const QString &commit)
{
Q_UNUSED(serial)
auto t2 = waylandServer()->seat()->textInputV2();
if (t2 && t2->isEnabled()) {
t2->preEdit(text.toUtf8(), commit.toUtf8());
}
auto t3 = waylandServer()->seat()->textInputV3();
if (t3 && t3->isEnabled()) {
preedit.text = text;
t3->sendPreEditString(preedit.text, preedit.begin, preedit.end);
}
}
void VirtualKeyboard::adoptInputMethodContext()
{
auto inputContext = waylandServer()->inputMethod()->context();
TextInputV2Interface *ti = waylandServer()->seat()->textInputV2();
inputContext->sendSurroundingText(ti->surroundingText(), ti->surroundingTextCursorPosition(), ti->surroundingTextSelectionAnchor());
inputContext->sendPreferredLanguage(ti->preferredLanguage());
inputContext->sendContentType(ti->contentHints(), ti->contentPurpose());
TextInputV2Interface *t2 = waylandServer()->seat()->textInputV2();
TextInputV3Interface *t3 = waylandServer()->seat()->textInputV3();
if (t2 && t2->isEnabled()) {
inputContext->sendSurroundingText(t2->surroundingText(), t2->surroundingTextCursorPosition(), t2->surroundingTextSelectionAnchor());
inputContext->sendPreferredLanguage(t2->preferredLanguage());
inputContext->sendContentType(t2->contentHints(), t2->contentPurpose());
connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::language, waylandServer(), &setLanguage);
connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::textDirection, waylandServer(), &setTextDirection);
}
if (t3 && t3->isEnabled()) {
inputContext->sendSurroundingText(t3->surroundingText(), t3->surroundingTextCursorPosition(), t3->surroundingTextSelectionAnchor());
inputContext->sendContentType(t3->contentHints(), t3->contentPurpose());
}
connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::keysym, waylandServer(), &keysymReceived);
connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::commitString, waylandServer(), &commitString);
connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::preeditCursor, waylandServer(), &setPreeditCursor);
connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::preeditString, waylandServer(), &setPreeditString);
connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::deleteSurroundingText, waylandServer(), &deleteSurroundingText);
connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::cursorPosition, waylandServer(), &setCursorPosition);
connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::language, waylandServer(), &setLanguage);
connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::textDirection, waylandServer(), &setTextDirection);
connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::preeditString, this, &VirtualKeyboard::setPreeditString);
connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::preeditCursor, this, &VirtualKeyboard::setPreeditCursor);
}
void VirtualKeyboard::updateSni()

View File

@ -42,15 +42,26 @@ private Q_SLOTS:
void surroundingTextChanged();
void contentTypeChanged();
void requestReset();
void textInputInterfaceEnabledChanged();
void textInputInterfaceV2EnabledChanged();
void textInputInterfaceV3EnabledChanged();
void stateCommitted(uint32_t serial);
// inputcontext slots
void setPreeditString(uint32_t serial, const QString &text, const QString &commit);
void setPreeditCursor(qint32 index);
private:
void setEnabled(bool enable);
void updateSni();
void updateInputPanelState();
void adoptInputMethodContext();
struct {
QString text = QString();
quint32 begin = 0;
quint32 end = 0;
} preedit;
bool m_enabled = false;
KStatusNotifierItem *m_sni = nullptr;
QPointer<AbstractClient> m_inputClient;