Commit 13f06777 authored by Bhushan Shah's avatar Bhushan Shah 📱

server: implement the text-input-unstable-v3

This commit implements the zwp_text_input_v3 in kwayland-server, part of
code is inspired based on the initial patch from Roman at D16735.
parent 5932a957
......@@ -741,7 +741,7 @@ void TextInputTest::testCommit()
QVERIFY(committedSpy.isValid());
ti->setCursorPosition(3, 4);
ti->deleteSurroundingText(2, 1);
ti->commit(QByteArrayLiteral("foo"));
ti->commitString(QByteArrayLiteral("foo"));
QVERIFY(committedSpy.wait());
QCOMPARE(textInput->commitText(), QByteArrayLiteral("foo"));
......
......@@ -115,3 +115,16 @@ add_executable(testLayerShellV1Interface test_layershellv1_interface.cpp ${LAYER
target_link_libraries(testLayerShellV1Interface Qt5::Test Plasma::KWaylandServer KF5::WaylandClient Wayland::Client)
add_test(NAME kwayland-testLayerShellV1Interface COMMAND testLayerShellV1Interface)
ecm_mark_as_test(testLayerShellV1Interface)
########################################################
# Test TextInputV3 Interface
########################################################
ecm_add_qtwayland_client_protocol(TEXTINPUTV3_SRCS
PROTOCOL ${WaylandProtocols_DATADIR}/unstable/text-input/text-input-unstable-v3.xml
BASENAME text-input-unstable-v3
)
add_executable(testTextInputV3Interface test_textinputv3_interface.cpp ${TEXTINPUTV3_SRCS})
target_link_libraries(testTextInputV3Interface Qt5::Test Plasma::KWaylandServer KF5::WaylandClient Wayland::Client)
add_test(NAME kwayland-testTextInputV3Interface COMMAND testTextInputV3Interface)
ecm_mark_as_test(testTextInputV3Interface)
This diff is collapsed.
......@@ -60,6 +60,7 @@ set(SERVER_LIB_SRCS
surfacerole.cpp
tablet_interface.cpp
textinput_v2_interface.cpp
textinput_v3_interface.cpp
touch_interface.cpp
viewporter_interface.cpp
xdgdecoration_v1_interface.cpp
......@@ -166,6 +167,11 @@ ecm_add_qtwayland_server_protocol(SERVER_LIB_SRCS
BASENAME text-input-unstable-v2
)
ecm_add_qtwayland_server_protocol(SERVER_LIB_SRCS
PROTOCOL ${WaylandProtocols_DATADIR}/unstable/text-input/text-input-unstable-v3.xml
BASENAME text-input-unstable-v3
)
ecm_add_wayland_server_protocol(SERVER_LIB_SRCS
PROTOCOL ${WaylandProtocols_DATADIR}/unstable/pointer-gestures/pointer-gestures-unstable-v1.xml
BASENAME pointer-gestures-unstable-v1
......@@ -314,6 +320,8 @@ set(SERVER_GENERATED_SRCS
${CMAKE_CURRENT_BINARY_DIR}/wayland-text-client-protocol.h
${CMAKE_CURRENT_BINARY_DIR}/wayland-text-input-unstable-v2-client-protocol.h
${CMAKE_CURRENT_BINARY_DIR}/wayland-text-input-unstable-v2-server-protocol.h
${CMAKE_CURRENT_BINARY_DIR}/wayland-text-input-unstable-v3-client-protocol.h
${CMAKE_CURRENT_BINARY_DIR}/wayland-text-input-unstable-v3-server-protocol.h
${CMAKE_CURRENT_BINARY_DIR}/wayland-text-server-protocol.h
${CMAKE_CURRENT_BINARY_DIR}/wayland-xdg-decoration-client-protocol.h
${CMAKE_CURRENT_BINARY_DIR}/wayland-xdg-decoration-server-protocol.h
......@@ -420,6 +428,7 @@ set(SERVER_LIB_HEADERS
tablet_interface.h
textinput.h
textinput_v2_interface.h
textinput_v3_interface.h
touch_interface.h
viewporter_interface.h
xdgdecoration_v1_interface.h
......
......@@ -41,6 +41,7 @@
#include "subcompositor_interface.h"
#include "tablet_interface.h"
#include "textinput_v2_interface_p.h"
#include "textinput_v3_interface_p.h"
#include "viewporter_interface.h"
#include "xdgdecoration_v1_interface.h"
#include "xdgforeign_v2_interface.h"
......@@ -350,6 +351,14 @@ TextInputManagerV2Interface *Display::createTextInputManagerV2(QObject *parent)
return t;
}
TextInputManagerV3Interface *Display::createTextInputManagerV3(QObject *parent)
{
auto t = new TextInputManagerV3Interface(this, parent);
connect(this, &Display::aboutToTerminate, t, [t] { delete t; });
return t;
}
XdgShellInterface *Display::createXdgShell(QObject *parent)
{
XdgShellInterface *shell = new XdgShellInterface(this, parent);
......
......@@ -55,6 +55,7 @@ class ServerSideDecorationManagerInterface;
class SlideManagerInterface;
class SubCompositorInterface;
class TextInputManagerV2Interface;
class TextInputManagerV3Interface;
class XdgShellInterface;
enum class RelativePointerInterfaceVersion;
class RelativePointerManagerInterface;
......@@ -199,6 +200,13 @@ public:
**/
TextInputManagerV2Interface *createTextInputManagerV2(QObject *parent = nullptr);
/**
* Create a text input manager v3
* @returns The created manager object
* @since 5.21
*/
TextInputManagerV3Interface *createTextInputManagerV3(QObject *parent = nullptr);
/**
* Creates the XdgShell in interface @p version.
*
......
......@@ -20,6 +20,7 @@
#include "primaryselectionsource_v1_interface.h"
#include "surface_interface.h"
#include "textinput_v2_interface_p.h"
#include "textinput_v3_interface_p.h"
// Qt
#include <QFile>
// Wayland
......@@ -47,6 +48,7 @@ SeatInterface::Private::Private(SeatInterface *q, Display *display)
, q(q)
{
textInputV2 = new TextInputV2Interface(q);
textInputV3 = new TextInputV3Interface(q);
}
#ifndef K_DOXYGEN
......@@ -1585,6 +1587,7 @@ void SeatInterface::setFocusedTextInputSurface(SurfaceInterface *surface)
if (d->focusedTextInputSurface != surface){
d->textInputV2->d->sendLeave(serial, d->focusedTextInputSurface);
d->textInputV3->d->sendLeave(d->focusedTextInputSurface);
d->focusedTextInputSurface = surface;
emit focusedTextInputSurfaceChanged();
}
......@@ -1596,8 +1599,10 @@ void SeatInterface::setFocusedTextInputSurface(SurfaceInterface *surface)
}
);
}
// TODO: setFocusedSurface like in other interfaces
d->textInputV2->d->sendEnter(surface, serial);
d->textInputV3->d->sendEnter(surface);
// TODO: setFocusedSurface like in other interfaces
}
SurfaceInterface *SeatInterface::focusedTextInputSurface() const
......@@ -1612,6 +1617,11 @@ TextInputV2Interface *SeatInterface::textInputV2() const
return d->textInputV2;
}
TextInputV3Interface *SeatInterface::textInputV3() const
{
Q_D();
return d->textInputV3;
}
AbstractDataSource *SeatInterface::selection() const
{
Q_D();
......
......@@ -27,6 +27,7 @@ class AbstractDataSource;
class Display;
class SurfaceInterface;
class TextInputV2Interface;
class TextInputV3Interface;
/**
* Describes the source types for axis events. This indicates to the
......@@ -686,6 +687,8 @@ public:
* @since 5.23
**/
TextInputV2Interface *textInputV2() const;
TextInputV3Interface *textInputV3() const;
///@}
/**
......
......@@ -24,6 +24,7 @@ class DataDeviceInterface;
class DataSourceInterface;
class DataControlDeviceV1Interface;
class TextInputV2Interface;
class TextInputV3Interface;
class PrimarySelectionDeviceV1Interface;
class SeatInterface::Private : public Global::Private
......@@ -57,6 +58,8 @@ public:
// TextInput v2
QPointer<TextInputV2Interface> textInputV2;
QPointer<TextInputV3Interface> textInputV3;
SurfaceInterface *focusedTextInputSurface = nullptr;
QMetaObject::Connection focusedSurfaceDestroyConnection;
......
......@@ -127,7 +127,23 @@ enum class TextInputContentPurpose {
/**
* input for a terminal
*/
Terminal
Terminal,
/**
* input is numeric password
*/
Pin
};
enum class TextInputChangeCause {
/**
* Change caused by input method
*/
InputMethod,
/**
* Something else other than input method caused change
*/
Other
};
}
......@@ -136,5 +152,6 @@ Q_DECLARE_METATYPE(KWaylandServer::TextInputContentHint)
Q_DECLARE_METATYPE(KWaylandServer::TextInputContentHints)
Q_DECLARE_OPERATORS_FOR_FLAGS(KWaylandServer::TextInputContentHints)
Q_DECLARE_METATYPE(KWaylandServer::TextInputContentPurpose)
Q_DECLARE_METATYPE(KWaylandServer::TextInputChangeCause)
#endif // TEXTINPUT_H_INCLUDED
......@@ -158,7 +158,7 @@ void TextInputV2InterfacePrivate::preEdit(const QString &text, const QString &co
}
}
void TextInputV2InterfacePrivate::commit(const QString &text)
void TextInputV2InterfacePrivate::commitString(const QString &text)
{
if (!surface) {
return;
......@@ -417,9 +417,9 @@ void TextInputV2Interface::preEdit(const QString &text, const QString &commit)
d->preEdit(text, commit);
}
void TextInputV2Interface::commit(const QString &text)
void TextInputV2Interface::commitString(const QString &text)
{
d->commit(text);
d->commitString(text);
}
void TextInputV2Interface::keysymPressed(quint32 keysym, Qt::KeyboardModifiers modifiers)
......
......@@ -154,7 +154,7 @@ public:
* @see preEdit
* @see deleteSurroundingText
**/
void commit(const QString &text);
void commitString(const QString &text);
/**
* Sets the cursor position inside the composing text (as byte offset) relative to the
......
......@@ -38,7 +38,7 @@ public:
void sendEnter(SurfaceInterface *surface, quint32 serial);
void sendLeave(quint32 serial, SurfaceInterface *surface);
void preEdit(const QString &text, const QString &commit);
void commit(const QString &text);
void commitString(const QString &text);
void deleteSurroundingText(quint32 beforeLength, quint32 afterLength);
void setTextDirection(Qt::LayoutDirection direction);
void setPreEditCursor(qint32 index);
......
/*
SPDX-FileCopyrightText: 2020 Bhushan Shah <bshah@kde.org>
SPDX-FileCopyrightText: 2018 Roman Glig <subdiff@gmail.com>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "textinput_v3_interface_p.h"
#include "display.h"
#include "seat_interface.h"
#include "surface_interface_p.h"
namespace KWaylandServer
{
static TextInputContentHints convertContentHint(uint32_t hint)
{
const auto hints = zwp_text_input_v3_content_hint(hint);
TextInputContentHints ret = TextInputContentHint::None;
if (hints & QtWaylandServer::zwp_text_input_v3::content_hint_completion) {
ret |= TextInputContentHint::AutoCompletion;
}
if (hints & QtWaylandServer::zwp_text_input_v3::content_hint_auto_capitalization) {
ret |= TextInputContentHint::AutoCapitalization;
}
if (hints & QtWaylandServer::zwp_text_input_v3::content_hint_lowercase) {
ret |= TextInputContentHint::LowerCase;
}
if (hints & QtWaylandServer::zwp_text_input_v3::content_hint_uppercase) {
ret |= TextInputContentHint::UpperCase;
}
if (hints & QtWaylandServer::zwp_text_input_v3::content_hint_titlecase) {
ret |= TextInputContentHint::TitleCase;
}
if (hints & QtWaylandServer::zwp_text_input_v3::content_hint_hidden_text) {
ret |= TextInputContentHint::HiddenText;
}
if (hints & QtWaylandServer::zwp_text_input_v3::content_hint_sensitive_data) {
ret |= TextInputContentHint::SensitiveData;
}
if (hints & QtWaylandServer::zwp_text_input_v3::content_hint_latin) {
ret |= TextInputContentHint::Latin;
}
if (hints & QtWaylandServer::zwp_text_input_v3::content_hint_multiline) {
ret |= TextInputContentHint::MultiLine;
}
if (hints & QtWaylandServer::zwp_text_input_v3::content_hint_spellcheck) {
ret |= TextInputContentHint::AutoCorrection;
}
return ret;
}
static TextInputContentPurpose convertContentPurpose(uint32_t purpose)
{
const auto wlPurpose = QtWaylandServer::zwp_text_input_v3::content_purpose(purpose);
switch (wlPurpose) {
case QtWaylandServer::zwp_text_input_v3::content_purpose_alpha:
return TextInputContentPurpose::Alpha;
case QtWaylandServer::zwp_text_input_v3::content_purpose_digits:
return TextInputContentPurpose::Digits;
case QtWaylandServer::zwp_text_input_v3::content_purpose_number:
return TextInputContentPurpose::Number;
case QtWaylandServer::zwp_text_input_v3::content_purpose_phone:
return TextInputContentPurpose::Phone;
case QtWaylandServer::zwp_text_input_v3::content_purpose_url:
return TextInputContentPurpose::Url;
case QtWaylandServer::zwp_text_input_v3::content_purpose_email:
return TextInputContentPurpose::Email;
case QtWaylandServer::zwp_text_input_v3::content_purpose_name:
return TextInputContentPurpose::Name;
case QtWaylandServer::zwp_text_input_v3::content_purpose_password:
return TextInputContentPurpose::Password;
case QtWaylandServer::zwp_text_input_v3::content_purpose_pin:
return TextInputContentPurpose::Pin;
case QtWaylandServer::zwp_text_input_v3::content_purpose_date:
return TextInputContentPurpose::Date;
case QtWaylandServer::zwp_text_input_v3::content_purpose_time:
return TextInputContentPurpose::Time;
case QtWaylandServer::zwp_text_input_v3::content_purpose_datetime:
return TextInputContentPurpose::DateTime;
case QtWaylandServer::zwp_text_input_v3::content_purpose_terminal:
return TextInputContentPurpose::Terminal;
case QtWaylandServer::zwp_text_input_v3::content_purpose_normal:
return TextInputContentPurpose::Normal;
default:
return TextInputContentPurpose::Normal;
}
}
static TextInputChangeCause convertChangeCause(uint32_t cause)
{
const auto wlCause = QtWaylandServer::zwp_text_input_v3::change_cause(cause);
switch (wlCause) {
case QtWaylandServer::zwp_text_input_v3::change_cause_input_method:
return TextInputChangeCause::InputMethod;
case QtWaylandServer::zwp_text_input_v3::change_cause_other:
default:
return TextInputChangeCause::Other;
}
}
const quint32 TextInputManagerV3InterfacePrivate::s_version = 1;
TextInputManagerV3InterfacePrivate::TextInputManagerV3InterfacePrivate(TextInputManagerV3Interface *_q, Display *display)
: QtWaylandServer::zwp_text_input_manager_v3(*display, s_version)
, q(_q)
{
}
void TextInputManagerV3InterfacePrivate::zwp_text_input_manager_v3_destroy(Resource *resource)
{
wl_resource_destroy(resource->handle);
}
void TextInputManagerV3InterfacePrivate::zwp_text_input_manager_v3_get_text_input(Resource *resource, uint32_t id, wl_resource *seat)
{
SeatInterface *s = SeatInterface::get(seat);
if (!s) {
wl_resource_post_error(resource->handle, 0, "Invalid seat");
return;
}
TextInputV3InterfacePrivate *textInputPrivate = TextInputV3InterfacePrivate::get(s->textInputV3());
textInputPrivate->add(resource->client(), id, resource->version());
}
TextInputManagerV3Interface::TextInputManagerV3Interface(Display* display, QObject* parent)
: QObject(parent)
, d(new TextInputManagerV3InterfacePrivate(this, display))
{
}
TextInputManagerV3Interface::~TextInputManagerV3Interface() = default;
TextInputV3InterfacePrivate::TextInputV3InterfacePrivate(SeatInterface *seat, TextInputV3Interface *_q)
: seat(seat)
, q(_q)
{
}
void TextInputV3InterfacePrivate::sendEnter(SurfaceInterface *s)
{
if (!s) {
return;
}
surface = QPointer<SurfaceInterface>(s);
const auto clientResources = textInputsForClient(s->client());
for (auto resource : clientResources) {
send_enter(resource->handle, s->resource());
}
}
void TextInputV3InterfacePrivate::sendLeave(SurfaceInterface *s)
{
if (!s) {
return;
}
surface.clear();
const auto clientResources = textInputsForClient(s->client());
for (auto resource : clientResources) {
send_leave(resource->handle, s->resource());
}
}
void TextInputV3InterfacePrivate::sendPreEdit(const QString &text, const quint32 cursorBegin, const quint32 cursorEnd)
{
if (!surface) {
return;
}
const QList<Resource *> textInputs = textInputsForClient(surface->client());
for (auto resource : textInputs) {
send_preedit_string(resource->handle, text, cursorBegin, cursorEnd);
}
}
void TextInputV3InterfacePrivate::commitString(const QString &text)
{
if (!surface) {
return;
}
const QList<Resource *> textInputs = textInputsForClient(surface->client());
for (auto resource : textInputs) {
send_commit_string(resource->handle, text);
}
}
void TextInputV3InterfacePrivate::deleteSurroundingText(quint32 before, quint32 after)
{
if (!surface) {
return;
}
const QList<Resource *> textInputs = textInputsForClient(surface->client());
for (auto resource : textInputs) {
send_delete_surrounding_text(resource->handle, before, after);
}
}
void TextInputV3InterfacePrivate::done()
{
if (!surface) {
return;
}
const QList<Resource *> textInputs = textInputsForClient(surface->client());
for (auto resource : textInputs) {
// zwp_text_input_v3.done takes the serial argument which is equal to number of commit requests issued
send_done(resource->handle, serial);
}
}
QList<TextInputV3InterfacePrivate::Resource *> TextInputV3InterfacePrivate::textInputsForClient(ClientConnection *client) const
{
return resourceMap().values(client->client());
}
void TextInputV3InterfacePrivate::zwp_text_input_v3_enable(Resource *resource)
{
// reset pending state to default
Q_UNUSED(resource)
defaultPending();
pending.enabled = true;
}
void TextInputV3InterfacePrivate::zwp_text_input_v3_disable(Resource *resource)
{
// reset pending state to default
Q_UNUSED(resource)
defaultPending();
}
void TextInputV3InterfacePrivate::zwp_text_input_v3_set_surrounding_text(Resource *resource, const QString &text, int32_t cursor, int32_t anchor)
{
Q_UNUSED(resource)
// zwp_text_input_v3_set_surrounding_text is no-op if enabled request is not pending
if (!pending.enabled) {
return;
}
pending.surroundingText = text;
pending.surroundingTextCursorPosition = cursor;
pending.surroundingTextSelectionAnchor = anchor;
}
void TextInputV3InterfacePrivate::zwp_text_input_v3_set_content_type(Resource *resource, uint32_t hint, uint32_t purpose)
{
Q_UNUSED(resource)
// zwp_text_input_v3_set_content_type is no-op if enabled request is not pending
if (!pending.enabled) {
return;
}
pending.contentHints = convertContentHint(hint);
pending.contentPurpose = convertContentPurpose(purpose);
}
void TextInputV3InterfacePrivate::zwp_text_input_v3_set_cursor_rectangle(Resource *resource, int32_t x, int32_t y, int32_t width, int32_t height)
{
Q_UNUSED(resource)
// zwp_text_input_v3_set_cursor_rectangle is no-op if enabled request is not pending
if (!pending.enabled) {
return;
}
pending.cursorRectangle = QRect(x, y, width, height);
}
void TextInputV3InterfacePrivate::zwp_text_input_v3_set_text_change_cause(Resource *resource, uint32_t cause)
{
Q_UNUSED(resource)
// zwp_text_input_v3_set_text_change_cause is no-op if enabled request is not pending
if (!pending.enabled) {
return;
}
pending.surroundingTextChangeCause = convertChangeCause(cause);
}
void TextInputV3InterfacePrivate::zwp_text_input_v3_commit(Resource *resource)
{
Q_UNUSED(resource)
serial++;
if (enabled != pending.enabled) {
enabled = pending.enabled;
emit q->enabledChanged();
}
if (surroundingTextChangeCause != pending.surroundingTextChangeCause) {
surroundingTextChangeCause = pending.surroundingTextChangeCause;
pending.surroundingTextChangeCause = TextInputChangeCause::InputMethod;
}
if (contentHints != pending.contentHints || contentPurpose != pending.contentPurpose) {
contentHints = pending.contentHints;
contentPurpose = pending.contentPurpose;
if (enabled) {
emit q->contentTypeChanged();
}
}
if (cursorRectangle != pending.cursorRectangle) {
cursorRectangle = pending.cursorRectangle;
if (enabled) {
emit q->cursorRectangleChanged(cursorRectangle);
}
}
if (surroundingText != pending.surroundingText || surroundingTextCursorPosition != pending.surroundingTextCursorPosition || surroundingTextSelectionAnchor != pending.surroundingTextSelectionAnchor) {
surroundingText = pending.surroundingText;
surroundingTextCursorPosition = pending.surroundingTextCursorPosition;
surroundingTextSelectionAnchor = pending.surroundingTextSelectionAnchor;
if (enabled) {
emit q->surroundingTextChanged();
}
}
emit q->stateCommitted(serial);
}
void TextInputV3InterfacePrivate::defaultPending()
{
pending.cursorRectangle = QRect();
pending.surroundingTextChangeCause = TextInputChangeCause::InputMethod;
pending.contentHints = TextInputContentHints(TextInputContentHint::None);
pending.contentPurpose = TextInputContentPurpose::Normal;
pending.enabled = false;
pending.surroundingText = QString();
pending.surroundingTextCursorPosition = 0;
pending.surroundingTextSelectionAnchor = 0;
}
TextInputV3Interface::TextInputV3Interface(SeatInterface *seat)
: QObject()
, d(new TextInputV3InterfacePrivate(seat, this))
{
}
TextInputV3Interface::~TextInputV3Interface() = default;
TextInputContentHints TextInputV3Interface::contentHints() const
{
return d->contentHints;
}
TextInputContentPurpose TextInputV3Interface::contentPurpose() const
{
return d->contentPurpose;
}
QString TextInputV3Interface::surroundingText() const