diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt --- a/src/server/CMakeLists.txt +++ b/src/server/CMakeLists.txt @@ -19,6 +19,7 @@ idle_interface.cpp idleinhibit_interface.cpp idleinhibit_interface_v1.cpp + inputmethod_interface.cpp keyboard_interface.cpp keystate_interface.cpp linuxdmabuf_v1_interface.cpp @@ -234,6 +235,11 @@ BASENAME tablet-unstable-v2 ) +ecm_add_qtwayland_server_protocol(SERVER_LIB_SRCS + PROTOCOL ${WaylandProtocols_DATADIR}/unstable/input-method/input-method-unstable-v1.xml + BASENAME input-method-unstable-v1 +) + set(SERVER_GENERATED_SRCS ${CMAKE_CURRENT_BINARY_DIR}/wayland-blur-client-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-blur-server-protocol.h @@ -350,6 +356,7 @@ global.h idle_interface.h idleinhibit_interface.h + inputmethod_interface.h keyboard_interface.h keystate_interface.h linuxdmabuf_v1_interface.h diff --git a/src/server/display.h b/src/server/display.h --- a/src/server/display.h +++ b/src/server/display.h @@ -80,6 +80,8 @@ class KeyStateInterface; class LinuxDmabufUnstableV1Interface; class TabletManagerInterface; +class InputMethodInterface; +class InputPanelInterface; /** * @brief Class holding the Wayland server display loop. @@ -311,6 +313,9 @@ */ EglStreamControllerInterface *createEglStreamControllerInterface(QObject *parent = nullptr); + InputMethodInterface *createInputMethodInterface(QObject *parent = nullptr); + InputPanelInterface *createInputPanelInterface(QObject *parent = nullptr); + /** * Creates the entry point to support wacom-like tablets and pens. * diff --git a/src/server/display.cpp b/src/server/display.cpp --- a/src/server/display.cpp +++ b/src/server/display.cpp @@ -45,6 +45,7 @@ #include "xdgshell_stable_interface_p.h" #include "xdgshell_v5_interface_p.h" #include "xdgshell_v6_interface_p.h" +#include "inputmethod_interface.h" #include #include @@ -531,6 +532,20 @@ return d; } +InputMethodInterface *Display::createInputMethodInterface(QObject *parent) +{ + auto d = new InputMethodInterface(this, parent); + connect(this, &Display::aboutToTerminate, d, [d] { delete d; }); + return d; +} + +InputPanelInterface *Display::createInputPanelInterface(QObject *parent) +{ + auto p = new InputPanelInterface(this, parent); + connect(this, &Display::aboutToTerminate, p, [p] { delete p; }); + return p; +} + void Display::createShm() { Q_ASSERT(d->display); diff --git a/src/server/inputmethod_interface.h b/src/server/inputmethod_interface.h new file mode 100644 --- /dev/null +++ b/src/server/inputmethod_interface.h @@ -0,0 +1,144 @@ +/* + SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#ifndef WAYLAND_SERVER_INPUTMETHOD_INTERFACE_H +#define WAYLAND_SERVER_INPUTMETHOD_INTERFACE_H + +#include +#include +#include + +namespace KWayland +{ +namespace Server +{ +class OutputInterface; +class SurfaceInterface; +class Display; +class InputPanelSurfaceInterface; +class InputMethodContextInterface; + +/** + * Implements the input-method-unstable-v1.xml protocol + * + * @since 5.68 + */ +class KWAYLANDSERVER_EXPORT InputMethodInterface : public QObject +{ + Q_OBJECT +public: + InputMethodInterface(Display *d, QObject *parent); + ~InputMethodInterface() override; + + /** + * Activates the input method. + */ + void sendActivate(); + + /** + * Deactivates the input method, probably because we're not on some area + * where we can write text. + */ + void sendDeactivate(); + + InputMethodContextInterface *context() const; + +private: + class Private; + QScopedPointer d; +}; + +/** + * Offers information about the input context and allows us to interact with it. + * + * @since 5.68 + */ +class KWAYLANDSERVER_EXPORT InputMethodContextInterface : public QObject +{ + Q_OBJECT +public: + ~InputMethodContextInterface() override; + + void sendSurroundingText(const QString &text, quint32 cursor, quint32 anchor); + void sendReset(); + void sendContentType(quint32 hint, quint32 purpose); + void sendInvokeAction(quint32 button, quint32 index); + void sendCommitState(quint32 serial); + void sendPreferredLanguage(const QString &language); + +Q_SIGNALS: + void commitString(quint32 serial, const QString &text); + void preeditString(quint32 serial, const QString &text, const QString &commit); + void preeditStyling(quint32 index, quint32 length, quint32 style); + void preeditCursor(qint32 index); + void deleteSurroundingText(qint32 index, quint32 length); + void cursorPosition(qint32 index, qint32 anchor); + void keysym(quint32 serial, quint32 time, quint32 sym, bool pressed, Qt::KeyboardModifiers modifiers); + void grabKeyboard(quint32 keyboard); + void key(quint32 serial, quint32 time, quint32 key, bool pressed); + void modifiers(quint32 serial, Qt::KeyboardModifiers mods_depressed, Qt::KeyboardModifiers mods_latched, Qt::KeyboardModifiers mods_locked, quint32 group); + void language(quint32 serial, const QString &language); + void textDirection(quint32 serial, Qt::LayoutDirection direction); + void destroy(); + +private: + friend class InputMethodInterface; + InputMethodContextInterface(InputMethodInterface *parent); + class Private; + QScopedPointer d; +}; + +class KWAYLANDSERVER_EXPORT InputPanelInterface : public QObject +{ + Q_OBJECT +public: + InputPanelInterface(Display *d, QObject *parent); + ~InputPanelInterface() override; + +Q_SIGNALS: + void inputPanelSurfaceAdded(InputPanelSurfaceInterface *surface); + +private: + class Private; + QScopedPointer d; +}; + +/** + * Representation of the panel's surface to tell the compositor where and how to show + * + * @since 5.68 + */ +class KWAYLANDSERVER_EXPORT InputPanelSurfaceInterface : public QObject +{ + Q_OBJECT +public: + ~InputPanelSurfaceInterface() override; + + enum Position { + CenterBottom = 0 + }; + Q_ENUM(Position) + + quint32 id() const; + SurfaceInterface *surface() const; + +Q_SIGNALS: + void topLevel(OutputInterface *output, Position position); + void overlayPanel(); + +private: + InputPanelSurfaceInterface(QObject *parent); + friend class InputPanelInterface; + class Private; + QScopedPointer d; +}; + +} +} + +Q_DECLARE_METATYPE(KWayland::Server::InputMethodInterface *) + +#endif diff --git a/src/server/inputmethod_interface.cpp b/src/server/inputmethod_interface.cpp new file mode 100644 --- /dev/null +++ b/src/server/inputmethod_interface.cpp @@ -0,0 +1,341 @@ +/* + SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#include "inputmethod_interface.h" +#include "resource_p.h" +#include "seat_interface.h" +#include "display.h" +#include "surface_interface.h" +#include "output_interface.h" +#include "surfacerole_p.h" + +#include +#include "qwayland-server-input-method-unstable-v1.h" +#include "wayland-text-server-protocol.h" + +using namespace KWayland; +using namespace Server; + +static int s_version = 1; + +using namespace KWayland::Server; + +class Q_DECL_HIDDEN InputMethodContextInterface::Private : public QtWaylandServer::zwp_input_method_context_v1 +{ +public: + Private(InputMethodContextInterface *q) + : zwp_input_method_context_v1() + , q(q) + { + } + + void zwp_input_method_context_v1_commit_string(Resource *, uint32_t serial, const QString &text) override + { + Q_EMIT q->commitString(serial, text); + } + void zwp_input_method_context_v1_preedit_string(Resource *, uint32_t serial, const QString &text, const QString &commit) override + { + Q_EMIT q->preeditString(serial, text, commit); + } + + void zwp_input_method_context_v1_preedit_styling(Resource *, uint32_t index, uint32_t length, uint32_t style) override + { + Q_EMIT q->preeditStyling(index, length, style); + } + void zwp_input_method_context_v1_preedit_cursor(Resource *, int32_t index) override + { + Q_EMIT q->preeditCursor(index); + } + void zwp_input_method_context_v1_delete_surrounding_text(Resource *, int32_t index, uint32_t length) override + { + Q_EMIT q->deleteSurroundingText(index, length); + } + void zwp_input_method_context_v1_cursor_position(Resource *, int32_t index, int32_t anchor) override + { + Q_EMIT q->cursorPosition(index, anchor); + } + void zwp_input_method_context_v1_modifiers_map(Resource *, wl_array *map) override + { + const QList modifiersMap = QByteArray::fromRawData(static_cast(map->data), map->size).split('\0'); + + mods.clear(); + for (const QByteArray &modifier : modifiersMap) { + if (modifier == "Shift") + mods << Qt::ShiftModifier; + else if (modifier == "Alt") + mods << Qt::AltModifier; + else if (modifier == "Control") + mods << Qt::ControlModifier; + else if (modifier == "Mod1") + mods << Qt::AltModifier; + else if (modifier == "Mod4") + mods << Qt::MetaModifier; + else + mods << Qt::NoModifier; + } + } + void zwp_input_method_context_v1_keysym(Resource *, uint32_t serial, uint32_t time, uint32_t sym, uint32_t state, uint32_t modifiers) override + { + Q_EMIT q->keysym(serial, time, sym, state == WL_KEYBOARD_KEY_STATE_PRESSED, toQtModifiers(modifiers)); + } + void zwp_input_method_context_v1_grab_keyboard(Resource *, uint32_t keyboard) override + { + Q_EMIT q->grabKeyboard(keyboard); + } + void zwp_input_method_context_v1_key(Resource *, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) override + { + Q_EMIT q->key(serial, time, key, state == WL_KEYBOARD_KEY_STATE_PRESSED); + } + void zwp_input_method_context_v1_modifiers(Resource *, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) override + { + Q_EMIT q->modifiers(serial, toQtModifiers(mods_depressed), toQtModifiers(mods_latched), toQtModifiers(mods_locked), group); + } + void zwp_input_method_context_v1_language(Resource *, uint32_t serial, const QString &language) override + { + Q_EMIT q->language(serial, language); + } + void zwp_input_method_context_v1_text_direction(Resource *, uint32_t serial, uint32_t direction) override + { + Qt::LayoutDirection qtDirection; + switch (direction) { + case WL_TEXT_INPUT_TEXT_DIRECTION_LTR: + qtDirection = Qt::LeftToRight; + break; + case WL_TEXT_INPUT_TEXT_DIRECTION_RTL: + qtDirection = Qt::RightToLeft; + break; + case WL_TEXT_INPUT_TEXT_DIRECTION_AUTO: + qtDirection = Qt::LayoutDirectionAuto; + break; + } + Q_EMIT q->textDirection(serial, qtDirection); + } + + Qt::KeyboardModifiers toQtModifiers(uint32_t modifiers) + { + Qt::KeyboardModifiers ret = Qt::NoModifier; + for (int i = 0; modifiers >>= 1; ++i) { + ret |= mods[i]; + } + return ret; + } + + void zwp_input_method_context_v1_destroy_resource(Resource *resource) override + { + Q_UNUSED(resource) + q->deleteLater(); + } + + void zwp_input_method_context_v1_destroy(Resource *resource) override + { + Q_EMIT q->destroy(); + wl_resource_destroy(resource->handle); + } + +private: + InputMethodContextInterface *const q; + QVector mods; +}; + +InputMethodContextInterface::InputMethodContextInterface(InputMethodInterface *parent) + : QObject(parent) + , d(new InputMethodContextInterface::Private(this)) +{ +} + +InputMethodContextInterface::~InputMethodContextInterface() = default; + +void InputMethodContextInterface::sendCommitState(uint32_t serial) +{ + for (auto r : d->resourceMap()) + d->send_commit_state(r->handle, serial); +} + +void InputMethodContextInterface::sendContentType(uint32_t hint, uint32_t purpose) +{ + for (auto r : d->resourceMap()) + d->send_content_type(r->handle, hint, purpose); +} + +void InputMethodContextInterface::sendInvokeAction(uint32_t button, uint32_t index) +{ + for (auto r : d->resourceMap()) + d->send_invoke_action(r->handle, button, index); +} + +void InputMethodContextInterface::sendPreferredLanguage(const QString &language) +{ + for (auto r : d->resourceMap()) + d->send_preferred_language(r->handle, language); +} + +void InputMethodContextInterface::sendReset() +{ + for (auto r : d->resourceMap()) + d->send_reset(r->handle); +} + +void InputMethodContextInterface::sendSurroundingText(const QString &text, uint32_t cursor, uint32_t anchor) +{ + for (auto r : d->resourceMap()) + d->send_surrounding_text(r->handle, text, cursor, anchor); +} + +class Q_DECL_HIDDEN InputPanelSurfaceInterface::Private : public QtWaylandServer::zwp_input_panel_surface_v1, public SurfaceRole +{ +public: + Private(InputPanelSurfaceInterface *q) + : zwp_input_panel_surface_v1() + , SurfaceRole() + , q(q) + { + } + + void zwp_input_panel_surface_v1_set_overlay_panel(Resource *) override + { + Q_EMIT q->overlayPanel(); + } + void zwp_input_panel_surface_v1_set_toplevel(Resource *, struct ::wl_resource *output, uint32_t position) override + { + Q_EMIT q->topLevel(OutputInterface::get(output), Position(position)); + } + + void zwp_input_panel_surface_v1_destroy_resource(Resource * resource) override { + wl_resource_destroy(resource->handle); + if (resourceMap().isEmpty()) + q->deleteLater(); + } + + void commit() override {} + + InputPanelSurfaceInterface *const q; + quint32 m_id = 0; +}; + +InputPanelSurfaceInterface::InputPanelSurfaceInterface(QObject *parent) + : QObject(parent) + , d(new InputPanelSurfaceInterface::Private(this)) +{ +} + +InputPanelSurfaceInterface::~InputPanelSurfaceInterface() = default; + +class Q_DECL_HIDDEN InputPanelInterface::Private : public QObject, public QtWaylandServer::zwp_input_panel_v1 +{ +public: + Private(InputPanelInterface *q, Display *d) + : zwp_input_panel_v1(*d, s_version) + , q(q) + { + } + + void zwp_input_panel_v1_get_input_panel_surface(Resource *resource, uint32_t id, struct ::wl_resource *surfaceResource) override + { + auto interface = new InputPanelSurfaceInterface(nullptr); + interface->d->init(resource->client(), id, resource->version()); + interface->d->m_id = id; + interface->d->setSurface(SurfaceInterface::get(surfaceResource)); + + Q_EMIT q->inputPanelSurfaceAdded(interface); + } + + void zwp_input_panel_v1_destroy_resource(Resource *resource) override + { + wl_resource_destroy(resource->handle); + if (resourceMap().isEmpty()) + q->deleteLater(); + } + + InputPanelInterface *const q; +}; + +InputPanelInterface::InputPanelInterface(Display *d, QObject *parent) + : QObject(parent) + , d(new InputPanelInterface::Private(this, d)) +{ +} + +InputPanelInterface::~InputPanelInterface() = default; + +quint32 InputPanelSurfaceInterface::id() const +{ + return d->m_id; +} + +SurfaceInterface *InputPanelSurfaceInterface::surface() const +{ + return d->surface(); +} + +class Q_DECL_HIDDEN InputMethodInterface::Private : public QtWaylandServer::zwp_input_method_v1 +{ +public: + Private(Display *d, InputMethodInterface *q) + : zwp_input_method_v1(*d, s_version) + , q(q) + , m_display(d) + { + } + + void zwp_input_method_v1_bind_resource(Resource *resource) override + { + if (!m_context) + return; + + auto x = m_context->d->add(resource->client(), resource->version()); + + if (m_enabled) { + send_activate(resource->handle, x->handle); + } + } + + InputMethodContextInterface *m_context = nullptr; + InputMethodInterface *const q; + Display *const m_display; + + bool m_enabled = false; +}; + +InputMethodInterface::InputMethodInterface(Display *d, QObject *parent) + : QObject(parent) + , d(new InputMethodInterface::Private(d, this)) +{ +} + +InputMethodInterface::~InputMethodInterface() = default; + +void InputMethodInterface::sendActivate() +{ + if (d->m_enabled) + return; + + Q_ASSERT(!d->m_context); + d->m_context = new InputMethodContextInterface(this); + + d->m_enabled = true; + for (auto resource : d->resourceMap()) { + auto connection = d->m_context->d->add(resource->client(), resource->version()); + d->send_activate(resource->handle, connection->handle); + } +} + +void InputMethodInterface::sendDeactivate() +{ + if (!d->m_enabled) + return; + + d->m_enabled = false; + for (auto resource : d->resourceMap()) { + auto connection = d->m_context->d->resourceMap().value(resource->client()); + d->send_deactivate(connection->handle, resource->handle); + } + d->m_context = nullptr; +} + +InputMethodContextInterface *InputMethodInterface::context() const +{ + return d->m_context; +} diff --git a/src/server/surfacerole.cpp b/src/server/surfacerole.cpp --- a/src/server/surfacerole.cpp +++ b/src/server/surfacerole.cpp @@ -13,6 +13,10 @@ namespace Server { +SurfaceRole::SurfaceRole() +{ +} + SurfaceRole::SurfaceRole(SurfaceInterface *surface) : m_surface(surface) { @@ -27,5 +31,13 @@ } } +void SurfaceRole::setSurface(SurfaceInterface *surface) +{ + m_surface = surface; + if (m_surface) { + m_surface->d_func()->role = this; + } +} + } } diff --git a/src/server/surfacerole_p.h b/src/server/surfacerole_p.h --- a/src/server/surfacerole_p.h +++ b/src/server/surfacerole_p.h @@ -19,9 +19,17 @@ class SurfaceRole { public: + explicit SurfaceRole(); explicit SurfaceRole(SurfaceInterface *surface); virtual ~SurfaceRole(); + const QPointer &surface() const + { + return m_surface; + } + + void setSurface(SurfaceInterface *surface); + virtual void commit() = 0; private: