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:
parent
f50866d3f8
commit
c074e2eb42
5 changed files with 204 additions and 34 deletions
|
@ -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"
|
||||
|
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue