Try our best to convert preedit styling to text-input-v3 cursor range.

text-input-v3 does not have preedit styling, instead, it can only
specify the range of cursor. Try to keep track of any
highlight/selection style range and combine them together. If it matches
the cursor position, use it as the cursor range.
This commit is contained in:
Weng Xuetian 2021-12-23 00:42:00 -08:00 committed by Xuetian Weng
parent f50866d3f8
commit c074e2eb42
5 changed files with 204 additions and 34 deletions

View file

@ -57,6 +57,7 @@ private Q_SLOTS:
void testEnableActive();
void testHidePanel();
void testSwitchFocusedSurfaces();
void testV3Styling();
private:
void touchNow() {
@ -339,6 +340,96 @@ void InputMethodTest::testSwitchFocusedSurfaces()
}
}
void InputMethodTest::testV3Styling()
{
// Create an xdg_toplevel surface and wait for the compositor to catch up.
QScopedPointer<KWayland::Client::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);
QSignalSpy inputMethodActivateSpy(Test::inputMethod(), &Test::MockInputMethod::activate);
// 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());
QVERIFY(inputMethodActivateSpy.wait());
auto context = Test::inputMethod()->context();
QSignalSpy textInputPreeditSpy(textInputV3, &Test::TextInputV3::preeditString);
zwp_input_method_context_v1_preedit_cursor(context, 0);
zwp_input_method_context_v1_preedit_styling(context, 0, 3, 7);
zwp_input_method_context_v1_preedit_string(context, 0, "ABCD", "ABCD");
QVERIFY(textInputPreeditSpy.wait());
QCOMPARE(textInputPreeditSpy.last().at(0), QString("ABCD"));
QCOMPARE(textInputPreeditSpy.last().at(1), 0);
QCOMPARE(textInputPreeditSpy.last().at(2), 0);
zwp_input_method_context_v1_preedit_cursor(context, 1);
zwp_input_method_context_v1_preedit_styling(context, 0, 3, 7);
zwp_input_method_context_v1_preedit_string(context, 0, "ABCDE", "ABCDE");
QVERIFY(textInputPreeditSpy.wait());
QCOMPARE(textInputPreeditSpy.last().at(0), QString("ABCDE"));
QCOMPARE(textInputPreeditSpy.last().at(1), 1);
QCOMPARE(textInputPreeditSpy.last().at(2), 1);
zwp_input_method_context_v1_preedit_cursor(context, 2);
// Use selection for [2, 2+2)
zwp_input_method_context_v1_preedit_styling(context, 2, 2, 6);
// Use high light for [3, 3+3)
zwp_input_method_context_v1_preedit_styling(context, 3, 3, 4);
zwp_input_method_context_v1_preedit_string(context, 0, "ABCDEF", "ABCDEF");
QVERIFY(textInputPreeditSpy.wait());
QCOMPARE(textInputPreeditSpy.last().at(0), QString("ABCDEF"));
// Merged range should be [2, 6)
QCOMPARE(textInputPreeditSpy.last().at(1), 2);
QCOMPARE(textInputPreeditSpy.last().at(2), 6);
zwp_input_method_context_v1_preedit_cursor(context, 2);
// Use selection for [0, 0+2)
zwp_input_method_context_v1_preedit_styling(context, 0, 2, 6);
// Use high light for [3, 3+3)
zwp_input_method_context_v1_preedit_styling(context, 3, 3, 4);
zwp_input_method_context_v1_preedit_string(context, 0, "ABCDEF", "ABCDEF");
QVERIFY(textInputPreeditSpy.wait());
QCOMPARE(textInputPreeditSpy.last().at(0), QString("ABCDEF"));
// Merged range should be none, because of the disjunction highlight.
QCOMPARE(textInputPreeditSpy.last().at(1), 2);
QCOMPARE(textInputPreeditSpy.last().at(2), 2);
zwp_input_method_context_v1_preedit_cursor(context, 1);
// Use selection for [0, 0+2)
zwp_input_method_context_v1_preedit_styling(context, 0, 2, 6);
// Use high light for [2, 2+3)
zwp_input_method_context_v1_preedit_styling(context, 2, 3, 4);
zwp_input_method_context_v1_preedit_string(context, 0, "ABCDEF", "ABCDEF");
QVERIFY(textInputPreeditSpy.wait());
QCOMPARE(textInputPreeditSpy.last().at(0), QString("ABCDEF"));
// Merged range should be none, starting offset does not match.
QCOMPARE(textInputPreeditSpy.last().at(1), 1);
QCOMPARE(textInputPreeditSpy.last().at(2), 1);
// Use different order of styling and cursor
// Use high light for [3, 3+3)
zwp_input_method_context_v1_preedit_styling(context, 3, 3, 4);
zwp_input_method_context_v1_preedit_cursor(context, 1);
// Use selection for [1, 1+2)
zwp_input_method_context_v1_preedit_styling(context, 1, 2, 6);
zwp_input_method_context_v1_preedit_string(context, 0, "ABCDEF", "ABCDEF");
QVERIFY(textInputPreeditSpy.wait());
QCOMPARE(textInputPreeditSpy.last().at(0), QString("ABCDEF"));
// Merged range should be [1,6).
QCOMPARE(textInputPreeditSpy.last().at(1), 1);
QCOMPARE(textInputPreeditSpy.last().at(2), 6);
}
WAYLANDTEST_MAIN(InputMethodTest)
#include "inputmethod_test.moc"

View file

@ -9,18 +9,22 @@
#ifndef KWIN_WAYLAND_TEST_H
#define KWIN_WAYLAND_TEST_H
#include "abstract_client.h"
#include "main.h"
// Qt
#include <QtTest>
#include <KWayland/Client/surface.h>
#include "qwayland-idle-inhibit-unstable-v1.h"
#include "qwayland-wlr-layer-shell-unstable-v1.h"
#include "qwayland-text-input-unstable-v3.h"
#include "qwayland-xdg-decoration-unstable-v1.h"
#include "qwayland-xdg-shell.h"
#include "qwayland-input-method-unstable-v1.h"
#include "qwayland-kde-output-device-v2.h"
#include "qwayland-kde-output-management-v2.h"
#include "qwayland-text-input-unstable-v3.h"
#include "qwayland-wlr-layer-shell-unstable-v1.h"
#include "qwayland-xdg-decoration-unstable-v1.h"
#include "qwayland-xdg-shell.h"
namespace KWayland
{
@ -93,9 +97,20 @@ public:
~TextInputManagerV3() override { destroy(); }
};
class TextInputV3 : public QtWayland::zwp_text_input_v3
class TextInputV3 : public QObject, public QtWayland::zwp_text_input_v3
{
Q_OBJECT
public:
~TextInputV3() override { destroy(); }
Q_SIGNALS:
void preeditString(const QString &text, int cursor_begin, int cursor_end);
protected:
void zwp_text_input_v3_preedit_string(const QString &text, int32_t cursor_begin, int32_t cursor_end) override
{
Q_EMIT preeditString(text, cursor_begin, cursor_end);
}
};
class LayerShellV1 : public QtWayland::zwlr_layer_shell_v1
@ -393,6 +408,40 @@ private:
uint32_t m_rgbRange;
};
class MockInputMethod : public QObject, QtWayland::zwp_input_method_v1
{
Q_OBJECT
public:
MockInputMethod(struct wl_registry *registry, int id, int version);
~MockInputMethod();
AbstractClient *client() const
{
return m_client;
}
KWayland::Client::Surface *inputPanelSurface() const
{
return m_inputSurface;
}
auto *context() const
{
return m_context;
}
Q_SIGNALS:
void activate();
protected:
void zwp_input_method_v1_activate(struct ::zwp_input_method_context_v1 *context) override;
void zwp_input_method_v1_deactivate(struct ::zwp_input_method_context_v1 *context) override;
private:
QPointer<KWayland::Client::Surface> m_inputSurface;
QtWayland::zwp_input_panel_surface_v1 *m_inputMethodSurface = nullptr;
QPointer<AbstractClient> m_client;
struct ::zwp_input_method_context_v1 *m_context = nullptr;
};
enum class AdditionalWaylandInterface {
Seat = 1 << 0,
Decoration = 1 << 1,
@ -528,6 +577,7 @@ bool unlockScreen();
void initWaylandWorkspace();
AbstractClient *inputPanelClient();
MockInputMethod *inputMethod();
KWayland::Client::Surface *inputPanelSurface();
}

View file

@ -7,11 +7,9 @@
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "kwin_wayland_test.h"
#include "abstract_client.h"
#include "screenlockerwatcher.h"
#include "wayland_server.h"
#include "workspace.h"
#include "qwayland-input-method-unstable-v1.h"
#include "inputmethod.h"
#include <KWayland/Client/compositor.h>
@ -249,30 +247,16 @@ static struct {
TextInputManagerV3 *textInputManagerV3 = nullptr;
} s_waylandConnection;
class MockInputMethod : public QtWayland::zwp_input_method_v1
{
public:
MockInputMethod(struct wl_registry *registry, int id, int version);
~MockInputMethod();
AbstractClient *client() const { return m_client; }
KWayland::Client::Surface *inputPanelSurface() const { return m_inputSurface; }
protected:
void zwp_input_method_v1_activate(struct ::zwp_input_method_context_v1 *context) override;
void zwp_input_method_v1_deactivate(struct ::zwp_input_method_context_v1 *context) override;
private:
QPointer<KWayland::Client::Surface> m_inputSurface;
QtWayland::zwp_input_panel_surface_v1 *m_inputMethodSurface = nullptr;
QPointer<AbstractClient> m_client;
};
AbstractClient *inputPanelClient()
{
return s_waylandConnection.inputMethodV1->client();
}
MockInputMethod *inputMethod()
{
return s_waylandConnection.inputMethodV1;
}
KWayland::Client::Surface *inputPanelSurface()
{
return s_waylandConnection.inputMethodV1->inputPanelSurface();
@ -295,11 +279,16 @@ void MockInputMethod::zwp_input_method_v1_activate(struct ::zwp_input_method_con
m_inputMethodSurface = Test::createInputPanelSurfaceV1(m_inputSurface, s_waylandConnection.outputs.first());
}
m_client = Test::renderAndWaitForShown(m_inputSurface, QSize(1280, 400), Qt::blue);
m_context = context;
Q_EMIT activate();
}
void MockInputMethod::zwp_input_method_v1_deactivate(struct ::zwp_input_method_context_v1 *context)
{
QCOMPARE(context, m_context);
zwp_input_method_context_v1_destroy(context);
m_context = nullptr;
if (m_inputSurface) {
m_inputSurface->release();

View file

@ -300,9 +300,7 @@ void InputMethod::textInputInterfaceV3EnabledChanged()
setActive(t3->isEnabled());
if (!t3->isEnabled()) {
// reset value of preedit when textinput is disabled
preedit.text = QString();
preedit.begin = 0;
preedit.end = 0;
resetPendingPreedit();
}
auto context = waylandServer()->inputMethod()->context();
if (context) {
@ -479,8 +477,7 @@ void InputMethod::setPreeditCursor(qint32 index)
}
auto t3 = waylandServer()->seat()->textInputV3();
if (t3 && t3->isEnabled()) {
preedit.begin = index;
preedit.end = index;
preedit.cursor = index;
}
}
@ -490,6 +487,13 @@ void InputMethod::setPreeditStyling(quint32 index, quint32 length, quint32 style
if (t2 && t2->isEnabled()) {
t2->preEditStyling(index, length, style);
}
auto t3 = waylandServer()->seat()->textInputV3();
if (t3 && t3->isEnabled()) {
// preedit style: highlight(4) or selection(6)
if (style == 4 || style == 6) {
preedit.highlightRanges.emplace_back(index, index + length);
}
}
}
void InputMethod::setPreeditString(uint32_t serial, const QString &text, const QString &commit)
@ -503,10 +507,36 @@ void InputMethod::setPreeditString(uint32_t serial, const QString &text, const Q
if (t3 && t3->isEnabled()) {
preedit.text = text;
if (!preedit.text.isEmpty()) {
t3->sendPreEditString(preedit.text, preedit.begin, preedit.end);
quint32 cursor = 0, cursorEnd = 0;
if (preedit.cursor > 0) {
cursor = cursorEnd = preedit.cursor;
}
// Check if we can convert highlight style to a range of selection.
if (!preedit.highlightRanges.empty()) {
std::sort(preedit.highlightRanges.begin(), preedit.highlightRanges.end());
// Check if starting point matches.
if (preedit.highlightRanges.front().first == cursor) {
quint32 end = preedit.highlightRanges.front().second;
bool nonContinousHighlight = false;
for (size_t i = 1 ; i < preedit.highlightRanges.size(); i ++) {
if (end >= preedit.highlightRanges[i].first) {
end = std::max(end, preedit.highlightRanges[i].second);
} else {
nonContinousHighlight = true;
break;
}
}
if (!nonContinousHighlight) {
cursorEnd = end;
}
}
}
t3->sendPreEditString(preedit.text, cursor, cursorEnd);
}
t3->done();
}
resetPendingPreedit();
}
void InputMethod::key(quint32 /*serial*/, quint32 /*time*/, quint32 keyCode, bool pressed)
@ -712,4 +742,10 @@ bool InputMethod::isAvailable() const
return !m_inputMethodCommand.isEmpty();
}
void InputMethod::resetPendingPreedit() {
preedit.text = QString();
preedit.cursor = 0;
preedit.highlightRanges.clear();
}
}

View file

@ -9,6 +9,9 @@
#ifndef KWIN_VIRTUAL_KEYBOARD_H
#define KWIN_VIRTUAL_KEYBOARD_H
#include <vector>
#include <utility>
#include <QObject>
#include <kwinglobals.h>
@ -99,11 +102,12 @@ private:
bool touchEventTriggered() const;
void forwardModifiers();
void resetPendingPreedit();
struct {
QString text = QString();
quint32 begin = 0;
quint32 end = 0;
qint32 cursor = 0;
std::vector<std::pair<quint32, quint32>> highlightRanges;
} preedit;
bool m_enabled = true;