diff options
author | Dominik Holland <dominik.holland@qt.io> | 2022-01-19 11:17:19 +0100 |
---|---|---|
committer | Dominik Holland <dominik.holland@qt.io> | 2022-02-02 13:02:17 +0000 |
commit | e68f0f05daca454a2c793c5862b78f78d100a805 (patch) | |
tree | 1bbd38e3af6c20f0ae43a5246ec2389cc5502f42 | |
parent | 1eded03865193d95fab7fd9279e34fb0dbe0cb45 (diff) | |
download | qtwayland-e68f0f05daca454a2c793c5862b78f78d100a805.tar.gz |
Add client support for the text-input-unstable-v1 protocol
This is used by weston for forwarding virtualkeyboard related
event from keyboard applications to a Qt client.
Right now Qt only supports text-input-unstable-v2, v4 and the
special qt-input-method protocol, while weston only supports
text-input-unstable-v1.
Without this, a virtual-keyboard application can't be used
with a Qt client within weston.
Change-Id: I9a34a87100854bb0b0f76762ced56419e70c297e
Reviewed-by: Inho Lee <inho.lee@qt.io>
Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
-rw-r--r-- | src/3rdparty/protocol/text-input-unstable-v1.xml | 385 | ||||
-rw-r--r-- | src/client/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/client/qwaylanddisplay.cpp | 38 | ||||
-rw-r--r-- | src/client/qwaylanddisplay_p.h | 3 | ||||
-rw-r--r-- | src/client/qwaylandinputcontext.cpp | 4 | ||||
-rw-r--r-- | src/client/qwaylandinputdevice.cpp | 7 | ||||
-rw-r--r-- | src/client/qwaylandintegration.cpp | 4 | ||||
-rw-r--r-- | src/client/qwaylandtextinputv1.cpp | 395 | ||||
-rw-r--r-- | src/client/qwaylandtextinputv1_p.h | 149 | ||||
-rw-r--r-- | sync.profile | 2 |
10 files changed, 983 insertions, 6 deletions
diff --git a/src/3rdparty/protocol/text-input-unstable-v1.xml b/src/3rdparty/protocol/text-input-unstable-v1.xml new file mode 100644 index 00000000..6ee26652 --- /dev/null +++ b/src/3rdparty/protocol/text-input-unstable-v1.xml @@ -0,0 +1,385 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="text_input_unstable_v1"> + + <copyright> + Copyright © 2012, 2013 Intel Corporation + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + </copyright> + + <interface name="zwp_text_input_v1" version="1"> + <description summary="text input"> + An object used for text input. Adds support for text input and input + methods to applications. A text_input object is created from a + wl_text_input_manager and corresponds typically to a text entry in an + application. + + Requests are used to activate/deactivate the text_input object and set + state information like surrounding and selected text or the content type. + The information about entered text is sent to the text_input object via + the pre-edit and commit events. Using this interface removes the need + for applications to directly process hardware key events and compose text + out of them. + + Text is generally UTF-8 encoded, indices and lengths are in bytes. + + Serials are used to synchronize the state between the text input and + an input method. New serials are sent by the text input in the + commit_state request and are used by the input method to indicate + the known text input state in events like preedit_string, commit_string, + and keysym. The text input can then ignore events from the input method + which are based on an outdated state (for example after a reset). + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + </description> + + <request name="activate"> + <description summary="request activation"> + Requests the text_input object to be activated (typically when the + text entry gets focus). + + The seat argument is a wl_seat which maintains the focus for this + activation. The surface argument is a wl_surface assigned to the + text_input object and tracked for focus lost. The enter event + is emitted on successful activation. + </description> + <arg name="seat" type="object" interface="wl_seat"/> + <arg name="surface" type="object" interface="wl_surface"/> + </request> + + <request name="deactivate"> + <description summary="request deactivation"> + Requests the text_input object to be deactivated (typically when the + text entry lost focus). The seat argument is a wl_seat which was used + for activation. + </description> + <arg name="seat" type="object" interface="wl_seat"/> + </request> + + <request name="show_input_panel"> + <description summary="show input panels"> + Requests input panels (virtual keyboard) to show. + </description> + </request> + + <request name="hide_input_panel"> + <description summary="hide input panels"> + Requests input panels (virtual keyboard) to hide. + </description> + </request> + + <request name="reset"> + <description summary="reset"> + Should be called by an editor widget when the input state should be + reset, for example after the text was changed outside of the normal + input method flow. + </description> + </request> + + <request name="set_surrounding_text"> + <description summary="sets the surrounding text"> + Sets the plain surrounding text around the input position. Text is + UTF-8 encoded. Cursor is the byte offset within the + surrounding text. Anchor is the byte offset of the + selection anchor within the surrounding text. If there is no selected + text anchor, then it is the same as cursor. + </description> + <arg name="text" type="string"/> + <arg name="cursor" type="uint"/> + <arg name="anchor" type="uint"/> + </request> + + <enum name="content_hint" bitfield="true"> + <description summary="content hint"> + Content hint is a bitmask to allow to modify the behavior of the text + input. + </description> + <entry name="none" value="0x0" summary="no special behaviour"/> + <entry name="default" value="0x7" summary="auto completion, correction and capitalization"/> + <entry name="password" value="0xc0" summary="hidden and sensitive text"/> + <entry name="auto_completion" value="0x1" summary="suggest word completions"/> + <entry name="auto_correction" value="0x2" summary="suggest word corrections"/> + <entry name="auto_capitalization" value="0x4" summary="switch to uppercase letters at the start of a sentence"/> + <entry name="lowercase" value="0x8" summary="prefer lowercase letters"/> + <entry name="uppercase" value="0x10" summary="prefer uppercase letters"/> + <entry name="titlecase" value="0x20" summary="prefer casing for titles and headings (can be language dependent)"/> + <entry name="hidden_text" value="0x40" summary="characters should be hidden"/> + <entry name="sensitive_data" value="0x80" summary="typed text should not be stored"/> + <entry name="latin" value="0x100" summary="just latin characters should be entered"/> + <entry name="multiline" value="0x200" summary="the text input is multiline"/> + </enum> + + <enum name="content_purpose"> + <description summary="content purpose"> + The content purpose allows to specify the primary purpose of a text + input. + + This allows an input method to show special purpose input panels with + extra characters or to disallow some characters. + </description> + <entry name="normal" value="0" summary="default input, allowing all characters"/> + <entry name="alpha" value="1" summary="allow only alphabetic characters"/> + <entry name="digits" value="2" summary="allow only digits"/> + <entry name="number" value="3" summary="input a number (including decimal separator and sign)"/> + <entry name="phone" value="4" summary="input a phone number"/> + <entry name="url" value="5" summary="input an URL"/> + <entry name="email" value="6" summary="input an email address"/> + <entry name="name" value="7" summary="input a name of a person"/> + <entry name="password" value="8" summary="input a password (combine with password or sensitive_data hint)"/> + <entry name="date" value="9" summary="input a date"/> + <entry name="time" value="10" summary="input a time"/> + <entry name="datetime" value="11" summary="input a date and time"/> + <entry name="terminal" value="12" summary="input for a terminal"/> + </enum> + + <request name="set_content_type"> + <description summary="set content purpose and hint"> + Sets the content purpose and content hint. While the purpose is the + basic purpose of an input field, the hint flags allow to modify some + of the behavior. + + When no content type is explicitly set, a normal content purpose with + default hints (auto completion, auto correction, auto capitalization) + should be assumed. + </description> + <arg name="hint" type="uint" enum="content_hint" /> + <arg name="purpose" type="uint" enum="content_purpose" /> + </request> + + <request name="set_cursor_rectangle"> + <arg name="x" type="int"/> + <arg name="y" type="int"/> + <arg name="width" type="int"/> + <arg name="height" type="int"/> + </request> + + <request name="set_preferred_language"> + <description summary="sets preferred language"> + Sets a specific language. This allows for example a virtual keyboard to + show a language specific layout. The "language" argument is an RFC-3066 + format language tag. + + It could be used for example in a word processor to indicate the + language of the currently edited document or in an instant message + application which tracks languages of contacts. + </description> + <arg name="language" type="string"/> + </request> + + <request name="commit_state"> + <arg name="serial" type="uint" summary="used to identify the known state"/> + </request> + + <request name="invoke_action"> + <arg name="button" type="uint"/> + <arg name="index" type="uint"/> + </request> + + <event name="enter"> + <description summary="enter event"> + Notify the text_input object when it received focus. Typically in + response to an activate request. + </description> + <arg name="surface" type="object" interface="wl_surface"/> + </event> + + <event name="leave"> + <description summary="leave event"> + Notify the text_input object when it lost focus. Either in response + to a deactivate request or when the assigned surface lost focus or was + destroyed. + </description> + </event> + + <event name="modifiers_map"> + <description summary="modifiers map"> + Transfer an array of 0-terminated modifier names. The position in + the array is the index of the modifier as used in the modifiers + bitmask in the keysym event. + </description> + <arg name="map" type="array"/> + </event> + + <event name="input_panel_state"> + <description summary="state of the input panel"> + Notify when the visibility state of the input panel changed. + </description> + <arg name="state" type="uint"/> + </event> + + <event name="preedit_string"> + <description summary="pre-edit"> + Notify when a new composing text (pre-edit) should be set around the + current cursor position. Any previously set composing text should + be removed. + + The commit text can be used to replace the preedit text on reset + (for example on unfocus). + + The text input should also handle all preedit_style and preedit_cursor + events occurring directly before preedit_string. + </description> + <arg name="serial" type="uint" summary="serial of the latest known text input state"/> + <arg name="text" type="string"/> + <arg name="commit" type="string"/> + </event> + + <enum name="preedit_style"> + <entry name="default" value="0" summary="default style for composing text"/> + <entry name="none" value="1" summary="style should be the same as in non-composing text"/> + <entry name="active" value="2"/> + <entry name="inactive" value="3"/> + <entry name="highlight" value="4"/> + <entry name="underline" value="5"/> + <entry name="selection" value="6"/> + <entry name="incorrect" value="7"/> + </enum> + + <event name="preedit_styling"> + <description summary="pre-edit styling"> + Sets styling information on composing text. The style is applied for + length bytes from index relative to the beginning of the composing + text (as byte offset). Multiple styles can + be applied to a composing text by sending multiple preedit_styling + events. + + This event is handled as part of a following preedit_string event. + </description> + <arg name="index" type="uint"/> + <arg name="length" type="uint"/> + <arg name="style" type="uint" enum="preedit_style" /> + </event> + + <event name="preedit_cursor"> + <description summary="pre-edit cursor"> + Sets the cursor position inside the composing text (as byte + offset) relative to the start of the composing text. When index is a + negative number no cursor is shown. + + This event is handled as part of a following preedit_string event. + </description> + <arg name="index" type="int"/> + </event> + + <event name="commit_string"> + <description summary="commit"> + Notify when text should be inserted into the editor widget. The text to + commit could be either just a single character after a key press or the + result of some composing (pre-edit). It could also be an empty text + when some text should be removed (see delete_surrounding_text) or when + the input cursor should be moved (see cursor_position). + + Any previously set composing text should be removed. + </description> + <arg name="serial" type="uint" summary="serial of the latest known text input state"/> + <arg name="text" type="string"/> + </event> + + <event name="cursor_position"> + <description summary="set cursor to new position"> + Notify when the cursor or anchor position should be modified. + + This event should be handled as part of a following commit_string + event. + </description> + <arg name="index" type="int"/> + <arg name="anchor" type="int"/> + </event> + + <event name="delete_surrounding_text"> + <description summary="delete surrounding text"> + Notify when the text around the current cursor position should be + deleted. + + Index is relative to the current cursor (in bytes). + Length is the length of deleted text (in bytes). + + This event should be handled as part of a following commit_string + event. + </description> + <arg name="index" type="int"/> + <arg name="length" type="uint"/> + </event> + + <event name="keysym"> + <description summary="keysym"> + Notify when a key event was sent. Key events should not be used + for normal text input operations, which should be done with + commit_string, delete_surrounding_text, etc. The key event follows + the wl_keyboard key event convention. Sym is an XKB keysym, state a + wl_keyboard key_state. Modifiers are a mask for effective modifiers + (where the modifier indices are set by the modifiers_map event) + </description> + <arg name="serial" type="uint" summary="serial of the latest known text input state"/> + <arg name="time" type="uint"/> + <arg name="sym" type="uint"/> + <arg name="state" type="uint"/> + <arg name="modifiers" type="uint"/> + </event> + + <event name="language"> + <description summary="language"> + Sets the language of the input text. The "language" argument is an + RFC-3066 format language tag. + </description> + <arg name="serial" type="uint" summary="serial of the latest known text input state"/> + <arg name="language" type="string"/> + </event> + + <enum name="text_direction"> + <entry name="auto" value="0" summary="automatic text direction based on text and language"/> + <entry name="ltr" value="1" summary="left-to-right"/> + <entry name="rtl" value="2" summary="right-to-left"/> + </enum> + + <event name="text_direction"> + <description summary="text direction"> + Sets the text direction of input text. + + It is mainly needed for showing an input cursor on the correct side of + the editor when there is no input done yet and making sure neutral + direction text is laid out properly. + </description> + <arg name="serial" type="uint" summary="serial of the latest known text input state"/> + <arg name="direction" type="uint" enum="text_direction" /> + </event> + </interface> + + <interface name="zwp_text_input_manager_v1" version="1"> + <description summary="text input manager"> + A factory for text_input objects. This object is a global singleton. + </description> + + <request name="create_text_input"> + <description summary="create text input"> + Creates a new text_input object. + </description> + <arg name="id" type="new_id" interface="zwp_text_input_v1"/> + </request> + </interface> + +</protocol> diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt index 7bcb65c8..1041f31e 100644 --- a/src/client/CMakeLists.txt +++ b/src/client/CMakeLists.txt @@ -29,6 +29,7 @@ qt_internal_add_module(WaylandClient qwaylanddisplay.cpp qwaylanddisplay_p.h qwaylandextendedsurface.cpp qwaylandextendedsurface_p.h qwaylandinputcontext.cpp qwaylandinputcontext_p.h + qwaylandtextinputv1.cpp qwaylandtextinputv1_p.h qwaylandtextinputv2.cpp qwaylandtextinputv2_p.h qwaylandtextinputinterface.cpp qwaylandtextinputinterface_p.h qwaylandinputdevice.cpp qwaylandinputdevice_p.h @@ -75,6 +76,7 @@ qt6_generate_wayland_protocol_client_sources(WaylandClient FILES ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/pointer-gestures-unstable-v1.xml ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/tablet-unstable-v2.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/text-input-unstable-v1.xml ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/text-input-unstable-v2.xml ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/text-input-unstable-v4-wip.xml ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/wayland.xml diff --git a/src/client/qwaylanddisplay.cpp b/src/client/qwaylanddisplay.cpp index 4e0a90b7..5937172f 100644 --- a/src/client/qwaylanddisplay.cpp +++ b/src/client/qwaylanddisplay.cpp @@ -60,6 +60,7 @@ #include <wayland-cursor.h> #endif #include "qwaylandhardwareintegration_p.h" +#include "qwaylandtextinputv1_p.h" #include "qwaylandtextinputv2_p.h" #if QT_WAYLAND_TEXT_INPUT_V4_WIP #include "qwaylandtextinputv4_p.h" @@ -80,6 +81,7 @@ #endif #include "qwaylandqtkey_p.h" +#include <QtWaylandClient/private/qwayland-text-input-unstable-v1.h> #include <QtWaylandClient/private/qwayland-text-input-unstable-v2.h> #include <QtWaylandClient/private/qwayland-text-input-unstable-v4-wip.h> #include <QtWaylandClient/private/qwayland-wp-primary-selection-unstable-v1.h> @@ -453,9 +455,11 @@ void QWaylandDisplay::checkTextInputProtocol() { QStringList tips, timps; // for text input protocols and text input manager protocols tips << QLatin1String(QtWayland::qt_text_input_method_v1::interface()->name) - << QLatin1String(QtWayland::zwp_text_input_v2::interface()->name); + << QLatin1String(QtWayland::zwp_text_input_v2::interface()->name) + << QLatin1String(QtWayland::zwp_text_input_v1::interface()->name); timps << QLatin1String(QtWayland::qt_text_input_method_manager_v1::interface()->name) - << QLatin1String(QtWayland::zwp_text_input_manager_v2::interface()->name); + << QLatin1String(QtWayland::zwp_text_input_manager_v2::interface()->name) + << QLatin1String(QtWayland::zwp_text_input_manager_v1::interface()->name); #if QT_WAYLAND_TEXT_INPUT_V4_WIP tips << QLatin1String(QtWayland::zwp_text_input_v4::interface()->name); timps << QLatin1String(QtWayland::zwp_text_input_manager_v4::interface()->name); @@ -540,6 +544,7 @@ void QWaylandDisplay::registry_global(uint32_t id, const QString &interface, uin && (mTextInputManagerList.contains(interface) && mTextInputManagerList.indexOf(interface) < mTextInputManagerIndex)) { qCDebug(lcQpaWayland) << "text input: register qt_text_input_method_manager_v1"; if (mTextInputManagerIndex < INT_MAX) { + mTextInputManagerv1.reset(); mTextInputManagerv2.reset(); #if QT_WAYLAND_TEXT_INPUT_V4_WIP mTextInputManagerv4.reset(); @@ -553,11 +558,34 @@ void QWaylandDisplay::registry_global(uint32_t id, const QString &interface, uin inputDevice->setTextInputMethod(new QWaylandTextInputMethod(this, mTextInputMethodManager->get_text_input_method(inputDevice->wl_seat()))); mWaylandIntegration->reconfigureInputContext(); mTextInputManagerIndex = mTextInputManagerList.indexOf(interface); + } else if (interface == QLatin1String(QtWayland::zwp_text_input_manager_v1::interface()->name) + && (mTextInputManagerList.contains(interface) && mTextInputManagerList.indexOf(interface) < mTextInputManagerIndex)) { + qCDebug(lcQpaWayland) << "text input: register zwp_text_input_v1"; + if (mTextInputManagerIndex < INT_MAX) { + mTextInputMethodManager.reset(); + mTextInputManagerv2.reset(); +#if QT_WAYLAND_TEXT_INPUT_V4_WIP + mTextInputManagerv4.reset(); +#endif // QT_WAYLAND_TEXT_INPUT_V4_WIP + for (QWaylandInputDevice *inputDevice : qAsConst(mInputDevices)) + inputDevice->setTextInputMethod(nullptr); + } + + mTextInputManagerv1.reset(new QtWayland::zwp_text_input_manager_v1(registry, id, 1)); + for (QWaylandInputDevice *inputDevice : qAsConst(mInputDevices)) { + auto textInput = new QWaylandTextInputv1(this, mTextInputManagerv1->create_text_input()); + textInput->setSeat(inputDevice->wl_seat()); + inputDevice->setTextInput(textInput); + } + + mWaylandIntegration->reconfigureInputContext(); + mTextInputManagerIndex = mTextInputManagerList.indexOf(interface); } else if (interface == QLatin1String(QtWayland::zwp_text_input_manager_v2::interface()->name) && (mTextInputManagerList.contains(interface) && mTextInputManagerList.indexOf(interface) < mTextInputManagerIndex)) { qCDebug(lcQpaWayland) << "text input: register zwp_text_input_v2"; if (mTextInputManagerIndex < INT_MAX) { mTextInputMethodManager.reset(); + mTextInputManagerv1.reset(); #if QT_WAYLAND_TEXT_INPUT_V4_WIP mTextInputManagerv4.reset(); #endif // QT_WAYLAND_TEXT_INPUT_V4_WIP @@ -633,6 +661,12 @@ void QWaylandDisplay::registry_global_remove(uint32_t id) } } } + if (global.interface == QLatin1String(QtWayland::zwp_text_input_manager_v1::interface()->name)) { + mTextInputManagerv1.reset(); + for (QWaylandInputDevice *inputDevice : qAsConst(mInputDevices)) + inputDevice->setTextInput(nullptr); + mWaylandIntegration->reconfigureInputContext(); + } if (global.interface == QLatin1String(QtWayland::zwp_text_input_manager_v2::interface()->name)) { mTextInputManagerv2.reset(); for (QWaylandInputDevice *inputDevice : qAsConst(mInputDevices)) diff --git a/src/client/qwaylanddisplay_p.h b/src/client/qwaylanddisplay_p.h index 8d8000d2..cf123cba 100644 --- a/src/client/qwaylanddisplay_p.h +++ b/src/client/qwaylanddisplay_p.h @@ -82,6 +82,7 @@ class QPlatformPlaceholderScreen; namespace QtWayland { class qt_surface_extension; + class zwp_text_input_manager_v1; class zwp_text_input_manager_v2; class zwp_text_input_manager_v4; class qt_text_input_method_manager_v1; @@ -176,6 +177,7 @@ public: QWaylandPointerGestures *pointerGestures() const { return mPointerGestures.data(); } QWaylandTouchExtension *touchExtension() const { return mTouchExtension.data(); } QtWayland::qt_text_input_method_manager_v1 *textInputMethodManager() const { return mTextInputMethodManager.data(); } + QtWayland::zwp_text_input_manager_v1 *textInputManagerv1() const { return mTextInputManagerv1.data(); } QtWayland::zwp_text_input_manager_v2 *textInputManagerv2() const { return mTextInputManagerv2.data(); } QtWayland::zwp_text_input_manager_v4 *textInputManagerv4() const { return mTextInputManagerv4.data(); } QWaylandHardwareIntegration *hardwareIntegration() const { return mHardwareIntegration.data(); } @@ -289,6 +291,7 @@ private: QScopedPointer<QWaylandPrimarySelectionDeviceManagerV1> mPrimarySelectionManager; #endif QScopedPointer<QtWayland::qt_text_input_method_manager_v1> mTextInputMethodManager; + QScopedPointer<QtWayland::zwp_text_input_manager_v1> mTextInputManagerv1; QScopedPointer<QtWayland::zwp_text_input_manager_v2> mTextInputManagerv2; QScopedPointer<QtWayland::zwp_text_input_manager_v4> mTextInputManagerv4; QScopedPointer<QWaylandHardwareIntegration> mHardwareIntegration; diff --git a/src/client/qwaylandinputcontext.cpp b/src/client/qwaylandinputcontext.cpp index c5948527..32433cdc 100644 --- a/src/client/qwaylandinputcontext.cpp +++ b/src/client/qwaylandinputcontext.cpp @@ -71,9 +71,9 @@ QWaylandInputContext::~QWaylandInputContext() bool QWaylandInputContext::isValid() const { #if QT_WAYLAND_TEXT_INPUT_V4_WIP - return mDisplay->textInputManagerv2() != nullptr || mDisplay->textInputManagerv4() != nullptr; + return mDisplay->textInputManagerv2() != nullptr || mDisplay->textInputManagerv1() != nullptr || mDisplay->textInputManagerv4() != nullptr; #else // QT_WAYLAND_TEXT_INPUT_V4_WIP - return mDisplay->textInputManagerv2() != nullptr; + return mDisplay->textInputManagerv2() != nullptr || mDisplay->textInputManagerv1() != nullptr; #endif // QT_WAYLAND_TEXT_INPUT_V4_WIP } diff --git a/src/client/qwaylandinputdevice.cpp b/src/client/qwaylandinputdevice.cpp index 4c32a534..34ca328a 100644 --- a/src/client/qwaylandinputdevice.cpp +++ b/src/client/qwaylandinputdevice.cpp @@ -59,6 +59,7 @@ #include "qwaylandcursor_p.h" #include "qwaylanddisplay_p.h" #include "qwaylandshmbackingstore_p.h" +#include "qwaylandtextinputv1_p.h" #include "qwaylandtextinputv2_p.h" #if QT_WAYLAND_TEXT_INPUT_V4_WIP #include "qwaylandtextinputv4_p.h" @@ -426,6 +427,12 @@ QWaylandInputDevice::QWaylandInputDevice(QWaylandDisplay *display, int version, setPrimarySelectionDevice(psm->createDevice(this)); #endif + if (mQDisplay->textInputManagerv1()) { + auto textInput = new QWaylandTextInputv1(mQDisplay, mQDisplay->textInputManagerv1()->create_text_input()); + textInput->setSeat(wl_seat()); + mTextInput.reset(textInput); + } + if (mQDisplay->textInputManagerv2()) mTextInput.reset(new QWaylandTextInputv2(mQDisplay, mQDisplay->textInputManagerv2()->get_text_input(wl_seat()))); diff --git a/src/client/qwaylandintegration.cpp b/src/client/qwaylandintegration.cpp index 274ae9a9..721af834 100644 --- a/src/client/qwaylandintegration.cpp +++ b/src/client/qwaylandintegration.cpp @@ -514,9 +514,9 @@ void QWaylandIntegration::reconfigureInputContext() if (mDisplay->textInputMethodManager() != nullptr) mInputContext.reset(new QWaylandInputMethodContext(mDisplay.data())); #if QT_WAYLAND_TEXT_INPUT_V4_WIP - else if (mDisplay->textInputManagerv2() != nullptr || mDisplay->textInputManagerv4() != nullptr) + else if (mDisplay->textInputManagerv1() != nullptr || mDisplay->textInputManagerv2() != nullptr || mDisplay->textInputManagerv4() != nullptr) #else // QT_WAYLAND_TEXT_INPUT_V4_WIP - else if (mDisplay->textInputManagerv2() != nullptr) + else if (mDisplay->textInputManagerv1() != nullptr || mDisplay->textInputManagerv2() != nullptr) #endif // QT_WAYLAND_TEXT_INPUT_V4_WIP mInputContext.reset(new QWaylandInputContext(mDisplay.data())); } else { diff --git a/src/client/qwaylandtextinputv1.cpp b/src/client/qwaylandtextinputv1.cpp new file mode 100644 index 00000000..5e204a7f --- /dev/null +++ b/src/client/qwaylandtextinputv1.cpp @@ -0,0 +1,395 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWaylandClient module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include <qpa/qplatforminputcontext.h> + +#include "qwaylandtextinputv1_p.h" + +#include "qwaylandwindow_p.h" +#include "qwaylandinputmethodeventbuilder_p.h" + +#include <QtCore/qloggingcategory.h> +#include <QtGui/QGuiApplication> +#include <QtGui/private/qguiapplication_p.h> +#include <QtGui/qpa/qplatformintegration.h> +#include <QtGui/qevent.h> +#include <QtGui/qwindow.h> +#include <QTextCharFormat> +#include <QList> +#include <QRectF> +#include <QLocale> + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(qLcQpaInputMethods) + +namespace QtWaylandClient { + +namespace { + +const Qt::InputMethodQueries supportedQueries = Qt::ImEnabled | + Qt::ImSurroundingText | + Qt::ImCursorPosition | + Qt::ImAnchorPosition | + Qt::ImHints | + Qt::ImCursorRectangle | + Qt::ImPreferredLanguage; +} + +QWaylandTextInputv1::QWaylandTextInputv1(QWaylandDisplay *display, struct ::zwp_text_input_v1 *text_input) + : QtWayland::zwp_text_input_v1(text_input) + , m_display(display) +{ +} + +QWaylandTextInputv1::~QWaylandTextInputv1() +{ + if (m_resetCallback) + wl_callback_destroy(m_resetCallback); +} + +void QWaylandTextInputv1::reset() +{ + m_builder.reset(); + m_preeditCommit = QString(); + updateState(Qt::ImQueryAll, QWaylandTextInputInterface::update_state_reset); +} + +void QWaylandTextInputv1::commit() +{ + if (QObject *o = QGuiApplication::focusObject()) { + QInputMethodEvent event; + event.setCommitString(m_preeditCommit); + QCoreApplication::sendEvent(o, &event); + } + + reset(); +} + +const wl_callback_listener QWaylandTextInputv1::callbackListener = { + QWaylandTextInputv1::resetCallback +}; + +void QWaylandTextInputv1::resetCallback(void *data, wl_callback *, uint32_t) +{ + QWaylandTextInputv1 *self = static_cast<QWaylandTextInputv1*>(data); + + if (self->m_resetCallback) { + wl_callback_destroy(self->m_resetCallback); + self->m_resetCallback = nullptr; + } +} + +void QWaylandTextInputv1::updateState(Qt::InputMethodQueries queries, uint32_t flags) +{ + if (!QGuiApplication::focusObject()) + return; + + if (!QGuiApplication::focusWindow() || !QGuiApplication::focusWindow()->handle()) + return; + + auto *window = static_cast<QWaylandWindow *>(QGuiApplication::focusWindow()->handle()); + auto *surface = window->wlSurface(); + if (!surface || (surface != m_surface)) + return; + + queries &= supportedQueries; + + // Surrounding text, cursor and anchor positions are transferred together + if ((queries & Qt::ImSurroundingText) || (queries & Qt::ImCursorPosition) || (queries & Qt::ImAnchorPosition)) + queries |= Qt::ImSurroundingText | Qt::ImCursorPosition | Qt::ImAnchorPosition; + + QInputMethodQueryEvent event(queries); + QCoreApplication::sendEvent(QGuiApplication::focusObject(), &event); + + if ((queries & Qt::ImSurroundingText) || (queries & Qt::ImCursorPosition) || (queries & Qt::ImAnchorPosition)) { + QString text = event.value(Qt::ImSurroundingText).toString(); + int cursor = event.value(Qt::ImCursorPosition).toInt(); + int anchor = event.value(Qt::ImAnchorPosition).toInt(); + + // Make sure text is not too big + if (text.toUtf8().size() > 2048) { + int c = qAbs(cursor - anchor) <= 512 ? qMin(cursor, anchor) + qAbs(cursor - anchor) / 2: cursor; + + const int offset = c - qBound(0, c, 512 - qMin(text.size() - c, 256)); + text = text.mid(offset + c - 256, 512); + cursor -= offset; + anchor -= offset; + } + + set_surrounding_text(text, QWaylandInputMethodEventBuilder::indexToWayland(text, cursor), QWaylandInputMethodEventBuilder::indexToWayland(text, anchor)); + } + + if (queries & Qt::ImHints) { + QWaylandInputMethodContentType contentType = QWaylandInputMethodContentType::convert(static_cast<Qt::InputMethodHints>(event.value(Qt::ImHints).toInt())); + set_content_type(contentType.hint, contentType.purpose); + } + + if (queries & Qt::ImCursorRectangle) { + const QRect &cRect = event.value(Qt::ImCursorRectangle).toRect(); + const QRect &windowRect = QGuiApplication::inputMethod()->inputItemTransform().mapRect(cRect); + const QMargins margins = window->frameMargins(); + const QRect &surfaceRect = windowRect.translated(margins.left(), margins.top()); + set_cursor_rectangle(surfaceRect.x(), surfaceRect.y(), surfaceRect.width(), surfaceRect.height()); + } + + if (queries & Qt::ImPreferredLanguage) { + const QString &language = event.value(Qt::ImPreferredLanguage).toString(); + set_preferred_language(language); + } + + if (flags == QWaylandTextInputInterface::update_state_reset) + QtWayland::zwp_text_input_v1::reset(); + else + commit_state(m_serial); +} + +void QWaylandTextInputv1::setCursorInsidePreedit(int) +{ + // Not supported yet +} + +bool QWaylandTextInputv1::isInputPanelVisible() const +{ + return m_inputPanelVisible; +} + +QRectF QWaylandTextInputv1::keyboardRect() const +{ + return m_keyboardRectangle; +} + +QLocale QWaylandTextInputv1::locale() const +{ + return m_locale; +} + +Qt::LayoutDirection QWaylandTextInputv1::inputDirection() const +{ + return m_inputDirection; +} + +void QWaylandTextInputv1::zwp_text_input_v1_enter(::wl_surface *surface) +{ + m_surface = surface; + + updateState(Qt::ImQueryAll, QWaylandTextInputInterface::update_state_reset); +} + +void QWaylandTextInputv1::zwp_text_input_v1_leave() +{ + m_surface = nullptr; +} + +void QWaylandTextInputv1::zwp_text_input_v1_modifiers_map(wl_array *map) +{ + const QList<QByteArray> modifiersMap = QByteArray::fromRawData(static_cast<const char*>(map->data), map->size).split('\0'); + + m_modifiersMap.clear(); + + for (const QByteArray &modifier : modifiersMap) { + if (modifier == "Shift") + m_modifiersMap.append(Qt::ShiftModifier); + else if (modifier == "Control") + m_modifiersMap.append(Qt::ControlModifier); + else if (modifier == "Alt") + m_modifiersMap.append(Qt::AltModifier); + else if (modifier == "Mod1") + m_modifiersMap.append(Qt::AltModifier); + else if (modifier == "Mod4") + m_modifiersMap.append(Qt::MetaModifier); + else + m_modifiersMap.append(Qt::NoModifier); + } +} + +void QWaylandTextInputv1::zwp_text_input_v1_input_panel_state(uint32_t visible) +{ + const bool inputPanelVisible = (visible == 1); + if (m_inputPanelVisible != inputPanelVisible) { + m_inputPanelVisible = inputPanelVisible; + QGuiApplicationPrivate::platformIntegration()->inputContext()->emitInputPanelVisibleChanged(); + } +} + +void QWaylandTextInputv1::zwp_text_input_v1_preedit_string(uint32_t serial, const QString &text, const QString &commit) +{ + m_serial = serial; + + if (m_resetCallback) { + qCDebug(qLcQpaInputMethods()) << "discard preedit_string: reset not confirmed"; + m_builder.reset(); + return; + } + + if (!QGuiApplication::focusObject()) + return; + + QInputMethodEvent *event = m_builder.buildPreedit(text); + + m_builder.reset(); + m_preeditCommit = commit; + + QCoreApplication::sendEvent(QGuiApplication::focusObject(), event); + delete event; +} + +void QWaylandTextInputv1::zwp_text_input_v1_preedit_styling(uint32_t index, uint32_t length, uint32_t style) +{ + m_builder.addPreeditStyling(index, length, style); +} + +void QWaylandTextInputv1::zwp_text_input_v1_preedit_cursor(int32_t index) +{ + m_builder.setPreeditCursor(index); +} + +void QWaylandTextInputv1::zwp_text_input_v1_commit_string(uint32_t serial, const QString &text) +{ + m_serial = serial; + + if (m_resetCallback) { + qCDebug(qLcQpaInputMethods()) << "discard commit_string: reset not confirmed"; + m_builder.reset(); + return; + } + + if (!QGuiApplication::focusObject()) + return; + + // When committing the text, the preeditString needs to be reset, to prevent it to be + // send again in the commit() function + m_preeditCommit.clear(); + + QInputMethodEvent *event = m_builder.buildCommit(text); + + m_builder.reset(); + + QCoreApplication::sendEvent(QGuiApplication::focusObject(), event); + delete event; +} + +void QWaylandTextInputv1::zwp_text_input_v1_cursor_position(int32_t index, int32_t anchor) +{ + m_builder.setCursorPosition(index, anchor); +} + +void QWaylandTextInputv1::zwp_text_input_v1_delete_surrounding_text(int32_t before_length, uint32_t after_length) +{ + //before_length is negative, but the builder expects it to be positive + m_builder.setDeleteSurroundingText(-before_length, after_length); +} + +void QWaylandTextInputv1::zwp_text_input_v1_keysym(uint32_t serial, uint32_t time, uint32_t sym, uint32_t state, uint32_t modifiers) +{ + m_serial = serial; + +#if QT_CONFIG(xkbcommon) + if (m_resetCallback) { + qCDebug(qLcQpaInputMethods()) << "discard keysym: reset not confirmed"; + return; + } + + if (!QGuiApplication::focusWindow()) + return; + + Qt::KeyboardModifiers qtModifiers = modifiersToQtModifiers(modifiers); + + QEvent::Type type = state == WL_KEYBOARD_KEY_STATE_PRESSED ? QEvent::KeyPress : QEvent::KeyRelease; + QString text = QXkbCommon::lookupStringNoKeysymTransformations(sym); + int qtkey = QXkbCommon::keysymToQtKey(sym, qtModifiers); + + QWindowSystemInterface::handleKeyEvent(QGuiApplication::focusWindow(), + time, type, qtkey, qtModifiers, text); +#else + Q_UNUSED(time); + Q_UNUSED(sym); + Q_UNUSED(state); + Q_UNUSED(modifiers); +#endif +} + +void QWaylandTextInputv1::zwp_text_input_v1_language(uint32_t serial, const QString &language) +{ + m_serial = serial; + + if (m_resetCallback) { + qCDebug(qLcQpaInputMethods()) << "discard language: reset not confirmed"; + return; + } + + const QLocale locale(language); + if (m_locale != locale) { + m_locale = locale; + QGuiApplicationPrivate::platformIntegration()->inputContext()->emitLocaleChanged(); + } +} + +void QWaylandTextInputv1::zwp_text_input_v1_text_direction(uint32_t serial, uint32_t direction) +{ + m_serial = serial; + + if (m_resetCallback) { + qCDebug(qLcQpaInputMethods()) << "discard text_direction: reset not confirmed"; + return; + } + + const Qt::LayoutDirection inputDirection = (direction == text_direction_auto) ? Qt::LayoutDirectionAuto : + (direction == text_direction_ltr) ? Qt::LeftToRight : + (direction == text_direction_rtl) ? Qt::RightToLeft : Qt::LayoutDirectionAuto; + if (m_inputDirection != inputDirection) { + m_inputDirection = inputDirection; + QGuiApplicationPrivate::platformIntegration()->inputContext()->emitInputDirectionChanged(m_inputDirection); + } +} + +Qt::KeyboardModifiers QWaylandTextInputv1::modifiersToQtModifiers(uint32_t modifiers) +{ + Qt::KeyboardModifiers ret = Qt::NoModifier; + for (int i = 0; i < m_modifiersMap.size(); ++i) { + if (modifiers & (1 << i)) { + ret |= m_modifiersMap[i]; + } + } + return ret; +} + +} + +QT_END_NAMESPACE + diff --git a/src/client/qwaylandtextinputv1_p.h b/src/client/qwaylandtextinputv1_p.h new file mode 100644 index 00000000..238db0b8 --- /dev/null +++ b/src/client/qwaylandtextinputv1_p.h @@ -0,0 +1,149 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWaylandClient module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef QWAYLANDTEXTINPUTV1_H +#define QWAYLANDTEXTINPUTV1_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qwaylandtextinputinterface_p.h" +#include <QtWaylandClient/private/qwayland-text-input-unstable-v1.h> +#include <qwaylandinputmethodeventbuilder_p.h> + +struct wl_callback; +struct wl_callback_listener; + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandDisplay; + +class QWaylandTextInputv1 : public QtWayland::zwp_text_input_v1, public QWaylandTextInputInterface +{ +public: + QWaylandTextInputv1(QWaylandDisplay *display, struct ::zwp_text_input_v1 *text_input); + ~QWaylandTextInputv1() override; + + void setSeat(struct ::wl_seat *seat) { m_seat = seat; } + + void reset() override; + void commit() override; + void updateState(Qt::InputMethodQueries queries, uint32_t flags) override; + + void setCursorInsidePreedit(int cursor) override; + + bool isInputPanelVisible() const override; + QRectF keyboardRect() const override; + + QLocale locale() const override; + Qt::LayoutDirection inputDirection() const override; + + void showInputPanel() override + { + show_input_panel(); + } + void hideInputPanel() override + { + hide_input_panel(); + } + void enableSurface(::wl_surface *surface) override + { + activate(m_seat, surface); + } + void disableSurface(::wl_surface *surface) override + { + Q_UNUSED(surface); + deactivate(m_seat); + } + +protected: + void zwp_text_input_v1_enter(struct ::wl_surface *surface) override; + void zwp_text_input_v1_leave() override; + void zwp_text_input_v1_modifiers_map(wl_array *map) override; + void zwp_text_input_v1_input_panel_state(uint32_t state) override; + void zwp_text_input_v1_preedit_string(uint32_t serial, const QString &text, const QString &commit) override; + void zwp_text_input_v1_preedit_styling(uint32_t index, uint32_t length, uint32_t style) override; + void zwp_text_input_v1_preedit_cursor(int32_t index) override; + void zwp_text_input_v1_commit_string(uint32_t serial, const QString &text) override; + void zwp_text_input_v1_cursor_position(int32_t index, int32_t anchor) override; + void zwp_text_input_v1_delete_surrounding_text(int32_t before_length, uint32_t after_length) override; + void zwp_text_input_v1_keysym(uint32_t serial, uint32_t time, uint32_t sym, uint32_t state, uint32_t modifiers) override; + void zwp_text_input_v1_language(uint32_t serial, const QString &language) override; + void zwp_text_input_v1_text_direction(uint32_t serial, uint32_t direction) override; + +private: + Qt::KeyboardModifiers modifiersToQtModifiers(uint32_t modifiers); + + QWaylandDisplay *m_display = nullptr; + QWaylandInputMethodEventBuilder m_builder; + + QList<Qt::KeyboardModifier> m_modifiersMap; + + uint32_t m_serial = 0; + struct ::wl_surface *m_surface = nullptr; + struct ::wl_seat *m_seat = nullptr; + + QString m_preeditCommit; + + bool m_inputPanelVisible = false; + QRectF m_keyboardRectangle; + QLocale m_locale; + Qt::LayoutDirection m_inputDirection = Qt::LayoutDirectionAuto; + + struct ::wl_callback *m_resetCallback = nullptr; + static const wl_callback_listener callbackListener; + static void resetCallback(void *data, struct wl_callback *wl_callback, uint32_t time); +}; + +} + +QT_END_NAMESPACE +#endif // QWAYLANDTEXTINPUTV1_H + diff --git a/sync.profile b/sync.profile index 0ce33622..c78827c2 100644 --- a/sync.profile +++ b/sync.profile @@ -30,6 +30,7 @@ "^qwayland-server-buffer-extension.h", "^qwayland-surface-extension.h", "^qwayland-tablet-unstable-v2.h", + "^qwayland-text-input-unstable-v1.h", "^qwayland-text-input-unstable-v2.h", "^qwayland-text-input-unstable-v4-wip.h", "^qwayland-qt-text-input-method-unstable-v1.h", @@ -44,6 +45,7 @@ "^wayland-server-buffer-extension-client-protocol.h", "^wayland-surface-extension-client-protocol.h", "^wayland-tablet-unstable-v2-client-protocol.h", + "^wayland-text-input-unstable-v1-client-protocol.h", "^wayland-text-input-unstable-v2-client-protocol.h", "^wayland-text-input-unstable-v4-wip-client-protocol.h", "^wayland-qt-text-input-method-unstable-v1-client-protocol.h", |