diff options
author | Inho Lee <inho.lee@qt.io> | 2021-06-19 06:27:22 +0200 |
---|---|---|
committer | Inho Lee <inho.lee@qt.io> | 2021-11-19 10:33:31 +0100 |
commit | 0cec50bece2dcd69127f19c87a3062e4b13f3723 (patch) | |
tree | 8ec11248518b1edfede9538a07ee5e0b0aa5624e | |
parent | 6f195a592b26ad8416a6f02d6bd7258ab3fadf65 (diff) | |
download | qtwayland-0cec50bece2dcd69127f19c87a3062e4b13f3723.tar.gz |
Support text-input-unstable-v4-wip
This feature can be enabled by -feature-wayland-text-input-v4-wip.
It is disabled by default.
TextInputManagerV4 is available in a compositor.
zwp_text_input_v4 is available for QT_WAYLAND_TEXT_INPUT_PROTOCOL in
a client
It supports Hangul(Korean) with a qtvirtualkeyboard patchset
(refs/changes/02/357902/3)
It includes some workarounds for ibus because each ibus module has its own
policy for focus-in/focus-out.
enter/leave will synchronize with enable/disable and they will happen
whenever focus-in/focus-out happen.
Cursor/anchor positions are byte offsets.
Surrounding text will be trimmed when it is over 4000 byte.
For debugging,
uses "qt.waylandcompositor.textinput" in a compositor side
uses "qt.qpa.wayland.textinput" in a client side
Tested on qtvirtualkeyboard and ibus
TODO :
* QTBUG-97248 - event:preedit_commit_mode is not implemented yet. Current
preedit_commit_mode is 'commit'.
* request:set_text_change_cause is not implemented.
Task-number: QTBUG-94327
Change-Id: I72644893f40f30c4b03cd6a7d05483d12bde1070
Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
32 files changed, 2398 insertions, 55 deletions
diff --git a/qt_cmdline.cmake b/qt_cmdline.cmake new file mode 100644 index 00000000..7151714f --- /dev/null +++ b/qt_cmdline.cmake @@ -0,0 +1 @@ +qt_commandline_subconfig(src) diff --git a/src/3rdparty/protocol/qt_attribution.json b/src/3rdparty/protocol/qt_attribution.json index 9b565a5a..745fafe5 100644 --- a/src/3rdparty/protocol/qt_attribution.json +++ b/src/3rdparty/protocol/qt_attribution.json @@ -119,6 +119,21 @@ }, { + "Id": "wayland-text-input-unstable-v4-wip", + "Name": "Wayland Text Input Protocol", + "QDocModule": "qtwaylandcompositor", + "QtUsage": "Used in the Qt Wayland Compositor, and the Qt Wayland platform plugin.", + "Files": "text-input-unstable-v4-wip.xml", + "Description": "Adds support for compositors to act as input methods and send text to applications.", + "Homepage": "https://wayland.freedesktop.org", + "Version": "unstable v4, WIP", + "LicenseId": "HPND", + "License": "HPND License", + "LicenseFile": "HPND_LICENSE.txt", + "Copyright": "Copyright © 2012, 2013 Intel Corporation\nCopyright © 2015, 2016 Jan Arne Petersen\nCopyright © 2017, 2018 Red Hat, Inc.\nCopyright © 2018 Purism SPC" + }, + + { "Id": "wayland-viewporter-protocol", "Name": "Wayland Viewporter Protocol", "QDocModule": "qtwaylandcompositor", diff --git a/src/3rdparty/protocol/text-input-unstable-v4-wip.xml b/src/3rdparty/protocol/text-input-unstable-v4-wip.xml new file mode 100644 index 00000000..1041e6f7 --- /dev/null +++ b/src/3rdparty/protocol/text-input-unstable-v4-wip.xml @@ -0,0 +1,467 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<protocol name="text_input_unstable_v4_wip"> + <copyright> + Copyright © 2012, 2013 Intel Corporation + Copyright © 2015, 2016 Jan Arne Petersen + Copyright © 2017, 2018 Red Hat, Inc. + Copyright © 2018 Purism SPC + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + </copyright> + + <description summary="Protocol for composing text"> + This protocol allows compositors to act as input methods and to send text + to applications. A text input object is used to manage state of what are + typically text entry fields in the application. + + This document adheres to the RFC 2119 when using words like "must", + "should", "may", etc. + + 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> + + <interface name="zwp_text_input_v4" version="1"> + <description summary="text input"> + The zwp_text_input_v4 interface represents text input and input methods + associated with a seat. It provides enter/leave events to follow the + text input focus for a seat. + + Requests are used to enable/disable the text-input object and set + state information like surrounding and selected text or the content type. + The information about the entered text is sent to the text-input object + via the preedit_string and commit_string events. + + Text is valid UTF-8 encoded, indices and lengths are in bytes. Indices + must not point to middle bytes inside a code point: they must either + point to the first byte of a code point or to the end of the buffer. + Lengths must be measured between two valid indices. + + Focus moving throughout surfaces will result in the emission of + zwp_text_input_v4.enter and zwp_text_input_v4.leave events. The focused + surface must commit zwp_text_input_v4.enable and + zwp_text_input_v4.disable requests as the keyboard focus moves across + editable and non-editable elements of the UI. Those two requests are not + expected to be paired with each other, the compositor must be able to + handle consecutive series of the same request. + + State is sent by the state requests (set_surrounding_text, + set_content_type and set_cursor_rectangle) and a commit request. After an + enter event or disable request all state information is invalidated and + needs to be resent by the client. + </description> + + <request name="destroy" type="destructor"> + <description summary="Destroy the wp_text_input"> + Destroy the wp_text_input object. Also disables all surfaces enabled + through this wp_text_input object. + </description> + </request> + + <request name="enable"> + <description summary="Request text input to be enabled"> + Requests text input on the surface previously obtained from the enter + event. + + This request must be issued every time the active text input changes + to a new one, including within the current surface. Use + zwp_text_input_v4.disable when there is no longer any input focus on + the current surface. + + This request resets all state associated with previous enable, disable, + set_surrounding_text, set_text_change_cause, set_content_type, and + set_cursor_rectangle requests, as well as the state associated with + preedit_string, commit_string, and delete_surrounding_text events. + + The set_surrounding_text, set_content_type and set_cursor_rectangle + requests must follow if the text input supports the necessary + functionality. + + State set with this request is double-buffered. It will get applied on + the next zwp_text_input_v4.commit request, and stay valid until the + next committed enable or disable request. + + The changes must be applied by the compositor after issuing a + zwp_text_input_v4.commit request. + </description> + </request> + + <request name="disable"> + <description summary="Disable text input on a surface"> + Explicitly disable text input on the current surface (typically when + there is no focus on any text entry inside the surface). + + State set with this request is double-buffered. It will get applied on + the next zwp_text_input_v4.commit request. + </description> + </request> + + <request name="set_surrounding_text"> + <description summary="sets the surrounding text"> + Sets the surrounding plain text around the input, excluding the preedit + text. + + The client should notify the compositor of any changes in any of the + values carried with this request, including changes caused by handling + incoming text-input events as well as changes caused by other + mechanisms like keyboard typing. + + If the client is unaware of the text around the cursor, it should not + issue this request, to signify lack of support to the compositor. + + Text is UTF-8 encoded, and should include the cursor position, the + complete selection and additional characters before and after them. + There is a maximum length of wayland messages, so text can not be + longer than 4000 bytes. + + Cursor is the byte offset of the cursor within text buffer. + + Anchor is the byte offset of the selection anchor within text buffer. + If there is no selected text, anchor is the same as cursor. + + If any preedit text is present, it is replaced with a cursor for the + purpose of this event. + + Values set with this request are double-buffered. They will get applied + on the next zwp_text_input_v4.commit request, and stay valid until the + next committed enable or disable request. + + The initial state for affected fields is empty, meaning that the text + input does not support sending surrounding text. If the empty values + get applied, subsequent attempts to change them may have no effect. + </description> + <arg name="text" type="string"/> + <arg name="cursor" type="int"/> + <arg name="anchor" type="int"/> + </request> + + <enum name="change_cause"> + <description summary="text change reason"> + Reason for the change of surrounding text or cursor posision. + </description> + <entry name="input_method" value="0" summary="input method caused the change"/> + <entry name="other" value="1" summary="something else than the input method caused the change"/> + </enum> + + <request name="set_text_change_cause"> + <description summary="indicates the cause of surrounding text change"> + Tells the compositor why the text surrounding the cursor changed. + + Whenever the client detects an external change in text, cursor, or + anchor posision, it must issue this request to the compositor. This + request is intended to give the input method a chance to update the + preedit text in an appropriate way, e.g. by removing it when the user + starts typing with a keyboard. + + cause describes the source of the change. + + The value set with this request is double-buffered. It must be applied + and reset to initial at the next zwp_text_input_v4.commit request. + + The initial value of cause is input_method. + </description> + <arg name="cause" type="uint" enum="change_cause"/> + </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 behavior"/> + <entry name="completion" value="0x1" summary="suggest word completions"/> + <entry name="spellcheck" 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 sensitive_data hint)"/> + <entry name="pin" value="9" summary="input is a numeric password (combine with sensitive_data hint)"/> + <entry name="date" value="10" summary="input a date"/> + <entry name="time" value="11" summary="input a time"/> + <entry name="datetime" value="12" summary="input a date and time"/> + <entry name="terminal" value="13" 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. + + Values set with this request are double-buffered. They will get applied + on the next zwp_text_input_v4.commit request. + Subsequent attempts to update them may have no effect. The values + remain valid until the next committed enable or disable request. + + The initial value for hint is none, and the initial value for purpose + is normal. + </description> + <arg name="hint" type="uint" enum="content_hint"/> + <arg name="purpose" type="uint" enum="content_purpose"/> + </request> + + <request name="set_cursor_rectangle"> + <description summary="set cursor position"> + Marks an area around the cursor as a x, y, width, height rectangle in + surface local coordinates. + + Allows the compositor to put a window with word suggestions near the + cursor, without obstructing the text being input. + + If the client is unaware of the position of edited text, it should not + issue this request, to signify lack of support to the compositor. + + Values set with this request are double-buffered. They will get applied + on the next zwp_text_input_v4.commit request, and stay valid until the + next committed enable or disable request. + + The initial values describing a cursor rectangle are empty. That means + the text input does not support describing the cursor area. If the + empty values get applied, subsequent attempts to change them may have + no effect. + </description> + <arg name="x" type="int"/> + <arg name="y" type="int"/> + <arg name="width" type="int"/> + <arg name="height" type="int"/> + </request> + + <request name="commit"> + <description summary="commit state"> + Atomically applies state changes recently sent to the compositor. + + The commit request establishes and updates the state of the client, and + must be issued after any changes to apply them. + + Text input state (enabled status, content purpose, content hint, + surrounding text and change cause, cursor rectangle) is conceptually + double-buffered within the context of a text input, i.e. between a + committed enable request and the following committed enable or disable + request. + + Protocol requests modify the pending state, as opposed to the current + state in use by the input method. A commit request atomically applies + all pending state, replacing the current state. After commit, the new + pending state is as documented for each related request. + + Requests are applied in the order of arrival. + + Neither current nor pending state are modified unless noted otherwise. + + The compositor must count the number of commit requests coming from + each zwp_text_input_v4 object and use the count as the serial in done + events. + </description> + </request> + + <event name="enter"> + <description summary="enter event"> + Notification that this seat's text-input focus is on a certain surface. + + When the seat has the keyboard capability the text-input focus follows + the keyboard focus. This event sets the current surface for the + text-input object. + </description> + <arg name="surface" type="object" interface="wl_surface"/> + </event> + + <event name="leave"> + <description summary="leave event"> + Notification that this seat's text-input focus is no longer on a + certain surface. The client should reset any preedit string previously + set. + + The leave notification clears the current surface. It is sent before + the enter notification for the new focus. + + When the seat has the keyboard capability the text-input focus follows + the keyboard focus. + </description> + <arg name="surface" type="object" interface="wl_surface"/> + </event> + + <event name="preedit_string"> + <description summary="pre-edit"> + Notify when a new composing text (pre-edit) should be set at the + current cursor position. Any previously set composing text must be + removed. Any previously existing selected text must be removed. + + The argument text contains the pre-edit string buffer. + + The parameters cursor_begin and cursor_end are counted in bytes + relative to the beginning of the submitted text buffer. Cursor should + be hidden when both are equal to -1. + + They could be represented by the client as a line if both values are + the same, or as a text highlight otherwise. + + Values set with this event are double-buffered. They must be applied + and reset to initial on the next zwp_text_input_v4.done event. + + The initial value of text is an empty string, and cursor_begin, + cursor_end and cursor_hidden are all 0. + </description> + <arg name="text" type="string" allow-null="true"/> + <arg name="cursor_begin" type="int"/> + <arg name="cursor_end" type="int"/> + </event> + + <event name="commit_string"> + <description summary="text 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). + + Values set with this event are double-buffered. They must be applied + and reset to initial on the next zwp_text_input_v4.done event. + + The initial value of text is an empty string. + </description> + <arg name="text" type="string" allow-null="true"/> + </event> + + <event name="delete_surrounding_text"> + <description summary="delete surrounding text"> + Notify when the text around the current cursor position should be + deleted. + + Before_length and after_length are the number of bytes before and after + the current cursor index (excluding the selection) to delete. + + If a preedit text is present, in effect before_length is counted from + the beginning of it, and after_length from its end (see done event + sequence). + + Values set with this event are double-buffered. They must be applied + and reset to initial on the next zwp_text_input_v4.done event. + + The initial values of both before_length and after_length are 0. + </description> + <arg name="before_length" type="uint" summary="length of text before current cursor position"/> + <arg name="after_length" type="uint" summary="length of text after current cursor position"/> + </event> + + <event name="done"> + <description summary="apply changes"> + Instruct the application to apply changes to state requested by the + preedit_string, commit_string and delete_surrounding_text events. The + state relating to these events is double-buffered, and each one + modifies the pending state. This event replaces the current state with + the pending state. + + The application must proceed by evaluating the changes in the following + order: + + 1. Replace existing preedit string with the cursor. + 2. Delete requested surrounding text. + 3. Insert commit string with the cursor at its end. + 4. Calculate surrounding text to send. + 5. Insert new preedit text in cursor position. + 6. Place cursor inside preedit text. + + The serial number reflects the last state of the zwp_text_input_v4 + object known to the compositor. The value of the serial argument must + be equal to the number of commit requests already issued on that object. + When the client receives a done event with a serial different than the + number of past commit requests, it must proceed as normal, except it + should not change the current state of the zwp_text_input_v4 object. + </description> + <arg name="serial" type="uint"/> + </event> + + <enum name="commit_mode"> + <description summary="focus commit mode"> + Pre-edit commit mode when the focus widget or the cursor position + is changed. + </description> + <entry name="clear" value="0" summary="pre-edit text is cleared"/> + <entry name="commit" value="1" summary="pre-edit text is committed"/> + </enum> + + <event name="preedit_commit_mode"> + <description summary="pre-edit commit mode"> + Specify how the visible preedit should be handled + when switching the focus widget or changing the cursor position, + whether to commit the preedit text or clear the preedit text. + + This is usually used together with the preedit_string event. + + The commit behavior is the same for focus switch and + cursor position change. + + The parameter mode selects the desired behavior and + its value is one from the commit mode enum. + </description> + <arg name="mode" type="uint" enum="commit_mode"/> + </event> + </interface> + + <interface name="zwp_text_input_manager_v4" version="1"> + <description summary="text input manager"> + A factory for text-input objects. This object is a global singleton. + </description> + + <request name="destroy" type="destructor"> + <description summary="Destroy the wp_text_input_manager"> + Destroy the wp_text_input_manager object. + </description> + </request> + + <request name="get_text_input"> + <description summary="create a new text input object"> + Creates a new text-input object for a given seat. + </description> + <arg name="id" type="new_id" interface="zwp_text_input_v4"/> + <arg name="seat" type="object" interface="wl_seat"/> + </request> + </interface> +</protocol> diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt index 79a07033..c7264d67 100644 --- a/src/client/CMakeLists.txt +++ b/src/client/CMakeLists.txt @@ -76,6 +76,7 @@ qt6_generate_wayland_protocol_client_sources(WaylandClient ${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-v2.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/text-input-unstable-v4-wip.xml ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/wayland.xml ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/wp-primary-selection-unstable-v1.xml ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/xdg-output-unstable-v1.xml @@ -105,6 +106,13 @@ qt6_generate_wayland_protocol_client_sources(WaylandClient # ) # special case end +qt_internal_extend_target(WaylandClient CONDITION QT_FEATURE_wayland_text_input_v4_wip + SOURCES + qwaylandtextinputv4.cpp qwaylandtextinputv4_p.h + DEFINES + QT_WAYLAND_TEXT_INPUT_V4_WIP=1 +) + qt_internal_extend_target(WaylandClient CONDITION QT_FEATURE_clipboard SOURCES qwaylandclipboard.cpp qwaylandclipboard_p.h diff --git a/src/client/qwaylanddisplay.cpp b/src/client/qwaylanddisplay.cpp index 523de1f3..8043115a 100644 --- a/src/client/qwaylanddisplay.cpp +++ b/src/client/qwaylanddisplay.cpp @@ -61,6 +61,9 @@ #endif #include "qwaylandhardwareintegration_p.h" #include "qwaylandtextinputv2_p.h" +#if QT_WAYLAND_TEXT_INPUT_V4_WIP +#include "qwaylandtextinputv4_p.h" +#endif // QT_WAYLAND_TEXT_INPUT_V4_WIP #include "qwaylandinputcontext_p.h" #include "qwaylandinputmethodcontext_p.h" @@ -76,6 +79,7 @@ #include "qwaylandqtkey_p.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> #include <QtWaylandClient/private/qwayland-qt-text-input-method-unstable-v1.h> @@ -450,6 +454,10 @@ void QWaylandDisplay::checkTextInputProtocol() << QLatin1String(QtWayland::zwp_text_input_v2::interface()->name); timps << QLatin1String(QtWayland::qt_text_input_method_manager_v1::interface()->name) << QLatin1String(QtWayland::zwp_text_input_manager_v2::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); +#endif // QT_WAYLAND_TEXT_INPUT_V4_WIP QString tiProtocols = QString::fromLocal8Bit(qgetenv("QT_WAYLAND_TEXT_INPUT_PROTOCOL")); qCDebug(lcQpaWayland) << "QT_WAYLAND_TEXT_INPUT_PROTOCOL=" << tiProtocols; @@ -528,7 +536,10 @@ 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) { - mTextInputManager.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->setTextInput(nullptr); } @@ -543,15 +554,35 @@ void QWaylandDisplay::registry_global(uint32_t id, const QString &interface, uin qCDebug(lcQpaWayland) << "text input: register zwp_text_input_v2"; if (mTextInputManagerIndex < INT_MAX) { mTextInputMethodManager.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); } - mTextInputManager.reset(new QtWayland::zwp_text_input_manager_v2(registry, id, 1)); + mTextInputManagerv2.reset(new QtWayland::zwp_text_input_manager_v2(registry, id, 1)); for (QWaylandInputDevice *inputDevice : qAsConst(mInputDevices)) - inputDevice->setTextInput(new QWaylandTextInputv2(this, mTextInputManager->get_text_input(inputDevice->wl_seat()))); + inputDevice->setTextInput(new QWaylandTextInputv2(this, mTextInputManagerv2->get_text_input(inputDevice->wl_seat()))); mWaylandIntegration->reconfigureInputContext(); mTextInputManagerIndex = mTextInputManagerList.indexOf(interface); +#if QT_WAYLAND_TEXT_INPUT_V4_WIP + } else if (interface == QLatin1String(QtWayland::zwp_text_input_manager_v4::interface()->name) + && (mTextInputManagerList.contains(interface) && mTextInputManagerList.indexOf(interface) < mTextInputManagerIndex)) { + qCDebug(lcQpaWayland) << "text input: register zwp_text_input_v4"; + if (mTextInputManagerIndex < INT_MAX) { + mTextInputMethodManager.reset(); + mTextInputManagerv2.reset(); + for (QWaylandInputDevice *inputDevice : qAsConst(mInputDevices)) + inputDevice->setTextInputMethod(nullptr); + } + + mTextInputManagerv4.reset(new QtWayland::zwp_text_input_manager_v4(registry, id, 1)); + for (QWaylandInputDevice *inputDevice : qAsConst(mInputDevices)) + inputDevice->setTextInput(new QWaylandTextInputv4(this, mTextInputManagerv4->get_text_input(inputDevice->wl_seat()))); + mWaylandIntegration->reconfigureInputContext(); + mTextInputManagerIndex = mTextInputManagerList.indexOf(interface); +#endif // QT_WAYLAND_TEXT_INPUT_V4_WIP } else if (interface == QLatin1String(QWaylandHardwareIntegration::interface()->name)) { bool disableHardwareIntegration = qEnvironmentVariableIntValue("QT_WAYLAND_DISABLE_HW_INTEGRATION"); if (!disableHardwareIntegration) { @@ -599,11 +630,19 @@ void QWaylandDisplay::registry_global_remove(uint32_t id) } } if (global.interface == QLatin1String(QtWayland::zwp_text_input_manager_v2::interface()->name)) { - mTextInputManager.reset(); + mTextInputManagerv2.reset(); + for (QWaylandInputDevice *inputDevice : qAsConst(mInputDevices)) + inputDevice->setTextInput(nullptr); + mWaylandIntegration->reconfigureInputContext(); + } +#if QT_WAYLAND_TEXT_INPUT_V4_WIP + if (global.interface == QLatin1String(QtWayland::zwp_text_input_manager_v4::interface()->name)) { + mTextInputManagerv4.reset(); for (QWaylandInputDevice *inputDevice : qAsConst(mInputDevices)) inputDevice->setTextInput(nullptr); mWaylandIntegration->reconfigureInputContext(); } +#endif // QT_WAYLAND_TEXT_INPUT_V4_WIP if (global.interface == QLatin1String(QtWayland::qt_text_input_method_manager_v1::interface()->name)) { mTextInputMethodManager.reset(); for (QWaylandInputDevice *inputDevice : qAsConst(mInputDevices)) diff --git a/src/client/qwaylanddisplay_p.h b/src/client/qwaylanddisplay_p.h index b58099ee..8be91118 100644 --- a/src/client/qwaylanddisplay_p.h +++ b/src/client/qwaylanddisplay_p.h @@ -83,6 +83,7 @@ class QPlatformPlaceholderScreen; namespace QtWayland { class qt_surface_extension; class zwp_text_input_manager_v2; + class zwp_text_input_manager_v4; class qt_text_input_method_manager_v1; } @@ -171,7 +172,8 @@ 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_v2 *textInputManager() const { return mTextInputManager.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(); } QWaylandXdgOutputManagerV1 *xdgOutputManager() const { return mXdgOutputManager.data(); } @@ -280,7 +282,8 @@ private: QScopedPointer<QWaylandPrimarySelectionDeviceManagerV1> mPrimarySelectionManager; #endif QScopedPointer<QtWayland::qt_text_input_method_manager_v1> mTextInputMethodManager; - QScopedPointer<QtWayland::zwp_text_input_manager_v2> mTextInputManager; + QScopedPointer<QtWayland::zwp_text_input_manager_v2> mTextInputManagerv2; + QScopedPointer<QtWayland::zwp_text_input_manager_v4> mTextInputManagerv4; QScopedPointer<QWaylandHardwareIntegration> mHardwareIntegration; QScopedPointer<QWaylandXdgOutputManagerV1> mXdgOutputManager; int mFd = -1; diff --git a/src/client/qwaylandinputcontext.cpp b/src/client/qwaylandinputcontext.cpp index 9ac1c3df..c5948527 100644 --- a/src/client/qwaylandinputcontext.cpp +++ b/src/client/qwaylandinputcontext.cpp @@ -70,7 +70,11 @@ QWaylandInputContext::~QWaylandInputContext() bool QWaylandInputContext::isValid() const { - return mDisplay->textInputManager() != nullptr; +#if QT_WAYLAND_TEXT_INPUT_V4_WIP + return mDisplay->textInputManagerv2() != nullptr || mDisplay->textInputManagerv4() != nullptr; +#else // QT_WAYLAND_TEXT_INPUT_V4_WIP + return mDisplay->textInputManagerv2() != nullptr; +#endif // QT_WAYLAND_TEXT_INPUT_V4_WIP } void QWaylandInputContext::reset() diff --git a/src/client/qwaylandinputdevice.cpp b/src/client/qwaylandinputdevice.cpp index 5fbdd241..7f503ee1 100644 --- a/src/client/qwaylandinputdevice.cpp +++ b/src/client/qwaylandinputdevice.cpp @@ -58,6 +58,9 @@ #include "qwaylanddisplay_p.h" #include "qwaylandshmbackingstore_p.h" #include "qwaylandtextinputv2_p.h" +#if QT_WAYLAND_TEXT_INPUT_V4_WIP +#include "qwaylandtextinputv4_p.h" +#endif // QT_WAYLAND_TEXT_INPUT_V4_WIP #include "qwaylandtextinputinterface_p.h" #include "qwaylandinputcontext_p.h" #include "qwaylandinputmethodcontext_p.h" @@ -421,8 +424,13 @@ QWaylandInputDevice::QWaylandInputDevice(QWaylandDisplay *display, int version, setPrimarySelectionDevice(psm->createDevice(this)); #endif - if (mQDisplay->textInputManager()) - mTextInput.reset(new QWaylandTextInputv2(mQDisplay, mQDisplay->textInputManager()->get_text_input(wl_seat()))); + if (mQDisplay->textInputManagerv2()) + mTextInput.reset(new QWaylandTextInputv2(mQDisplay, mQDisplay->textInputManagerv2()->get_text_input(wl_seat()))); + +#if QT_WAYLAND_TEXT_INPUT_V4_WIP + if (mQDisplay->textInputManagerv4()) + mTextInput.reset(new QWaylandTextInputv4(mQDisplay, mQDisplay->textInputManagerv4()->get_text_input(wl_seat()))); +#endif // QT_WAYLAND_TEXT_INPUT_V4_WIP if (mQDisplay->textInputMethodManager()) mTextInputMethod.reset(new QWaylandTextInputMethod(mQDisplay, mQDisplay->textInputMethodManager()->get_text_input_method(wl_seat()))); diff --git a/src/client/qwaylandintegration.cpp b/src/client/qwaylandintegration.cpp index 67af87cb..a1da5ccf 100644 --- a/src/client/qwaylandintegration.cpp +++ b/src/client/qwaylandintegration.cpp @@ -497,7 +497,11 @@ void QWaylandIntegration::reconfigureInputContext() if (requested.isNull()) { if (mDisplay->textInputMethodManager() != nullptr) mInputContext.reset(new QWaylandInputMethodContext(mDisplay.data())); - else if (mDisplay->textInputManager() != nullptr) +#if QT_WAYLAND_TEXT_INPUT_V4_WIP + else if (mDisplay->textInputManagerv2() != nullptr || mDisplay->textInputManagerv4() != nullptr) +#else // QT_WAYLAND_TEXT_INPUT_V4_WIP + else if (mDisplay->textInputManagerv2() != nullptr) +#endif // QT_WAYLAND_TEXT_INPUT_V4_WIP mInputContext.reset(new QWaylandInputContext(mDisplay.data())); } else { mInputContext.reset(QPlatformInputContextFactory::create(requested)); diff --git a/src/client/qwaylandtextinputinterface_p.h b/src/client/qwaylandtextinputinterface_p.h index d918f083..2b0f381b 100644 --- a/src/client/qwaylandtextinputinterface_p.h +++ b/src/client/qwaylandtextinputinterface_p.h @@ -39,6 +39,7 @@ #ifndef QWAYLANDTEXTINPUTINTERFACE_P_H #define QWAYLANDTEXTINPUTINTERFACE_P_H + // // W A R N I N G // ------------- @@ -68,8 +69,8 @@ public: virtual void disableSurface(::wl_surface *surface) = 0; virtual void enableSurface(::wl_surface *surface) = 0; virtual void updateState(Qt::InputMethodQueries queries, uint32_t flags) = 0; - virtual void showInputPanel() = 0; - virtual void hideInputPanel() = 0; + virtual void showInputPanel() {} + virtual void hideInputPanel() {} virtual bool isInputPanelVisible() const = 0; virtual QRectF keyboardRect() const = 0; virtual QLocale locale() const = 0; diff --git a/src/client/qwaylandtextinputv4.cpp b/src/client/qwaylandtextinputv4.cpp new file mode 100644 index 00000000..5cecaf7a --- /dev/null +++ b/src/client/qwaylandtextinputv4.cpp @@ -0,0 +1,409 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins 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 "qwaylandtextinputv4_p.h" + +#include "qwaylandwindow_p.h" +#include "qwaylandinputmethodeventbuilder_p.h" + +#include <QtCore/qloggingcategory.h> +#include <QtGui/qguiapplication.h> +#include <QtGui/qevent.h> +#include <QtGui/qwindow.h> +#include <QTextCharFormat> + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(qLcQpaWaylandTextInput, "qt.qpa.wayland.textinput") + +namespace QtWaylandClient { + +QWaylandTextInputv4::QWaylandTextInputv4(QWaylandDisplay *display, + struct ::zwp_text_input_v4 *text_input) + : QtWayland::zwp_text_input_v4(text_input) + , m_display(display) +{ + +} + +QWaylandTextInputv4::~QWaylandTextInputv4() +{ +} + +namespace { +const Qt::InputMethodQueries supportedQueries = Qt::ImEnabled | + Qt::ImSurroundingText | + Qt::ImCursorPosition | + Qt::ImAnchorPosition | + Qt::ImHints | + Qt::ImCursorRectangle; +} + +void QWaylandTextInputv4::zwp_text_input_v4_enter(struct ::wl_surface *surface) +{ + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO; + + m_surface = surface; + + m_pendingPreeditString.clear(); + m_pendingCommitString.clear(); + m_pendingDeleteBeforeText = 0; + m_pendingDeleteAfterText = 0; + + enable(); + updateState(supportedQueries, update_state_enter); +} + +void QWaylandTextInputv4::zwp_text_input_v4_leave(struct ::wl_surface *surface) +{ + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO; + + if (m_surface != surface) { + qCWarning(qLcQpaWaylandTextInput()) << Q_FUNC_INFO << "Got leave event for surface" << surface << "focused surface" << m_surface; + return; + } + + // QTBUG-97248: check commit_mode + // Currently text-input-unstable-v4-wip is implemented with preedit_commit_mode + // 'commit' + + m_currentPreeditString.clear(); + + m_surface = nullptr; + m_currentSerial = 0U; + + disable(); + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << "Done"; +} + +void QWaylandTextInputv4::zwp_text_input_v4_preedit_string(const QString &text, int32_t cursorBegin, int32_t cursorEnd) +{ + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << text << cursorBegin << cursorEnd; + + if (!QGuiApplication::focusObject()) + return; + + m_pendingPreeditString.text = text; + m_pendingPreeditString.cursorBegin = cursorBegin; + m_pendingPreeditString.cursorEnd = cursorEnd; +} + +void QWaylandTextInputv4::zwp_text_input_v4_commit_string(const QString &text) +{ + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << text; + + if (!QGuiApplication::focusObject()) + return; + + m_pendingCommitString = text; +} + +void QWaylandTextInputv4::zwp_text_input_v4_delete_surrounding_text(uint32_t beforeText, uint32_t afterText) +{ + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << beforeText << afterText; + + if (!QGuiApplication::focusObject()) + return; + + m_pendingDeleteBeforeText = QWaylandInputMethodEventBuilder::indexFromWayland(m_surroundingText, beforeText); + m_pendingDeleteAfterText = QWaylandInputMethodEventBuilder::indexFromWayland(m_surroundingText, afterText); +} + +void QWaylandTextInputv4::zwp_text_input_v4_done(uint32_t serial) +{ + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << "with serial" << serial << m_currentSerial; + + // This is a case of double click. + // text_input_v4 will ignore this done signal and just keep the selection of the clicked word. + if (m_cursorPos != m_anchorPos && (m_pendingDeleteBeforeText != 0 || m_pendingDeleteAfterText != 0)) { + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << "Ignore done"; + m_pendingDeleteBeforeText = 0; + m_pendingDeleteAfterText = 0; + m_pendingPreeditString.clear(); + m_pendingCommitString.clear(); + return; + } + + QObject *focusObject = QGuiApplication::focusObject(); + if (!focusObject) + return; + + if (!m_surface) { + qCWarning(qLcQpaWaylandTextInput) << Q_FUNC_INFO << serial << "Surface is not enabled yet"; + return; + } + + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << "PREEDIT" << m_pendingPreeditString.text << m_pendingPreeditString.cursorBegin; + + QList<QInputMethodEvent::Attribute> attributes; + { + if (m_pendingPreeditString.cursorBegin != -1 || + m_pendingPreeditString.cursorEnd != -1) { + // Current supported cursor shape is just line. + // It means, cursorEnd and cursorBegin are the same. + QInputMethodEvent::Attribute attribute1(QInputMethodEvent::Cursor, + m_pendingPreeditString.text.length(), + 1); + attributes.append(attribute1); + } + + // only use single underline style for now + QTextCharFormat format; + format.setFontUnderline(true); + format.setUnderlineStyle(QTextCharFormat::SingleUnderline); + QInputMethodEvent::Attribute attribute2(QInputMethodEvent::TextFormat, + 0, + m_pendingPreeditString.text.length(), format); + attributes.append(attribute2); + } + QInputMethodEvent event(m_pendingPreeditString.text, attributes); + + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << "DELETE" << m_pendingDeleteBeforeText << m_pendingDeleteAfterText; + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << "COMMIT" << m_pendingCommitString; + + // A workaround for reselection + // It will disable redundant commit after reselection + if (m_pendingDeleteBeforeText != 0 || m_pendingDeleteAfterText != 0) + m_condReselection = true; + + event.setCommitString(m_pendingCommitString, + -m_pendingDeleteBeforeText, + m_pendingDeleteBeforeText + m_pendingDeleteAfterText); + m_currentPreeditString = m_pendingPreeditString; + m_pendingPreeditString.clear(); + m_pendingCommitString.clear(); + m_pendingDeleteBeforeText = 0; + m_pendingDeleteAfterText = 0; + QCoreApplication::sendEvent(focusObject, &event); + + if (serial == m_currentSerial) + updateState(supportedQueries, update_state_full); +} + +void QWaylandTextInputv4::reset() +{ + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO; + + m_pendingPreeditString.clear(); +} + +void QWaylandTextInputv4::enableSurface(::wl_surface *) +{ + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO; +} + +void QWaylandTextInputv4::disableSurface(::wl_surface *surface) +{ + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO; + + if (m_surface != surface) { + qCWarning(qLcQpaWaylandTextInput()) << Q_FUNC_INFO << "for surface" << surface << "focused surface" << m_surface; + return; + } +} + +void QWaylandTextInputv4::commit() +{ + m_currentSerial = (m_currentSerial < UINT_MAX) ? m_currentSerial + 1U: 0U; + + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << "with serial" << m_currentSerial; + QtWayland::zwp_text_input_v4::commit(); +} + +void QWaylandTextInputv4::updateState(Qt::InputMethodQueries queries, uint32_t flags) +{ + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << queries << 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; + bool needsCommit = false; + + QInputMethodQueryEvent event(queries); + QCoreApplication::sendEvent(QGuiApplication::focusObject(), &event); + + // For some reason, a query for Qt::ImSurroundingText gives an empty string even though it is not. + if (!(queries & Qt::ImSurroundingText) && event.value(Qt::ImSurroundingText).toString().isEmpty()) { + return; + } + + 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()); + if (surfaceRect != m_cursorRect) { + set_cursor_rectangle(surfaceRect.x(), surfaceRect.y(), surfaceRect.width(), surfaceRect.height()); + m_cursorRect = surfaceRect; + needsCommit = true; + } + } + + 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(); + + qCDebug(qLcQpaWaylandTextInput) << "Orginal surrounding_text from InputMethodQuery: " << text << cursor << anchor; + + // Make sure text is not too big + // surround_text cannot exceed 4000byte in wayland protocol + // The worst case will be supposed here. + const int MAX_MESSAGE_SIZE = 4000; + + if (text.toUtf8().size() > MAX_MESSAGE_SIZE) { + const int selectionStart = QWaylandInputMethodEventBuilder::indexToWayland(text, qMin(cursor, anchor)); + const int selectionEnd = QWaylandInputMethodEventBuilder::indexToWayland(text, qMax(cursor, anchor)); + const int selectionLength = selectionEnd - selectionStart; + // If selection is bigger than 4000 byte, it is fixed to 4000 byte. + // anchor will be moved in the 4000 byte boundary. + if (selectionLength > MAX_MESSAGE_SIZE) { + if (anchor > cursor) { + const int length = MAX_MESSAGE_SIZE; + anchor = QWaylandInputMethodEventBuilder::trimmedIndexFromWayland(text, length, cursor); + anchor -= cursor; + text = text.mid(cursor, anchor); + cursor = 0; + } else { + const int length = -MAX_MESSAGE_SIZE; + anchor = QWaylandInputMethodEventBuilder::trimmedIndexFromWayland(text, length, cursor); + cursor -= anchor; + text = text.mid(anchor, cursor); + anchor = 0; + } + } else { + const int offset = (MAX_MESSAGE_SIZE - selectionLength) / 2; + + int textStart = QWaylandInputMethodEventBuilder::trimmedIndexFromWayland(text, -offset, qMin(cursor, anchor)); + int textEnd = QWaylandInputMethodEventBuilder::trimmedIndexFromWayland(text, MAX_MESSAGE_SIZE, textStart); + + anchor -= textStart; + cursor -= textStart; + text = text.mid(textStart, textEnd - textStart); + } + } + qCDebug(qLcQpaWaylandTextInput) << "Modified surrounding_text: " << text << cursor << anchor; + + const int cursorPos = QWaylandInputMethodEventBuilder::indexToWayland(text, cursor); + const int anchorPos = QWaylandInputMethodEventBuilder::indexToWayland(text, anchor); + + if (m_surroundingText != text || m_cursorPos != cursorPos || m_anchorPos != anchorPos) { + qCDebug(qLcQpaWaylandTextInput) << "Current surrounding_text: " << m_surroundingText << m_cursorPos << m_anchorPos; + qCDebug(qLcQpaWaylandTextInput) << "New surrounding_text: " << text << cursorPos << anchorPos; + + set_surrounding_text(text, cursorPos, anchorPos); + + // A workaround in the case of reselection + // It will work when re-clicking a preedit text + if (m_condReselection) { + qCDebug(qLcQpaWaylandTextInput) << "\"commit\" is disabled when Reselection by changing focus"; + m_condReselection = false; + needsCommit = false; + + } + + m_surroundingText = text; + m_cursorPos = cursorPos; + m_anchorPos = anchorPos; + m_cursor = cursor; + } + } + + if (queries & Qt::ImHints) { + QWaylandInputMethodContentType contentType = QWaylandInputMethodContentType::convertV4(static_cast<Qt::InputMethodHints>(event.value(Qt::ImHints).toInt())); + qCDebug(qLcQpaWaylandTextInput) << m_contentHint << contentType.hint; + qCDebug(qLcQpaWaylandTextInput) << m_contentPurpose << contentType.purpose; + + if (m_contentHint != contentType.hint || m_contentPurpose != contentType.purpose) { + qCDebug(qLcQpaWaylandTextInput) << "set_content_type: " << contentType.hint << contentType.purpose; + set_content_type(contentType.hint, contentType.purpose); + + m_contentHint = contentType.hint; + m_contentPurpose = contentType.purpose; + needsCommit = true; + } + } + + if (needsCommit + && (flags == update_state_change || flags == update_state_enter)) + commit(); +} + +void QWaylandTextInputv4::setCursorInsidePreedit(int cursor) +{ + Q_UNUSED(cursor); + qCWarning(qLcQpaWaylandTextInput) << "QWaylandTextInputV4: Input protocol \"text-input-unstable-v4-wip\" does not support setting cursor inside preedit. Use qt-text-input-method-unstable-v1 instead for full support of Qt input method events."; +} + +bool QWaylandTextInputv4::isInputPanelVisible() const +{ + qCWarning(qLcQpaWaylandTextInput) << "QWaylandTextInputV4: Input protocol \"text-input-unstable-v4-wip\" does not support querying input method visibility. Use qt-text-input-method-unstable-v1 instead for full support of Qt input method events."; + return false; +} + +QRectF QWaylandTextInputv4::keyboardRect() const +{ + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO; + return m_cursorRect; +} + +QLocale QWaylandTextInputv4::locale() const +{ + qCWarning(qLcQpaWaylandTextInput) << "QWaylandTextInputV4: Input protocol \"text-input-unstable-v4-wip\" does not support querying input language. Use qt-text-input-method-unstable-v1 instead for full support of Qt input method events."; + return QLocale(); +} + +Qt::LayoutDirection QWaylandTextInputv4::inputDirection() const +{ + qCWarning(qLcQpaWaylandTextInput) << "QWaylandTextInputV4: Input protocol \"text-input-unstable-v4-wip\" does not support querying input direction. Use qt-text-input-method-unstable-v1 instead for full support of Qt input method events."; + return Qt::LeftToRight; +} + +} + +QT_END_NAMESPACE diff --git a/src/client/qwaylandtextinputv4_p.h b/src/client/qwaylandtextinputv4_p.h new file mode 100644 index 00000000..d273cef2 --- /dev/null +++ b/src/client/qwaylandtextinputv4_p.h @@ -0,0 +1,139 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins 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 QWAYLANDTEXTINPUTV4_P_H +#define QWAYLANDTEXTINPUTV4_P_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-v4-wip.h> +#include <qwaylandinputmethodeventbuilder_p.h> + +struct wl_callback; +struct wl_callback_listener; + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(qLcQpaWaylandTextInput) + +namespace QtWaylandClient { + +class QWaylandDisplay; + +class QWaylandTextInputv4 : public QtWayland::zwp_text_input_v4, public QWaylandTextInputInterface +{ +public: + QWaylandTextInputv4(QWaylandDisplay *display, struct ::zwp_text_input_v4 *text_input); + ~QWaylandTextInputv4() override; + + void reset() override; + void commit() override; + void updateState(Qt::InputMethodQueries queries, uint32_t flags) override; + // TODO: not supported yet + void setCursorInsidePreedit(int cursor) override; + + bool isInputPanelVisible() const override; + QRectF keyboardRect() const override; + + QLocale locale() const override; + Qt::LayoutDirection inputDirection() const override; + + void enableSurface(::wl_surface *surface) override; + void disableSurface(::wl_surface *surface) override; + +protected: + void zwp_text_input_v4_enter(struct ::wl_surface *surface) override; + void zwp_text_input_v4_leave(struct ::wl_surface *surface) override; + void zwp_text_input_v4_preedit_string(const QString &text, int32_t cursor_begin, int32_t cursor_end) override; + void zwp_text_input_v4_commit_string(const QString &text) override; + void zwp_text_input_v4_delete_surrounding_text(uint32_t before_length, uint32_t after_length) override; + void zwp_text_input_v4_done(uint32_t serial) override; + +private: + QWaylandDisplay *m_display; + QWaylandInputMethodEventBuilder m_builder; + + ::wl_surface *m_surface = nullptr; // ### Here for debugging purposes + + struct PreeditInfo { + QString text; + int cursorBegin = 0; + int cursorEnd = 0; + + void clear() { + text.clear(); + cursorBegin = 0; + cursorEnd = 0; + } + }; + + PreeditInfo m_pendingPreeditString; + PreeditInfo m_currentPreeditString; + QString m_pendingCommitString; + uint m_pendingDeleteBeforeText = 0; + uint m_pendingDeleteAfterText = 0; + + QString m_surroundingText; + int m_cursor; // cursor position in QString + int m_cursorPos; // cursor position in wayland index + int m_anchorPos; // anchor position in wayland index + uint32_t m_contentHint = 0; + uint32_t m_contentPurpose = 0; + QRect m_cursorRect; + + uint m_currentSerial = 0; + + bool m_condReselection = false; +}; + +} + +QT_END_NAMESPACE + +#endif // QWAYLANDTEXTINPUTV4_P_H diff --git a/src/compositor/CMakeLists.txt b/src/compositor/CMakeLists.txt index 7d1de2a4..95118afb 100644 --- a/src/compositor/CMakeLists.txt +++ b/src/compositor/CMakeLists.txt @@ -89,13 +89,13 @@ qt_internal_add_resource(WaylandCompositor "compositor" ${compositor_resource_files} ) - qt6_generate_wayland_protocol_server_sources(WaylandCompositor FILES ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/idle-inhibit-unstable-v1.xml ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/ivi-application.xml ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/scaler.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/viewporter.xml ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/wayland.xml ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/xdg-decoration-unstable-v1.xml @@ -116,6 +116,14 @@ qt6_generate_wayland_protocol_server_sources(WaylandCompositor ## Scopes: ##################################################################### +qt_internal_extend_target(WaylandCompositor CONDITION QT_FEATURE_wayland_text_input_v4_wip + SOURCES + extensions/qwaylandtextinputv4.cpp extensions/qwaylandtextinputv4.h extensions/qwaylandtextinputv4_p.h + extensions/qwaylandtextinputmanagerv4.cpp extensions/qwaylandtextinputmanagerv4.h extensions/qwaylandtextinputmanagerv4_p.h + DEFINES + QT_WAYLAND_TEXT_INPUT_V4_WIP=1 +) + qt_internal_extend_target(WaylandCompositor CONDITION QT_FEATURE_opengl SOURCES hardware_integration/qwlclientbufferintegrationfactory.cpp hardware_integration/qwlclientbufferintegrationfactory_p.h diff --git a/src/compositor/compositor_api/qwaylandclient.h b/src/compositor/compositor_api/qwaylandclient.h index 667d7f64..b2e27f69 100644 --- a/src/compositor/compositor_api/qwaylandclient.h +++ b/src/compositor/compositor_api/qwaylandclient.h @@ -65,6 +65,7 @@ public: NoProtocol = 0, QtTextInputMethodV1 = 1, TextInputV2 = 2, + TextInputV4 = 4, QtTextInputMethod = QtTextInputMethodV1, TextInput = TextInputV2 diff --git a/src/compositor/compositor_api/qwaylandcompositor.cpp b/src/compositor/compositor_api/qwaylandcompositor.cpp index 5647bf53..98a0bb7f 100644 --- a/src/compositor/compositor_api/qwaylandcompositor.cpp +++ b/src/compositor/compositor_api/qwaylandcompositor.cpp @@ -88,6 +88,9 @@ QT_BEGIN_NAMESPACE Q_LOGGING_CATEGORY(qLcWaylandCompositor, "qt.waylandcompositor") Q_LOGGING_CATEGORY(qLcWaylandCompositorHardwareIntegration, "qt.waylandcompositor.hardwareintegration") Q_LOGGING_CATEGORY(qLcWaylandCompositorInputMethods, "qt.waylandcompositor.inputmethods") +#if QT_WAYLAND_TEXT_INPUT_V4_WIP +Q_LOGGING_CATEGORY(qLcWaylandCompositorTextInput, "qt.waylandcompositor.textinput") +#endif // QT_WAYLAND_TEXT_INPUT_V4_WIP namespace QtWayland { diff --git a/src/compositor/compositor_api/qwaylandcompositor.h b/src/compositor/compositor_api/qwaylandcompositor.h index 28431b46..c5272a5d 100644 --- a/src/compositor/compositor_api/qwaylandcompositor.h +++ b/src/compositor/compositor_api/qwaylandcompositor.h @@ -63,6 +63,7 @@ class QWaylandBufferRef; Q_WAYLAND_COMPOSITOR_EXPORT Q_DECLARE_LOGGING_CATEGORY(qLcWaylandCompositor) Q_WAYLAND_COMPOSITOR_EXPORT Q_DECLARE_LOGGING_CATEGORY(qLcWaylandCompositorHardwareIntegration) Q_DECLARE_LOGGING_CATEGORY(qLcWaylandCompositorInputMethods) +Q_DECLARE_LOGGING_CATEGORY(qLcWaylandCompositorTextInput) class Q_WAYLAND_COMPOSITOR_EXPORT QWaylandCompositor : public QWaylandObject { diff --git a/src/compositor/compositor_api/qwaylandcompositorquickextensions_p.h b/src/compositor/compositor_api/qwaylandcompositorquickextensions_p.h index 18426713..d592afbe 100644 --- a/src/compositor/compositor_api/qwaylandcompositorquickextensions_p.h +++ b/src/compositor/compositor_api/qwaylandcompositorquickextensions_p.h @@ -49,6 +49,9 @@ #include <QtWaylandCompositor/qwaylandquickcompositor.h> #include <QtWaylandCompositor/qwaylandqtwindowmanager.h> #include <QtWaylandCompositor/qwaylandtextinputmanager.h> +#if QT_WAYLAND_TEXT_INPUT_V4_WIP +#include <QtWaylandCompositor/qwaylandtextinputmanagerv4.h> +#endif // QT_WAYLAND_TEXT_INPUT_V4_WIP #include <QtWaylandCompositor/qwaylandqttextinputmethodmanager.h> #include <QtWaylandCompositor/qwaylandidleinhibitv1.h> @@ -107,6 +110,9 @@ private: Q_COMPOSITOR_DECLARE_QUICK_EXTENSION_NAMED_CLASS(QWaylandQtWindowManager, QtWindowManager) Q_COMPOSITOR_DECLARE_QUICK_EXTENSION_NAMED_CLASS(QWaylandIdleInhibitManagerV1, IdleInhibitManagerV1) Q_COMPOSITOR_DECLARE_QUICK_EXTENSION_NAMED_CLASS(QWaylandTextInputManager, TextInputManager) +#if QT_WAYLAND_TEXT_INPUT_V4_WIP +Q_COMPOSITOR_DECLARE_QUICK_EXTENSION_NAMED_CLASS(QWaylandTextInputManagerV4, TextInputManagerV4) +#endif // QT_WAYLAND_TEXT_INPUT_V4_WIP Q_COMPOSITOR_DECLARE_QUICK_EXTENSION_NAMED_CLASS(QWaylandQtTextInputMethodManager, QtTextInputMethodManager) QT_END_NAMESPACE diff --git a/src/compositor/compositor_api/qwaylandinputmethodcontrol.cpp b/src/compositor/compositor_api/qwaylandinputmethodcontrol.cpp index 9c52f646..be019b5b 100644 --- a/src/compositor/compositor_api/qwaylandinputmethodcontrol.cpp +++ b/src/compositor/compositor_api/qwaylandinputmethodcontrol.cpp @@ -35,6 +35,9 @@ #include "qwaylandsurface.h" #include "qwaylandview.h" #include "qwaylandtextinput.h" +#if QT_WAYLAND_TEXT_INPUT_V4_WIP +#include "qwaylandtextinputv4.h" +#endif // QT_WAYLAND_TEXT_INPUT_V4_WIP #include "qwaylandqttextinputmethod.h" #include <QtGui/QInputMethodEvent> @@ -51,6 +54,15 @@ QWaylandInputMethodControl::QWaylandInputMethodControl(QWaylandSurface *surface) connect(textInput, &QWaylandTextInput::updateInputMethod, this, &QWaylandInputMethodControl::updateInputMethod); } +#if QT_WAYLAND_TEXT_INPUT_V4_WIP + QWaylandTextInputV4 *textInputV4 = d_func()->textInputV4(); + if (textInputV4) { + connect(textInputV4, &QWaylandTextInputV4::surfaceEnabled, this, &QWaylandInputMethodControl::surfaceEnabled); + connect(textInputV4, &QWaylandTextInputV4::surfaceDisabled, this, &QWaylandInputMethodControl::surfaceDisabled); + connect(textInputV4, &QWaylandTextInputV4::updateInputMethod, this, &QWaylandInputMethodControl::updateInputMethod); + } +#endif // QT_WAYLAND_TEXT_INPUT_V4_WIP + QWaylandQtTextInputMethod *textInputMethod = d_func()->textInputMethod(); if (textInputMethod) { connect(textInputMethod, &QWaylandQtTextInputMethod::surfaceEnabled, this, &QWaylandInputMethodControl::surfaceEnabled); @@ -67,6 +79,12 @@ QVariant QWaylandInputMethodControl::inputMethodQuery(Qt::InputMethodQuery query if (textInput != nullptr && textInput->focus() == d->surface) return textInput->inputMethodQuery(query, argument); +#if QT_WAYLAND_TEXT_INPUT_V4_WIP + QWaylandTextInputV4 *textInputV4 = d->textInputV4(); + if (textInputV4 != nullptr && textInputV4->focus() == d->surface) + return textInputV4->inputMethodQuery(query, argument); +#endif // QT_WAYLAND_TEXT_INPUT_V4_WIP + QWaylandQtTextInputMethod *textInputMethod = d_func()->textInputMethod(); if (textInputMethod && textInputMethod->focusedSurface() == d->surface) return textInputMethod->inputMethodQuery(query, argument); @@ -78,11 +96,13 @@ void QWaylandInputMethodControl::inputMethodEvent(QInputMethodEvent *event) { Q_D(QWaylandInputMethodControl); - QWaylandTextInput *textInput = d->textInput(); - QWaylandQtTextInputMethod *textInputMethod = d->textInputMethod(); - if (textInput) { + if (QWaylandTextInput *textInput = d->textInput()) { textInput->sendInputMethodEvent(event); - } else if (textInputMethod) { +#if QT_WAYLAND_TEXT_INPUT_V4_WIP + } else if (QWaylandTextInputV4 *textInputV4 = d->textInputV4()) { + textInputV4->sendInputMethodEvent(event); +#endif // QT_WAYLAND_TEXT_INPUT_V4_WIP + } else if (QWaylandQtTextInputMethod *textInputMethod = d->textInputMethod()) { textInputMethod->sendInputMethodEvent(event); } else { event->ignore(); @@ -134,8 +154,14 @@ void QWaylandInputMethodControl::setSurface(QWaylandSurface *surface) d->surface = surface; QWaylandTextInput *textInput = d->textInput(); +#if QT_WAYLAND_TEXT_INPUT_V4_WIP + QWaylandTextInputV4 *textInputV4 = d->textInputV4(); +#endif // QT_WAYLAND_TEXT_INPUT_V4_WIP QWaylandQtTextInputMethod *textInputMethod = d->textInputMethod(); setEnabled((textInput && textInput->isSurfaceEnabled(d->surface)) +#if QT_WAYLAND_TEXT_INPUT_V4_WIP + || (textInputV4 && textInputV4->isSurfaceEnabled(d->surface)) +#endif // QT_WAYLAND_TEXT_INPUT_V4_WIP || (textInputMethod && textInputMethod->isSurfaceEnabled(d->surface))); } @@ -144,10 +170,16 @@ void QWaylandInputMethodControl::defaultSeatChanged() Q_D(QWaylandInputMethodControl); disconnect(d->textInput(), nullptr, this, nullptr); +#if QT_WAYLAND_TEXT_INPUT_V4_WIP + disconnect(d->textInputV4(), nullptr, this, nullptr); +#endif // QT_WAYLAND_TEXT_INPUT_V4_WIP disconnect(d->textInputMethod(), nullptr, this, nullptr); d->seat = d->compositor->defaultSeat(); QWaylandTextInput *textInput = d->textInput(); +#if QT_WAYLAND_TEXT_INPUT_V4_WIP + QWaylandTextInputV4 *textInputV4 = d->textInputV4(); +#endif // QT_WAYLAND_TEXT_INPUT_V4_WIP QWaylandQtTextInputMethod *textInputMethod = d->textInputMethod(); if (textInput) { @@ -155,12 +187,22 @@ void QWaylandInputMethodControl::defaultSeatChanged() connect(textInput, &QWaylandTextInput::surfaceDisabled, this, &QWaylandInputMethodControl::surfaceDisabled); } +#if QT_WAYLAND_TEXT_INPUT_V4_WIP + if (textInputV4) { + connect(textInputV4, &QWaylandTextInputV4::surfaceEnabled, this, &QWaylandInputMethodControl::surfaceEnabled); + connect(textInputV4, &QWaylandTextInputV4::surfaceDisabled, this, &QWaylandInputMethodControl::surfaceDisabled); + } +#endif // QT_WAYLAND_TEXT_INPUT_V4_WIP + if (textInputMethod) { connect(textInputMethod, &QWaylandQtTextInputMethod::surfaceEnabled, this, &QWaylandInputMethodControl::surfaceEnabled); connect(textInputMethod, &QWaylandQtTextInputMethod::surfaceDisabled, this, &QWaylandInputMethodControl::surfaceDisabled); } setEnabled((textInput && textInput->isSurfaceEnabled(d->surface)) +#if QT_WAYLAND_TEXT_INPUT_V4_WIP + || (textInputV4 && textInputV4->isSurfaceEnabled(d->surface)) +#endif // QT_WAYLAND_TEXT_INPUT_V4_WIP || (textInputMethod && textInputMethod->isSurfaceEnabled(d->surface))); } @@ -185,4 +227,11 @@ QWaylandTextInput *QWaylandInputMethodControlPrivate::textInput() const return QWaylandTextInput::findIn(seat); } +#if QT_WAYLAND_TEXT_INPUT_V4_WIP +QWaylandTextInputV4 *QWaylandInputMethodControlPrivate::textInputV4() const +{ + return QWaylandTextInputV4::findIn(seat); +} +#endif // QT_WAYLAND_TEXT_INPUT_V4_WIP + #include "moc_qwaylandinputmethodcontrol.cpp" diff --git a/src/compositor/compositor_api/qwaylandinputmethodcontrol_p.h b/src/compositor/compositor_api/qwaylandinputmethodcontrol_p.h index e25ac48c..4ca46d13 100644 --- a/src/compositor/compositor_api/qwaylandinputmethodcontrol_p.h +++ b/src/compositor/compositor_api/qwaylandinputmethodcontrol_p.h @@ -52,6 +52,9 @@ class QWaylandCompositor; class QWaylandSeat; class QWaylandSurface; class QWaylandTextInput; +#if QT_WAYLAND_TEXT_INPUT_V4_WIP +class QWaylandTextInputV4; +#endif // QT_WAYLAND_TEXT_INPUT_V4_WIP class QWaylandQtTextInputMethod; class Q_WAYLAND_COMPOSITOR_EXPORT QWaylandInputMethodControlPrivate : public QObjectPrivate @@ -62,6 +65,9 @@ public: explicit QWaylandInputMethodControlPrivate(QWaylandSurface *surface); QWaylandTextInput *textInput() const; +#if QT_WAYLAND_TEXT_INPUT_V4_WIP + QWaylandTextInputV4 *textInputV4() const; +#endif // QT_WAYLAND_TEXT_INPUT_V4_WIP QWaylandQtTextInputMethod *textInputMethod() const; QWaylandCompositor *compositor = nullptr; diff --git a/src/compositor/compositor_api/qwaylandquickitem.cpp b/src/compositor/compositor_api/qwaylandquickitem.cpp index d1b14ceb..9796b72a 100644 --- a/src/compositor/compositor_api/qwaylandquickitem.cpp +++ b/src/compositor/compositor_api/qwaylandquickitem.cpp @@ -32,6 +32,9 @@ #include "qwaylandquicksurface.h" #include "qwaylandinputmethodcontrol.h" #include "qwaylandtextinput.h" +#if QT_WAYLAND_TEXT_INPUT_V4_WIP +#include "qwaylandtextinputv4.h" +#endif // QT_WAYLAND_TEXT_INPUT_V4_WIP #include "qwaylandqttextinputmethod.h" #include "qwaylandquickoutput.h" #include <QtWaylandCompositor/qwaylandcompositor.h> @@ -1129,6 +1132,14 @@ void QWaylandQuickItem::takeFocus(QWaylandSeat *device) textInput->setFocus(surface()); } +#if QT_WAYLAND_TEXT_INPUT_V4_WIP + if (surface()->client()->textInputProtocols().testFlag(QWaylandClient::TextInputProtocol::TextInputV4)) { + QWaylandTextInputV4 *textInputV4 = QWaylandTextInputV4::findIn(target); + if (textInputV4) + textInputV4->setFocus(surface()); + } +#endif // QT_WAYLAND_TEXT_INPUT_V4_WIP + if (surface()->client()->textInputProtocols().testFlag(QWaylandClient::TextInputProtocol::QtTextInputMethodV1)) { QWaylandQtTextInputMethod *textInputMethod = QWaylandQtTextInputMethod::findIn(target); if (textInputMethod) diff --git a/src/compositor/compositor_api/qwaylandseat.cpp b/src/compositor/compositor_api/qwaylandseat.cpp index 9f607c5d..54538d1d 100644 --- a/src/compositor/compositor_api/qwaylandseat.cpp +++ b/src/compositor/compositor_api/qwaylandseat.cpp @@ -48,6 +48,9 @@ #include "extensions/qwlqtkey_p.h" #include "extensions/qwaylandtextinput.h" +#if QT_WAYLAND_TEXT_INPUT_V4_WIP +#include "extensions/qwaylandtextinputv4.h" +#endif // QT_WAYLAND_TEXT_INPUT_V4_WIP #include "extensions/qwaylandqttextinputmethod.h" QT_BEGIN_NAMESPACE @@ -484,6 +487,18 @@ void QWaylandSeat::sendFullKeyEvent(QKeyEvent *event) return; } } + +#if QT_WAYLAND_TEXT_INPUT_V4_WIP + if (keyboardFocus()->client()->textInputProtocols().testFlag(QWaylandClient::TextInputProtocol::TextInputV4)) { + QWaylandTextInputV4 *textInputV4 = QWaylandTextInputV4::findIn(this); + if (textInputV4 && !event->text().isEmpty()) { + // it will just commit the text for text-input-unstable-v4-wip when keyPress + if (event->type() == QEvent::KeyPress) + textInputV4->sendKeyEvent(event); + return; + } + } +#endif // QT_WAYLAND_TEXT_INPUT_V4_WIP } #endif diff --git a/src/compositor/extensions/qwaylandtextinputmanagerv4.cpp b/src/compositor/extensions/qwaylandtextinputmanagerv4.cpp new file mode 100644 index 00000000..09d70c0c --- /dev/null +++ b/src/compositor/extensions/qwaylandtextinputmanagerv4.cpp @@ -0,0 +1,143 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins 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 "qwaylandtextinputmanagerv4.h" +#include "qwaylandtextinputmanagerv4_p.h" + +#include <QtWaylandCompositor/QWaylandCompositor> +#include <QtWaylandCompositor/QWaylandSeat> + +#include "qwaylandtextinputv4.h" + +QT_BEGIN_NAMESPACE + +QWaylandTextInputManagerV4Private::QWaylandTextInputManagerV4Private() +{ +} + +void QWaylandTextInputManagerV4Private::zwp_text_input_manager_v4_get_text_input(Resource *resource, uint32_t id, struct ::wl_resource *seatResource) +{ + qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO; + + Q_Q(QWaylandTextInputManagerV4); + QWaylandCompositor *compositor = static_cast<QWaylandCompositor *>(q->extensionContainer()); + QWaylandSeat *seat = QWaylandSeat::fromSeatResource(seatResource); + QWaylandTextInputV4 *textInput = QWaylandTextInputV4::findIn(seat); + if (!textInput) { + textInput = new QWaylandTextInputV4(seat, compositor); + } + textInput->add(resource->client(), id, wl_resource_get_version(resource->handle)); + QWaylandClient *client = QWaylandClient::fromWlClient(compositor, resource->client()); + QWaylandClient::TextInputProtocols p = client->textInputProtocols(); + client->setTextInputProtocols(p.setFlag(QWaylandClient::TextInputProtocol::TextInputV4)); + if (!textInput->isInitialized()) + textInput->initialize(); +} + +/*! + \internal + \preliminary + + \qmltype TextInputManagerV4 + \instantiates QWaylandTextInputManagerV4 + \inqmlmodule QtWayland.Compositor + \brief Provides access to input methods in the compositor. + + The \c TextInputManagerV4 corresponds to the \c zwp_text_input_manager_v4 interface + in the \c text_input_unstable_v4 extension protocol. + + Instantiating this as child of a \l WaylandCompositor adds it to the list of interfaces available + to the client. If a client binds to it, then it will be used to communciate text input to + that client. + + \note This protocol is currently a work-in-progress and only exists in Qt for validation purposes. It may change at any time. +*/ + +/*! + \internal + \preliminary + \class QWaylandTextInputManagerV4 + \inmodule QtWaylandCompositor + \brief Provides access to input methods in the compositor. + + The \c QWaylandTextInputManagerV4 corresponds to the \c zwp_text_input_manager_v4 interface + in the \c text_input_unstable_v4 extension protocol. + + Instantiating this as child of a \l WaylandCompositor adds it to the list of interfaces available + to the client. If a client binds to it, then it will be used to communciate text input to + that client. + \note This protocol is currently a work-in-progress and only exists in Qt for validation purposes. It may change at any time. +*/ + +QWaylandTextInputManagerV4::QWaylandTextInputManagerV4() + : QWaylandCompositorExtensionTemplate<QWaylandTextInputManagerV4>(*new QWaylandTextInputManagerV4Private) +{ +} + +QWaylandTextInputManagerV4::QWaylandTextInputManagerV4(QWaylandCompositor *compositor) + : QWaylandCompositorExtensionTemplate<QWaylandTextInputManagerV4>(compositor, *new QWaylandTextInputManagerV4Private) +{ +} + +void QWaylandTextInputManagerV4::initialize() +{ + qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO; + + Q_D(QWaylandTextInputManagerV4); + + QWaylandCompositorExtensionTemplate::initialize(); + QWaylandCompositor *compositor = static_cast<QWaylandCompositor *>(extensionContainer()); + if (!compositor) { + qWarning() << "Failed to find QWaylandCompositor when initializing QWaylandTextInputManagerV4"; + return; + } + d->init(compositor->display(), 1); +} + +const wl_interface *QWaylandTextInputManagerV4::interface() +{ + return QWaylandTextInputManagerV4Private::interface(); +} + +QByteArray QWaylandTextInputManagerV4::interfaceName() +{ + return QWaylandTextInputManagerV4Private::interfaceName(); +} + +QT_END_NAMESPACE diff --git a/src/compositor/extensions/qwaylandtextinputmanagerv4.h b/src/compositor/extensions/qwaylandtextinputmanagerv4.h new file mode 100644 index 00000000..f004648c --- /dev/null +++ b/src/compositor/extensions/qwaylandtextinputmanagerv4.h @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins 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 QWAYLANDTEXTINPUTMANAGERV4_H +#define QWAYLANDTEXTINPUTMANAGERV4_H + +#include <QtWaylandCompositor/QWaylandCompositorExtension> + +#include <QtCore/QSize> + +QT_BEGIN_NAMESPACE + +class QWaylandTextInputManagerV4Private; + +class Q_WAYLAND_COMPOSITOR_EXPORT QWaylandTextInputManagerV4 : public QWaylandCompositorExtensionTemplate<QWaylandTextInputManagerV4> +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QWaylandTextInputManagerV4) +public: + QWaylandTextInputManagerV4(); + QWaylandTextInputManagerV4(QWaylandCompositor *compositor); + + void initialize() override; + + static const struct wl_interface *interface(); + static QByteArray interfaceName(); +}; + +QT_END_NAMESPACE + +#endif // QWAYLANDTEXTINPUTMANAGERV4_H diff --git a/src/compositor/extensions/qwaylandtextinputmanagerv4_p.h b/src/compositor/extensions/qwaylandtextinputmanagerv4_p.h new file mode 100644 index 00000000..e0d65afe --- /dev/null +++ b/src/compositor/extensions/qwaylandtextinputmanagerv4_p.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins 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 QWAYLANDTEXTINPUTMANAGERV4_P_H +#define QWAYLANDTEXTINPUTMANAGERV4_P_H + +#include <QtWaylandCompositor/private/qwaylandcompositorextension_p.h> + +#include <QtWaylandCompositor/private/qwayland-server-text-input-unstable-v4-wip.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. +// + +QT_BEGIN_NAMESPACE + +class Q_WAYLAND_COMPOSITOR_EXPORT QWaylandTextInputManagerV4Private : public QWaylandCompositorExtensionPrivate, public QtWaylandServer::zwp_text_input_manager_v4 +{ + Q_DECLARE_PUBLIC(QWaylandTextInputManagerV4) +public: + QWaylandTextInputManagerV4Private(); + +protected: + void zwp_text_input_manager_v4_get_text_input(Resource *resource, uint32_t id, struct ::wl_resource *seatResource) override; +}; + +QT_END_NAMESPACE + +#endif // QWAYLANDTEXTINPUTMANAGERV4_P_H diff --git a/src/compositor/extensions/qwaylandtextinputv4.cpp b/src/compositor/extensions/qwaylandtextinputv4.cpp new file mode 100644 index 00000000..fa25ba01 --- /dev/null +++ b/src/compositor/extensions/qwaylandtextinputv4.cpp @@ -0,0 +1,552 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins 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 "qwaylandtextinputv4.h" +#include "qwaylandtextinputv4_p.h" + +#include <QtWaylandCompositor/QWaylandCompositor> +#include <QtWaylandCompositor/private/qwaylandseat_p.h> + +#include "qwaylandsurface.h" +#include "qwaylandview.h" +#include "qwaylandinputmethodeventbuilder_p.h" + +#include <QGuiApplication> +#include <QInputMethodEvent> +#include <qpa/qwindowsysteminterface.h> + +#if QT_CONFIG(xkbcommon) +#include <QtGui/private/qxkbcommon_p.h> +#endif + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(qLcWaylandCompositorTextInput) + +QWaylandTextInputV4ClientState::QWaylandTextInputV4ClientState() +{ +} + +Qt::InputMethodQueries QWaylandTextInputV4ClientState::updatedQueries(const QWaylandTextInputV4ClientState &other) const +{ + qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO; + + Qt::InputMethodQueries queries; + + if (hints != other.hints) + queries |= Qt::ImHints; + if (cursorRectangle != other.cursorRectangle) + queries |= Qt::ImCursorRectangle; + if (surroundingText != other.surroundingText) + queries |= Qt::ImSurroundingText | Qt::ImCurrentSelection; + if (cursorPosition != other.cursorPosition) + queries |= Qt::ImCursorPosition | Qt::ImCurrentSelection; + if (anchorPosition != other.anchorPosition) + queries |= Qt::ImAnchorPosition | Qt::ImCurrentSelection; + + return queries; +} + +Qt::InputMethodQueries QWaylandTextInputV4ClientState::mergeChanged(const QWaylandTextInputV4ClientState &other) { + + Qt::InputMethodQueries queries; + + if ((other.changedState & Qt::ImHints) && hints != other.hints) { + hints = other.hints; + queries |= Qt::ImHints; + } + + if ((other.changedState & Qt::ImCursorRectangle) && cursorRectangle != other.cursorRectangle) { + cursorRectangle = other.cursorRectangle; + queries |= Qt::ImCursorRectangle; + } + + if ((other.changedState & Qt::ImSurroundingText) && surroundingText != other.surroundingText) { + surroundingText = other.surroundingText; + queries |= Qt::ImSurroundingText | Qt::ImCurrentSelection; + } + + if ((other.changedState & Qt::ImCursorPosition) && cursorPosition != other.cursorPosition) { + cursorPosition = other.cursorPosition; + queries |= Qt::ImCursorPosition | Qt::ImCurrentSelection; + } + + if ((other.changedState & Qt::ImAnchorPosition) && anchorPosition != other.anchorPosition) { + anchorPosition = other.anchorPosition; + queries |= Qt::ImAnchorPosition | Qt::ImCurrentSelection; + } + + return queries; +} + +QWaylandTextInputV4Private::QWaylandTextInputV4Private(QWaylandCompositor *compositor) + : compositor(compositor) + , currentState(new QWaylandTextInputV4ClientState) + , pendingState(new QWaylandTextInputV4ClientState) +{ +} + +void QWaylandTextInputV4Private::sendInputMethodEvent(QInputMethodEvent *event) +{ + Q_Q(QWaylandTextInputV4); + qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO; + + if (!focusResource || !focusResource->handle) + return; + + bool needsDone = false; + + const QString &newPreeditString = event->preeditString(); + + // Current cursor shape is only line. It means both cursorBegin + // and cursorEnd will be the same values. + int32_t preeditCursorPos = newPreeditString.length(); + + if (event->replacementLength() > 0 || event->replacementStart() < 0) { + if (event->replacementStart() <= 0 && (event->replacementLength() >= -event->replacementStart())) { + const int selectionStart = qMin(currentState->cursorPosition, currentState->anchorPosition); + const int selectionEnd = qMax(currentState->cursorPosition, currentState->anchorPosition); + const int before = QWaylandInputMethodEventBuilder::indexToWayland(currentState->surroundingText, -event->replacementStart(), selectionStart + event->replacementStart()); + const int after = QWaylandInputMethodEventBuilder::indexToWayland(currentState->surroundingText, event->replacementLength() + event->replacementStart(), selectionEnd); + send_delete_surrounding_text(focusResource->handle, before, after); + needsDone = true; + } else { + qCWarning(qLcWaylandCompositorTextInput) << "Not yet supported case of replacement. Start:" << event->replacementStart() << "length:" << event->replacementLength(); + } + preeditCursorPos = event->replacementStart() + event->replacementLength(); + } + + if (currentPreeditString != newPreeditString) { + currentPreeditString = newPreeditString; + send_preedit_string(focusResource->handle, currentPreeditString, preeditCursorPos, preeditCursorPos); + needsDone = true; + } + if (!event->commitString().isEmpty()) { + send_commit_string(focusResource->handle, event->commitString()); + needsDone = true; + } + + if (needsDone) + send_done(focusResource->handle, serial); +} + + +void QWaylandTextInputV4Private::sendKeyEvent(QKeyEvent *event) +{ + qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO; + + Q_Q(QWaylandTextInputV4); + + if (!focusResource || !focusResource->handle) + return; + + send_commit_string(focusResource->handle, event->text()); + + send_done(focusResource->handle, serial); +} + +QVariant QWaylandTextInputV4Private::inputMethodQuery(Qt::InputMethodQuery property, QVariant argument) const +{ + qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO << property; + + switch (property) { + case Qt::ImHints: + return QVariant(static_cast<int>(currentState->hints)); + case Qt::ImCursorRectangle: + return currentState->cursorRectangle; + case Qt::ImFont: + // Not supported + return QVariant(); + case Qt::ImCursorPosition: + qCDebug(qLcWaylandCompositorTextInput) << currentState->cursorPosition; + return currentState->cursorPosition; + case Qt::ImSurroundingText: + qCDebug(qLcWaylandCompositorTextInput) << currentState->surroundingText; + return currentState->surroundingText; + case Qt::ImCurrentSelection: + return currentState->surroundingText.mid(qMin(currentState->cursorPosition, currentState->anchorPosition), + qAbs(currentState->anchorPosition - currentState->cursorPosition)); + case Qt::ImMaximumTextLength: + // Not supported + return QVariant(); + case Qt::ImAnchorPosition: + qCDebug(qLcWaylandCompositorTextInput) << currentState->anchorPosition; + return currentState->anchorPosition; + case Qt::ImAbsolutePosition: + // We assume the surrounding text is our whole document for now + return currentState->cursorPosition; + case Qt::ImTextAfterCursor: + if (argument.isValid()) + return currentState->surroundingText.mid(currentState->cursorPosition, argument.toInt()); + return currentState->surroundingText.mid(currentState->cursorPosition); + case Qt::ImTextBeforeCursor: + if (argument.isValid()) + return currentState->surroundingText.left(currentState->cursorPosition).right(argument.toInt()); + return currentState->surroundingText.left(currentState->cursorPosition); + + default: + return QVariant(); + } +} + +void QWaylandTextInputV4Private::setFocus(QWaylandSurface *surface) +{ + qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO; + Q_Q(QWaylandTextInputV4); + + if (focusResource && focus) { + // sync before leave + // IBUS commits by itself but qtvirtualkeyboard doesn't + // And when handling chinese input, it is required to commit + // before leaving the focus. + if (qgetenv("QT_IM_MODULE") != QByteArrayLiteral("ibus") + || qApp->inputMethod()->locale().language() == QLocale::Chinese) { + qApp->inputMethod()->commit(); + } + + qApp->inputMethod()->hide(); + inputPanelVisible = false; + send_leave(focusResource->handle, focus->resource()); + currentPreeditString.clear(); + } + + if (focus != surface) + focusDestroyListener.reset(); + + Resource *resource = surface ? resourceMap().value(surface->waylandClient()) : 0; + if (resource && surface) { + send_enter(resource->handle, surface->resource()); + + if (focus != surface) + focusDestroyListener.listenForDestruction(surface->resource()); + } + + focus = surface; + focusResource = resource; +} + +void QWaylandTextInputV4Private::zwp_text_input_v4_bind_resource(Resource *resource) +{ + qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO; + + Q_UNUSED(resource); +} + +void QWaylandTextInputV4Private::zwp_text_input_v4_destroy_resource(Resource *resource) +{ + qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO; + + if (focusResource == resource) + focusResource = nullptr; +} + +void QWaylandTextInputV4Private::zwp_text_input_v4_destroy(Resource *resource) +{ + qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO; + + wl_resource_destroy(resource->handle); +} + +void QWaylandTextInputV4Private::zwp_text_input_v4_enable(Resource *resource) +{ + qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO; + + Q_Q(QWaylandTextInputV4); + + pendingState.reset(new QWaylandTextInputV4ClientState); + + enabledSurfaces.insert(resource, focus); + emit q->surfaceEnabled(focus); + + serial = 0; + inputPanelVisible = true; + qApp->inputMethod()->show(); +} + +void QWaylandTextInputV4Private::zwp_text_input_v4_disable(QtWaylandServer::zwp_text_input_v4::Resource *resource) +{ + qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO; + + Q_Q(QWaylandTextInputV4); + + QWaylandSurface *s = enabledSurfaces.take(resource); + emit q->surfaceDisabled(s); + + // When reselecting a word by setFocus + if (qgetenv("QT_IM_MODULE") != QByteArrayLiteral("ibus") + || qApp->inputMethod()->locale().language() == QLocale::Chinese) { + qApp->inputMethod()->commit(); + } + qApp->inputMethod()->reset(); + pendingState.reset(new QWaylandTextInputV4ClientState); +} + +void QWaylandTextInputV4Private::zwp_text_input_v4_set_cursor_rectangle(Resource *resource, int32_t x, int32_t y, int32_t width, int32_t height) +{ + qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO << x << y << width << height; + + Q_Q(QWaylandTextInputV4); + + if (resource != focusResource) + return; + + pendingState->cursorRectangle = QRect(x, y, width, height); + + pendingState->changedState |= Qt::ImCursorRectangle; +} + +void QWaylandTextInputV4Private::zwp_text_input_v4_commit(Resource *resource) +{ + qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO; + + Q_Q(QWaylandTextInputV4); + + if (resource != focusResource) { + qCDebug(qLcWaylandCompositorTextInput) << "OBS: Disabled surface!!"; + return; + } + + serial = serial < UINT_MAX ? serial + 1U : 0U; + + // Just increase serials and ignore empty commits + if (!pendingState->changedState) { + qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO << "pendingState is not changed"; + return; + } + + // Selection starts. + // But since qtvirtualkeyboard with hunspell does not reset its preedit string, + // compositor forces to reset inputMethod. + if ((currentState->cursorPosition == currentState->anchorPosition) + && (pendingState->cursorPosition != pendingState->anchorPosition)) + qApp->inputMethod()->reset(); + + // Enable reselection + // This is a workaround to make qtvirtualkeyboad's state empty by clearing State::InputMethodClick. + if (currentState->surroundingText == pendingState->surroundingText && currentState->cursorPosition != pendingState->cursorPosition) + qApp->inputMethod()->invokeAction(QInputMethod::Click, pendingState->cursorPosition); + + Qt::InputMethodQueries queries = currentState->mergeChanged(*pendingState.data()); + pendingState.reset(new QWaylandTextInputV4ClientState); + + if (queries) { + qCDebug(qLcWaylandCompositorTextInput) << "QInputMethod::update() after commit with" << queries; + + qApp->inputMethod()->update(queries); + } +} + +void QWaylandTextInputV4Private::zwp_text_input_v4_set_content_type(Resource *resource, uint32_t hint, uint32_t purpose) +{ + qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO << hint << purpose; + + if (resource != focusResource) + return; + + pendingState->hints = Qt::ImhNone; + + if ((hint & content_hint_completion) == 0) + pendingState->hints |= Qt::ImhNoPredictiveText; + if ((hint & content_hint_auto_capitalization) == 0) + pendingState->hints |= Qt::ImhNoAutoUppercase; + if ((hint & content_hint_lowercase) != 0) + pendingState->hints |= Qt::ImhPreferLowercase; + if ((hint & content_hint_uppercase) != 0) + pendingState->hints |= Qt::ImhPreferUppercase; + if ((hint & content_hint_hidden_text) != 0) + pendingState->hints |= Qt::ImhHiddenText; + if ((hint & content_hint_sensitive_data) != 0) + pendingState->hints |= Qt::ImhSensitiveData; + if ((hint & content_hint_latin) != 0) + pendingState->hints |= Qt::ImhLatinOnly; + if ((hint & content_hint_multiline) != 0) + pendingState->hints |= Qt::ImhMultiLine; + + switch (purpose) { + case content_purpose_normal: + break; + case content_purpose_alpha: + pendingState->hints |= Qt::ImhUppercaseOnly | Qt::ImhLowercaseOnly; + break; + case content_purpose_digits: + pendingState->hints |= Qt::ImhDigitsOnly; + break; + case content_purpose_number: + pendingState->hints |= Qt::ImhFormattedNumbersOnly; + break; + case content_purpose_phone: + pendingState->hints |= Qt::ImhDialableCharactersOnly; + break; + case content_purpose_url: + pendingState->hints |= Qt::ImhUrlCharactersOnly; + break; + case content_purpose_email: + pendingState->hints |= Qt::ImhEmailCharactersOnly; + break; + case content_purpose_name: + case content_purpose_password: + break; + case content_purpose_date: + pendingState->hints |= Qt::ImhDate; + break; + case content_purpose_time: + pendingState->hints |= Qt::ImhTime; + break; + case content_purpose_datetime: + pendingState->hints |= Qt::ImhDate | Qt::ImhTime; + break; + case content_purpose_terminal: + default: + break; + } + + qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO << pendingState->hints; + + pendingState->changedState |= Qt::ImHints; +} + +void QWaylandTextInputV4Private::zwp_text_input_v4_set_surrounding_text(Resource *resource, const QString &text, int32_t cursor, int32_t anchor) +{ + qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO << text << cursor << anchor; + + if (resource != focusResource) + return; + + pendingState->surroundingText = text; + pendingState->cursorPosition = QWaylandInputMethodEventBuilder::indexFromWayland(text, cursor); + pendingState->anchorPosition = QWaylandInputMethodEventBuilder::indexFromWayland(text, anchor); + + pendingState->changedState |= Qt::ImSurroundingText | Qt::ImCursorPosition | Qt::ImAnchorPosition; +} + +void QWaylandTextInputV4Private::zwp_text_input_v4_set_text_change_cause(Resource *resource, uint32_t cause) +{ + qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO; + + Q_UNUSED(resource); + Q_UNUSED(cause); +} + +QWaylandTextInputV4::QWaylandTextInputV4(QWaylandObject *container, QWaylandCompositor *compositor) + : QWaylandCompositorExtensionTemplate(container, *new QWaylandTextInputV4Private(compositor)) +{ + connect(&d_func()->focusDestroyListener, &QWaylandDestroyListener::fired, + this, &QWaylandTextInputV4::focusSurfaceDestroyed); +} + +QWaylandTextInputV4::~QWaylandTextInputV4() +{ +} + +void QWaylandTextInputV4::sendInputMethodEvent(QInputMethodEvent *event) +{ + Q_D(QWaylandTextInputV4); + + d->sendInputMethodEvent(event); +} + +void QWaylandTextInputV4::sendKeyEvent(QKeyEvent *event) +{ + Q_D(QWaylandTextInputV4); + + d->sendKeyEvent(event); +} + +QVariant QWaylandTextInputV4::inputMethodQuery(Qt::InputMethodQuery property, QVariant argument) const +{ + const Q_D(QWaylandTextInputV4); + + return d->inputMethodQuery(property, argument); +} + +QWaylandSurface *QWaylandTextInputV4::focus() const +{ + const Q_D(QWaylandTextInputV4); + + return d->focus; +} + +void QWaylandTextInputV4::setFocus(QWaylandSurface *surface) +{ + Q_D(QWaylandTextInputV4); + + d->setFocus(surface); +} + +void QWaylandTextInputV4::focusSurfaceDestroyed(void *) +{ + qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO; + + Q_D(QWaylandTextInputV4); + + d->focusDestroyListener.reset(); + + d->focus = nullptr; + d->focusResource = nullptr; +} + +bool QWaylandTextInputV4::isSurfaceEnabled(QWaylandSurface *surface) const +{ + qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO; + + const Q_D(QWaylandTextInputV4); + + return d->enabledSurfaces.values().contains(surface); +} + +void QWaylandTextInputV4::add(::wl_client *client, uint32_t id, int version) +{ + qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO; + + Q_D(QWaylandTextInputV4); + + d->add(client, id, version); +} + +const wl_interface *QWaylandTextInputV4::interface() +{ + return QWaylandTextInputV4Private::interface(); +} + +QByteArray QWaylandTextInputV4::interfaceName() +{ + return QWaylandTextInputV4Private::interfaceName(); +} + +QT_END_NAMESPACE diff --git a/src/compositor/extensions/qwaylandtextinputv4.h b/src/compositor/extensions/qwaylandtextinputv4.h new file mode 100644 index 00000000..ffb556cb --- /dev/null +++ b/src/compositor/extensions/qwaylandtextinputv4.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins 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 QWAYLANDTEXTINPUTV4_H +#define QWAYLANDTEXTINPUTV4_H + +#include <QtWaylandCompositor/QWaylandCompositorExtension> + +struct wl_client; + +QT_BEGIN_NAMESPACE + +class QWaylandTextInputV4Private; + +class QInputMethodEvent; +class QKeyEvent; +class QWaylandSurface; + +class QWaylandTextInputV4 : public QWaylandCompositorExtensionTemplate<QWaylandTextInputV4> +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QWaylandTextInputV4) +public: + explicit QWaylandTextInputV4(QWaylandObject *container, QWaylandCompositor *compositor); + ~QWaylandTextInputV4() override; + + void sendInputMethodEvent(QInputMethodEvent *event); + void sendKeyEvent(QKeyEvent *event); + + QVariant inputMethodQuery(Qt::InputMethodQuery property, QVariant argument) const; + + QWaylandSurface *focus() const; + void setFocus(QWaylandSurface *surface); + + bool isSurfaceEnabled(QWaylandSurface *surface) const; + + void add(::wl_client *client, uint32_t id, int version); + static const struct wl_interface *interface(); + static QByteArray interfaceName(); + +Q_SIGNALS: + void updateInputMethod(Qt::InputMethodQueries queries); + void surfaceEnabled(QWaylandSurface *surface); + void surfaceDisabled(QWaylandSurface *surface); + +private: + void focusSurfaceDestroyed(void *); +}; + +QT_END_NAMESPACE + +#endif // QWAYLANDTEXTINPUTV4_H diff --git a/src/compositor/extensions/qwaylandtextinputv4_p.h b/src/compositor/extensions/qwaylandtextinputv4_p.h new file mode 100644 index 00000000..7db6ec63 --- /dev/null +++ b/src/compositor/extensions/qwaylandtextinputv4_p.h @@ -0,0 +1,132 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins 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 QWAYLANDTEXTINPUTV4_P_H +#define QWAYLANDTEXTINPUTV4_P_H + +#include <QtWaylandCompositor/private/qwaylandcompositorextension_p.h> +#include <QtWaylandCompositor/private/qwayland-server-text-input-unstable-v4-wip.h> +#include <QtWaylandCompositor/QWaylandDestroyListener> + +#include <QtCore/QObject> +#include <QtCore/QRect> +#include <QtGui/QInputMethod> +#include <QtWaylandCompositor/QWaylandSurface> + +// +// 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. +// + +QT_BEGIN_NAMESPACE + +class QInputMethodEvent; +class QKeyEvent; +class QWaylandCompositor; +class QWaylandView; + +class QWaylandTextInputV4ClientState { +public: + QWaylandTextInputV4ClientState(); + + Qt::InputMethodQueries updatedQueries(const QWaylandTextInputV4ClientState &other) const; + Qt::InputMethodQueries mergeChanged(const QWaylandTextInputV4ClientState &other); + + Qt::InputMethodHints hints = Qt::ImhNone; + QRect cursorRectangle; + QString surroundingText; + int cursorPosition = 0; + int anchorPosition = 0; + + Qt::InputMethodQueries changedState; +}; + +class Q_WAYLAND_COMPOSITOR_EXPORT QWaylandTextInputV4Private : public QWaylandCompositorExtensionPrivate, public QtWaylandServer::zwp_text_input_v4 +{ + Q_DECLARE_PUBLIC(QWaylandTextInputV4) +public: + explicit QWaylandTextInputV4Private(QWaylandCompositor *compositor); + + void sendInputMethodEvent(QInputMethodEvent *event); + void sendKeyEvent(QKeyEvent *event); + + QVariant inputMethodQuery(Qt::InputMethodQuery property, QVariant argument) const; + + void setFocus(QWaylandSurface *surface); + + QWaylandCompositor *compositor = nullptr; + + QWaylandSurface *focus = nullptr; + Resource *focusResource = nullptr; + QWaylandDestroyListener focusDestroyListener; + + bool inputPanelVisible = false; + + QString currentPreeditString; + + QScopedPointer<QWaylandTextInputV4ClientState> currentState; + QScopedPointer<QWaylandTextInputV4ClientState> pendingState; + + uint32_t serial = 0; + + QHash<Resource *, QWaylandSurface*> enabledSurfaces; + +protected: + void zwp_text_input_v4_bind_resource(Resource *resource) override; + void zwp_text_input_v4_destroy_resource(Resource *resource) override; + + void zwp_text_input_v4_destroy(Resource *resource) override; + void zwp_text_input_v4_enable(Resource *resource) override; + void zwp_text_input_v4_disable(Resource *resource) override; + void zwp_text_input_v4_set_surrounding_text(Resource *resource, const QString &text, int32_t cursor, int32_t anchor) override; + void zwp_text_input_v4_set_text_change_cause(Resource *resource, uint32_t cause) override; + void zwp_text_input_v4_set_content_type(Resource *resource, uint32_t hint, uint32_t purpose) override; + void zwp_text_input_v4_set_cursor_rectangle(Resource *resource, int32_t x, int32_t y, int32_t width, int32_t height) override; + void zwp_text_input_v4_commit(Resource *resource) override; +}; + +QT_END_NAMESPACE + +#endif // QWAYLANDTEXTINPUTV4_P_H diff --git a/src/configure.cmake b/src/configure.cmake index aaa426e1..ce7fcf59 100644 --- a/src/configure.cmake +++ b/src/configure.cmake @@ -1,6 +1,7 @@ # configure.cmake for the QtWaylandGlobalPrivate module #### Inputs +set(INPUT_wayland_text_input_v4_wip OFF CACHE BOOL "") @@ -233,7 +234,12 @@ qt_feature("xcomposite-glx" PRIVATE AND QT_FEATURE_opengl AND NOT QT_FEATURE_opengles2 AND QT_FEATURE_xlib AND XComposite_FOUND ) +qt_feature("wayland-text-input-v4-wip" PRIVATE + LABEL "Qt Wayland TextInput Protocol V4(WIP)" + PURPOSE "Enables wayland_text_input_unstable_v4(wip)" +) +qt_configure_add_summary_entry(ARGS "wayland-text-input-v4-wip") qt_configure_add_summary_entry(ARGS "wayland-client") qt_configure_add_summary_entry(ARGS "wayland-server") qt_configure_add_summary_section(NAME "Qt Wayland Drivers") diff --git a/src/qt_cmdline.cmake b/src/qt_cmdline.cmake new file mode 100644 index 00000000..0921d53e --- /dev/null +++ b/src/qt_cmdline.cmake @@ -0,0 +1 @@ +qt_commandline_option(wayland-text-input-v4-wip TYPE boolean) diff --git a/src/shared/qwaylandinputmethodeventbuilder.cpp b/src/shared/qwaylandinputmethodeventbuilder.cpp index 00e3ae14..cfc4a99f 100644 --- a/src/shared/qwaylandinputmethodeventbuilder.cpp +++ b/src/shared/qwaylandinputmethodeventbuilder.cpp @@ -44,8 +44,10 @@ #ifdef QT_BUILD_WAYLANDCOMPOSITOR_LIB #include <QtWaylandCompositor/private/qwayland-server-text-input-unstable-v2.h> +#include <QtWaylandCompositor/private/qwayland-server-text-input-unstable-v4-wip.h> #else #include <QtWaylandClient/private/qwayland-text-input-unstable-v2.h> +#include <QtWaylandClient/private/qwayland-text-input-unstable-v4-wip.h> #endif QT_BEGIN_NAMESPACE @@ -194,68 +196,105 @@ QWaylandInputMethodContentType QWaylandInputMethodContentType::convert(Qt::Input uint32_t hint = ZWP_TEXT_INPUT_V2_CONTENT_HINT_NONE; uint32_t purpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_NORMAL; - if (hints & Qt::ImhHiddenText) { + if (hints & Qt::ImhHiddenText) hint |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_HIDDEN_TEXT; - } - if (hints & Qt::ImhSensitiveData) { + if (hints & Qt::ImhSensitiveData) hint |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_SENSITIVE_DATA; - } - if ((hints & Qt::ImhNoAutoUppercase) == 0) { + if ((hints & Qt::ImhNoAutoUppercase) == 0) hint |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_AUTO_CAPITALIZATION; - } if (hints & Qt::ImhPreferNumbers) { // Nothing yet } - if (hints & Qt::ImhPreferUppercase) { + if (hints & Qt::ImhPreferUppercase) hint |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_UPPERCASE; - } - if (hints & Qt::ImhPreferLowercase) { + if (hints & Qt::ImhPreferLowercase) hint |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_LOWERCASE; - } if ((hints & Qt::ImhNoPredictiveText) == 0) { - hint |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_AUTO_COMPLETION | ZWP_TEXT_INPUT_V2_CONTENT_HINT_AUTO_CORRECTION; + hint |= (ZWP_TEXT_INPUT_V2_CONTENT_HINT_AUTO_COMPLETION + | ZWP_TEXT_INPUT_V2_CONTENT_HINT_AUTO_CORRECTION); } - if ((hints & Qt::ImhDate) && (hints & Qt::ImhTime) == 0) { + if ((hints & Qt::ImhDate) && (hints & Qt::ImhTime) == 0) purpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_DATE; - } else if ((hints & Qt::ImhDate) && (hints & Qt::ImhTime)) { + else if ((hints & Qt::ImhDate) && (hints & Qt::ImhTime)) purpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_DATETIME; - } else if ((hints & Qt::ImhDate) == 0 && (hints & Qt::ImhTime)) { + else if ((hints & Qt::ImhDate) == 0 && (hints & Qt::ImhTime)) purpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_TIME; - } - if (hints & Qt::ImhPreferLatin) { + if (hints & Qt::ImhPreferLatin) hint |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_LATIN; - } - - if (hints & Qt::ImhMultiLine) { + if (hints & Qt::ImhMultiLine) hint |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_MULTILINE; - } - - if (hints & Qt::ImhDigitsOnly) { + if (hints & Qt::ImhDigitsOnly) purpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_DIGITS; - } - if (hints & Qt::ImhFormattedNumbersOnly) { + if (hints & Qt::ImhFormattedNumbersOnly) purpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_NUMBER; - } - if (hints & Qt::ImhUppercaseOnly) { + if (hints & Qt::ImhUppercaseOnly) hint |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_UPPERCASE; - } - if (hints & Qt::ImhLowercaseOnly) { + if (hints & Qt::ImhLowercaseOnly) hint |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_LOWERCASE; - } - if (hints & Qt::ImhDialableCharactersOnly) { + if (hints & Qt::ImhDialableCharactersOnly) purpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_PHONE; - } - if (hints & Qt::ImhEmailCharactersOnly) { + if (hints & Qt::ImhEmailCharactersOnly) purpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_EMAIL; - } - if (hints & Qt::ImhUrlCharactersOnly) { + if (hints & Qt::ImhUrlCharactersOnly) purpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_URL; - } - if (hints & Qt::ImhLatinOnly) { + if (hints & Qt::ImhLatinOnly) hint |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_LATIN; + + return QWaylandInputMethodContentType{hint, purpose}; +} + +QWaylandInputMethodContentType QWaylandInputMethodContentType::convertV4(Qt::InputMethodHints hints) +{ + uint32_t hint = ZWP_TEXT_INPUT_V4_CONTENT_HINT_NONE; + uint32_t purpose = ZWP_TEXT_INPUT_V4_CONTENT_PURPOSE_NORMAL; + + if (hints & Qt::ImhHiddenText) + hint |= ZWP_TEXT_INPUT_V4_CONTENT_HINT_HIDDEN_TEXT; + if (hints & Qt::ImhSensitiveData) + hint |= ZWP_TEXT_INPUT_V4_CONTENT_HINT_SENSITIVE_DATA; + if ((hints & Qt::ImhNoAutoUppercase) == 0) + hint |= ZWP_TEXT_INPUT_V4_CONTENT_HINT_AUTO_CAPITALIZATION; + if (hints & Qt::ImhPreferNumbers) { + // Nothing yet + } + if (hints & Qt::ImhPreferUppercase) + hint |= ZWP_TEXT_INPUT_V4_CONTENT_HINT_UPPERCASE; + if (hints & Qt::ImhPreferLowercase) + hint |= ZWP_TEXT_INPUT_V4_CONTENT_HINT_LOWERCASE; + if ((hints & Qt::ImhNoPredictiveText) == 0) { + hint |= (ZWP_TEXT_INPUT_V4_CONTENT_HINT_COMPLETION + | ZWP_TEXT_INPUT_V4_CONTENT_HINT_SPELLCHECK); } + + if ((hints & Qt::ImhDate) && (hints & Qt::ImhTime) == 0) + purpose = ZWP_TEXT_INPUT_V4_CONTENT_PURPOSE_DATE; + else if ((hints & Qt::ImhDate) && (hints & Qt::ImhTime)) + purpose = ZWP_TEXT_INPUT_V4_CONTENT_PURPOSE_DATETIME; + else if ((hints & Qt::ImhDate) == 0 && (hints & Qt::ImhTime)) + purpose = ZWP_TEXT_INPUT_V4_CONTENT_PURPOSE_TIME; + if (hints & Qt::ImhPreferLatin) + hint |= ZWP_TEXT_INPUT_V4_CONTENT_HINT_LATIN; + if (hints & Qt::ImhMultiLine) + hint |= ZWP_TEXT_INPUT_V4_CONTENT_HINT_MULTILINE; + if (hints & Qt::ImhDigitsOnly) + purpose = ZWP_TEXT_INPUT_V4_CONTENT_PURPOSE_DIGITS; + if (hints & Qt::ImhFormattedNumbersOnly) + purpose = ZWP_TEXT_INPUT_V4_CONTENT_PURPOSE_NUMBER; + if (hints & Qt::ImhUppercaseOnly) + hint |= ZWP_TEXT_INPUT_V4_CONTENT_HINT_UPPERCASE; + if (hints & Qt::ImhLowercaseOnly) + hint |= ZWP_TEXT_INPUT_V4_CONTENT_HINT_LOWERCASE; + if (hints & Qt::ImhDialableCharactersOnly) + purpose = ZWP_TEXT_INPUT_V4_CONTENT_PURPOSE_PHONE; + if (hints & Qt::ImhEmailCharactersOnly) + purpose = ZWP_TEXT_INPUT_V4_CONTENT_PURPOSE_EMAIL; + if (hints & Qt::ImhUrlCharactersOnly) + purpose = ZWP_TEXT_INPUT_V4_CONTENT_PURPOSE_URL; + if (hints & Qt::ImhLatinOnly) + hint |= ZWP_TEXT_INPUT_V4_CONTENT_HINT_LATIN; + return QWaylandInputMethodContentType{hint, purpose}; } @@ -273,6 +312,44 @@ int QWaylandInputMethodEventBuilder::indexFromWayland(const QString &text, int l } } +int QWaylandInputMethodEventBuilder::trimmedIndexFromWayland(const QString &text, int length, int base) +{ + if (length == 0) + return base; + + if (length < 0) { + const QByteArray &utf8 = QStringView{text}.left(base).toUtf8(); + const int len = utf8.size(); + const int start = len + length; + if (start <= 0) + return 0; + + for (int i = 0; i < 4; i++) { + if (start + i >= len) + return base; + + const unsigned char ch = utf8.at(start + i); + // check if current character is a utf8's initial character. + if (ch < 0x80 || ch > 0xbf) + return QString::fromUtf8(utf8.left(start + i)).length(); + } + } else { + const QByteArray &utf8 = QStringView{text}.mid(base).toUtf8(); + const int len = utf8.size(); + const int start = length; + if (start >= len) + return base + QString::fromUtf8(utf8).length(); + + for (int i = 0; i < 4; i++) { + const unsigned char ch = utf8.at(start - i); + // check if current character is a utf8's initial character. + if (ch < 0x80 || ch > 0xbf) + return base + QString::fromUtf8(utf8.left(start - i)).length(); + } + } + return -1; +} + int QWaylandInputMethodEventBuilder::indexToWayland(const QString &text, int length, int base) { return QStringView{text}.mid(base, length).toUtf8().size(); diff --git a/src/shared/qwaylandinputmethodeventbuilder_p.h b/src/shared/qwaylandinputmethodeventbuilder_p.h index 2499a563..f362562f 100644 --- a/src/shared/qwaylandinputmethodeventbuilder_p.h +++ b/src/shared/qwaylandinputmethodeventbuilder_p.h @@ -63,6 +63,8 @@ public: static int indexFromWayland(const QString &text, int length, int base = 0); static int indexToWayland(const QString &text, int length, int base = 0); + + static int trimmedIndexFromWayland(const QString &text, int length, int base = 0); private: QPair<int, int> replacementForDeleteSurrounding(); @@ -76,10 +78,11 @@ private: }; struct QWaylandInputMethodContentType { - uint32_t hint; - uint32_t purpose; + uint32_t hint = 0; + uint32_t purpose = 0; static QWaylandInputMethodContentType convert(Qt::InputMethodHints hints); + static QWaylandInputMethodContentType convertV4(Qt::InputMethodHints hints); }; diff --git a/sync.profile b/sync.profile index 67026fa7..50825d46 100644 --- a/sync.profile +++ b/sync.profile @@ -31,6 +31,7 @@ "^qwayland-surface-extension.h", "^qwayland-tablet-unstable-v2.h", "^qwayland-text-input-unstable-v2.h", + "^qwayland-text-input-unstable-v4-wip.h", "^qwayland-qt-text-input-method-unstable-v1.h", "^qwayland-touch-extension.h", "^qwayland-wayland.h", @@ -44,6 +45,7 @@ "^wayland-surface-extension-client-protocol.h", "^wayland-tablet-unstable-v2-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", "^wayland-touch-extension-client-protocol.h", "^wayland-wayland-client-protocol.h", @@ -67,6 +69,7 @@ "^qwayland-server-scaler.h", "^qwayland-server-server-buffer-extension.h", "^qwayland-server-text-input-unstable-v2.h", + "^qwayland-server-text-input-unstable-v4-wip.h", "^qwayland-server-qt-text-input-method-unstable-v1.h", "^qwayland-server-touch-extension.h", "^qwayland-server-viewporter.h", @@ -82,6 +85,7 @@ "^wayland-scaler-server-protocol.h", "^wayland-server-buffer-extension-server-protocol.h", "^wayland-text-input-unstable-v2-server-protocol.h", + "^wayland-text-input-unstable-v4-wip-server-protocol.h", "^wayland-qt-text-input-method-unstable-v1-server-protocol.h", "^wayland-viewporter-server-protocol.h", "^wayland-touch-extension-server-protocol.h", |