From 853f8f6b67e4f3993e2548fbb49320bd20fb2df1 Mon Sep 17 00:00:00 2001 From: Jan Arne Petersen Date: Wed, 30 Dec 2015 00:08:17 +0100 Subject: [PATCH] Fix text-input support for new API Update text input support to upstream text-input protocol v2 from wayland-protocols. Remove support for input-method protocol for now. Map text-input protocol on compositor side to the Qt input method API, this allows to use any qt platform input method on compositor side (especially qtvirtualkeyboard). Add support for qtvirtualkeyboard to pure-qml example. Implement all missing functions of the text-input protocol. Change-Id: I597451ff65454a63dff86026b6a8d1ffbe07ce02 Done-with: Zeno Endemann Reviewed-by: Giulio Camuffo --- .../wayland/protocols/input-method.xml | 273 ---------- .../protocols/text-input-unstable-v2.xml | 478 ++++++++++++++++ src/3rdparty/wayland/protocols/text.xml | 346 ------------ src/plugins/platforms/wayland/client.pro | 4 +- .../platforms/wayland/qwaylanddisplay.cpp | 10 +- .../platforms/wayland/qwaylanddisplay_p.h | 6 +- .../wayland/qwaylandinputcontext.cpp | 512 +++++++++++++----- .../wayland/qwaylandinputcontext_p.h | 77 ++- .../platforms/wayland/qwaylandinputdevice.cpp | 27 +- .../platforms/wayland/qwaylandinputdevice_p.h | 6 + .../qwaylandinputmethodeventbuilder.cpp | 286 ++++++++++ .../shared/qwaylandinputmethodeventbuilder.h | 87 +++ .../platforms/wayland/shared/qwaylandxkb.cpp | 55 ++ .../platforms/wayland/shared/qwaylandxkb.h | 7 + 14 files changed, 1392 insertions(+), 782 deletions(-) delete mode 100644 src/3rdparty/wayland/protocols/input-method.xml create mode 100644 src/3rdparty/wayland/protocols/text-input-unstable-v2.xml delete mode 100644 src/3rdparty/wayland/protocols/text.xml create mode 100644 src/plugins/platforms/wayland/shared/qwaylandinputmethodeventbuilder.cpp create mode 100644 src/plugins/platforms/wayland/shared/qwaylandinputmethodeventbuilder.h diff --git a/src/3rdparty/wayland/protocols/input-method.xml b/src/3rdparty/wayland/protocols/input-method.xml deleted file mode 100644 index 70afdcb1dd4..00000000000 --- a/src/3rdparty/wayland/protocols/input-method.xml +++ /dev/null @@ -1,273 +0,0 @@ - - - - Copyright © 2012, 2013 Intel Corporation - - 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. - - - - - - Corresponds to a text model on input method side. An input method context - is created on text mode activation on the input method side. It allows to - receive information about the text model from the application via events. - Input method contexts do not keep state after deactivation and should be - destroyed after deactivation is handled. - - Text is generally UTF-8 encoded, indices and lengths are in bytes. - - Serials are used to synchronize the state between the text input and - an input method. New serials are sent by the text input in the - commit_state request and are used by the input method to indicate - the known text input state in events like preedit_string, commit_string, - and keysym. The text input can then ignore events from the input method - which are based on an outdated state (for example after a reset). - - - - - Send the commit string text for insertion to the application. - - The text to commit could be either just a single character after a key - press or the result of some composing (pre-edit). It could be also an - empty text when some text should be removed (see - delete_surrounding_text) or when the input cursor should be moved (see - cursor_position). - - Any previously set composing text will be removed. - - - - - - - Send the pre-edit string text to the application text input. - - The commit text can be used to replace the preedit text on reset (for - example on unfocus). - - Also previously sent preedit_style and preedit_cursor requests are - processed bt the text_input also. - - - - - - - - Sets styling information on composing text. The style is applied for - length in bytes from index relative to the beginning of - the composing text (as byte offset). Multiple styles can - be applied to a composing text. - - This request should be sent before sending preedit_string request. - - - - - - - - Sets the cursor position inside the composing text (as byte offset) - relative to the start of the composing text. - - When index is negative no cursor should be displayed. - - This request should be sent before sending preedit_string request. - - - - - - - - This request will be handled on text_input side as part of a directly - following commit_string request. - - - - - - - Sets the cursor and anchor to a new position. Index is the new cursor - position in bytess (when >= 0 relative to the end of inserted text - else relative to beginning of inserted text). Anchor is the new anchor - position in bytes (when >= 0 relative to the end of inserted text, else - relative to beginning of inserted text). When there should be no - selected text anchor should be the same as index. - - This request will be handled on text_input side as part of a directly - following commit_string request. - - - - - - - - - - Notify when a key event was sent. Key events should not be used for - normal text input operations, which should be done with commit_string, - delete_surrounfing_text, etc. The key event follows the wl_keyboard key - event convention. Sym is a XKB keysym, state a wl_keyboard key_state. - - - - - - - - - - Allows an input method to receive hardware keyboard input and process - key events to generate text events (with pre-edit) over the wire. This - allows input methods which compose multiple key events for inputting - text like it is done for CJK languages. - - - - - - Should be used when filtering key events with grab_keyboard. - - When the wl_keyboard::key event is not processed by the input - method itself and should be sent to the client instead, forward it - with this request. The arguments should be the ones from the - wl_keyboard::key event. - - For generating custom key events use the keysym request instead. - - - - - - - - - Should be used when filtering key events with grab_keyboard. - - When the wl_keyboard::modifiers event should be also send to the - client, forward it with this request. The arguments should be the ones - from the wl_keyboard::modifiers event. - - - - - - - - - - - - - - - - - - The plain surrounding text around the input position. Cursor is the - position in bytes within the surrounding text relative to the beginning - of the text. Anchor is the position in bytes of the selection anchor - within the surrounding text relative to the beginning of the text. If - there is no selected text anchor is the same as cursor. - - - - - - - - - - - - - - - - - - - - - - - - - - An input method object is responsible to compose text in response to - input from hardware or virtual keyboards. There is one input method - object per seat. On activate there is a new input method context object - created which allows the input method to communicate with the text model. - - - - A text model was activated. Creates an input method context object - which allows communication with the text model. - - - - - - The text model corresponding to the context argument was deactivated. - The input method context should be destroyed after deactivation is - handled. - - - - - - - - Only one client can bind this interface at a time. - - - - - - - - - - - - - - - - A keybaord surface is only shown, when a text model is active - - - - - - - - An overlay panel is shown near the input cursor above the application - window when a text model is active. - - - - diff --git a/src/3rdparty/wayland/protocols/text-input-unstable-v2.xml b/src/3rdparty/wayland/protocols/text-input-unstable-v2.xml new file mode 100644 index 00000000000..bb366c9215c --- /dev/null +++ b/src/3rdparty/wayland/protocols/text-input-unstable-v2.xml @@ -0,0 +1,478 @@ + + + + + Copyright © 2012, 2013 Intel Corporation + Copyright © 2015, 2016 Jan Arne Petersen + + 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. + + + + + The zwp_text_input_v2 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 pre-edit and commit events. Using this interface removes the need + for applications to directly process hardware key events and compose text + out of them. + + Text is valid UTF-8 encoded, indices and lengths are in bytes. Indices + have to always point to the first byte of an UTF-8 encoded code point. + Lengths are not allowed to contain just a part of an UTF-8 encoded code + point. + + State is sent by the state requests (set_surrounding_text, + set_content_type, set_cursor_rectangle and set_preferred_language) and + an update_state request. After an enter or an input_method_change event + all state information is invalidated and needs to be resent from the + client. A reset or entering a new widget on client side also + invalidates all current state information. + + + + + Destroy the wp_text_input object. Also disables all surfaces enabled + through this wp_text_input object + + + + + + Enable text input in a surface (usually when a text entry inside of it + has focus). + + This can be called before or after a surface gets text (or keyboard) + focus via the enter event. Text input to a surface is only active + when it has the current text (or keyboard) focus and is enabled. + + + + + + + Disable text input in a surface (typically when there is no focus on any + text entry inside the surface). + + + + + + + Requests input panels (virtual keyboard) to show. + + This should be used for example to show a virtual keyboard again + (with a tap) after it was closed by pressing on a close button on the + keyboard. + + + + + + Requests input panels (virtual keyboard) to hide. + + + + + + Sets the plain surrounding text around the input position. Text is + UTF-8 encoded. Cursor is the byte offset within the surrounding text. + Anchor is the byte offset of the selection anchor within the + surrounding text. If there is no selected text, anchor is the same as + cursor. + + Make sure to always send some text before and after the cursor + except when the cursor is at the beginning or end of text. + + When there was a configure_surrounding_text event take the + before_cursor and after_cursor arguments into account for picking how + much surrounding text to send. + + There is a maximum length of wayland messages so text can not be + longer than 4000 bytes. + + + + + + + + + Content hint is a bitmask to allow to modify the behavior of the text + input. + + + + + + + + + + + + + + + + + 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. + + + + + + + + + + + + + + + + + + + Sets the content purpose and content hint. While the purpose is the + basic purpose of an input field, the hint flags allow to modify some + of the behavior. + + When no content type is explicitly set, a normal content purpose with + none hint should be assumed. + + + + + + + + Sets the cursor outline as a x, y, width, height rectangle in surface + local coordinates. + + Allows the compositor to put a window with word suggestions near the + cursor. + + + + + + + + + + Sets a specific language. This allows for example a virtual keyboard to + show a language specific layout. The "language" argument is a RFC-3066 + format language tag. + + It could be used for example in a word processor to indicate language of + currently edited document or in an instant message application which + tracks languages of contacts. + + + + + + + Defines the reason for sending an updated state. + + + + + + + + + + Allows to atomically send state updates from client. + + This request should follow after a batch of state updating requests + like set_surrounding_text, set_content_type, set_cursor_rectangle and + set_preferred_language. + + The flags field indicates why an updated state is sent to the input + method. + + Reset should be used by an editor widget after the text was changed + outside of the normal input method flow. + + For "change" it is enough to send the changed state, else the full + state should be send. + + Serial should be set to the serial from the last enter or + input_method_changed event. + + To make sure to not receive outdated input method events after a + reset or switching to a new widget wl_display_sync() should be used + after update_state in these cases. + + + + + + + + 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. + + + + + + + + Notification that this seat's text-input focus is no longer on + a certain surface. + + The leave notification is sent before the enter notification + for the new focus. + + When the seat has the keyboard capabillity the text-input focus follows + the keyboard focus. + + + + + + + + + + + + + Notification that the visibility of the input panel (virtual keyboard) + changed. + + The rectangle x, y, width, height defines the area overlapped by the + input panel (virtual keyboard) on the surface having the text + focus in surface local coordinates. + + That can be used to make sure widgets are visible and not covered by + a virtual keyboard. + + + + + + + + + + + Notify when a new composing text (pre-edit) should be set around the + current cursor position. Any previously set composing text should + be removed. + + The commit text can be used to replace the composing text in some cases + (for example when losing focus). + + The text input should also handle all preedit_style and preedit_cursor + events occurring directly before preedit_string. + + + + + + + + + + + + + + + + + + + Sets styling information on composing text. The style is applied for + length bytes from index relative to the beginning of the composing + text (as byte offset). Multiple styles can be applied to a composing + text by sending multiple preedit_styling events. + + This event is handled as part of a following preedit_string event. + + + + + + + + + Sets the cursor position inside the composing text (as byte + offset) relative to the start of the composing text. When index is a + negative number no cursor is shown. + + When no preedit_cursor event is sent the cursor will be at the end of + the composing text by default. + + This event is handled as part of a following preedit_string event. + + + + + + + Notify when text should be inserted into the editor widget. The text to + commit could be either just a single character after a key press or the + result of some composing (pre-edit). It could be also an empty text + when some text should be removed (see delete_surrounding_text) or when + the input cursor should be moved (see cursor_position). + + Any previously set composing text should be removed. + + + + + + + Notify when the cursor or anchor position should be modified. + + This event should be handled as part of a following commit_string + event. + + The text between anchor and index should be selected. + + + + + + + + Notify when the text around the current cursor position should be + deleted. BeforeLength and afterLength is the length (in bytes) of text + before and after the current cursor position (excluding the selection) + to delete. + + This event should be handled as part of a following commit_string + or preedit_string event. + + + + + + + + Transfer an array of 0-terminated modifiers names. The position in + the array is the index of the modifier as used in the modifiers + bitmask in the keysym event. + + + + + + + Notify when a key event was sent. Key events should not be used + for normal text input operations, which should be done with + commit_string, delete_surrounding_text, etc. The key event follows + the wl_keyboard key event convention. Sym is a XKB keysym, state a + wl_keyboard key_state. Modifiers are a mask for effective modifiers + (where the modifier indices are set by the modifiers_map event) + + + + + + + + + + Sets the language of the input text. The "language" argument is a RFC-3066 + format language tag. + + + + + + + + + + + + + Sets the text direction of input text. + + It is mainly needed for showing input cursor on correct side of the + editor when there is no input yet done and making sure neutral + direction text is laid out properly. + + + + + + + Configure what amount of surrounding text is expected by the + input method. The surrounding text will be sent in the + set_surrounding_text request on the following state information updates. + + + + + + + + The input method changed on compositor side, which invalidates all + current state information. New state information should be sent from + the client via state requests (set_surrounding_text, + set_content_hint, ...) and update_state. + + + + + + + + + A factory for text-input objects. This object is a global singleton. + + + + + Destroy the wp_text_input_manager object. + + + + + + Creates a new text-input object for a given seat. + + + + + + diff --git a/src/3rdparty/wayland/protocols/text.xml b/src/3rdparty/wayland/protocols/text.xml deleted file mode 100644 index 1b5284dc307..00000000000 --- a/src/3rdparty/wayland/protocols/text.xml +++ /dev/null @@ -1,346 +0,0 @@ - - - - - Copyright © 2012, 2013 Intel Corporation - - 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. - - - - - An object used for text input. Adds support for text input and input - methods to applications. A text-input object is created from a - wl_text_input_manager and corresponds typically to a text entry in an - application. - Requests are used to activate/deactivate the text-input object and set - state information like surrounding and selected text or the content type. - The information about entered text is sent to the text-input object via - the pre-edit and commit events. Using this interface removes the need - for applications to directly process hardware key events and compose text - out of them. - - Text is generally UTF-8 encoded, indices and lengths are in bytes. - - Serials are used to synchronize the state between the text input and - an input method. New serials are sent by the text input in the - commit_state request and are used by the input method to indicate - the known text input state in events like preedit_string, commit_string, - and keysym. The text input can then ignore events from the input method - which are based on an outdated state (for example after a reset). - - - - Requests the text-input object to be activated (typically when the - text entry gets focus). - The seat argument is a wl_seat which maintains the focus for this - activation. The surface argument is a wl_surface assigned to the - text-input object and tracked for focus lost. The enter event - is emitted on successful activation. - - - - - - - Requests the text-input object to be deactivated (typically when the - text entry lost focus). The seat argument is a wl_seat which was used - for activation. - - - - - - Requests input panels (virtual keyboard) to show. - - - - - Requests input panels (virtual keyboard) to hide. - - - - - Should be called by an editor widget when the input state should be - reset, for example after the text was changed outside of the normal - input method flow. - - - - - Sets the plain surrounding text around the input position. Text is - UTF-8 encoded. Cursor is the byte offset within the - surrounding text. Anchor is the byte offset of the - selection anchor within the surrounding text. If there is no selected - text anchor is the same as cursor. - - - - - - - - Content hint is a bitmask to allow to modify the behavior of the text - input. - - - - - - - - - - - - - - - - - - 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. - - - - - - - - - - - - - - - - - - Sets the content purpose and content hint. While the purpose is the - basic purpose of an input field, the hint flags allow to modify some - of the behavior. - - When no content type is explicitly set, a normal content purpose with - default hints (auto completion, auto correction, auto capitalization) - should be assumed. - - - - - - - - - - - - - Sets a specific language. This allows for example a virtual keyboard to - show a language specific layout. The "language" argument is a RFC-3066 - format language tag. - - It could be used for example in a word processor to indicate language of - currently edited document or in an instant message application which tracks - languages of contacts. - - - - - - - - - - - - - Notify the text-input object when it received focus. Typically in - response to an activate request. - - - - - - Notify the text-input object when it lost focus. Either in response - to a deactivate request or when the assigned surface lost focus or was - destroyed. - - - - - Transfer an array of 0-terminated modifiers names. The position in - the array is the index of the modifier as used in the modifiers - bitmask in the keysym event. - - - - - - Notify when the visibility state of the input panel changed. - - - - - - Notify when a new composing text (pre-edit) should be set around the - current cursor position. Any previously set composing text should - be removed. - - The commit text can be used to replace the preedit text on reset - (for example on unfocus). - - The text input should also handle all preedit_style and preedit_cursor - events occuring directly before preedit_string. - - - - - - - - - - - - - - - - - - Sets styling information on composing text. The style is applied for - length bytes from index relative to the beginning of the composing - text (as byte offset). Multiple styles can - be applied to a composing text by sending multiple preedit_styling - events. - - This event is handled as part of a following preedit_string event. - - - - - - - - Sets the cursor position inside the composing text (as byte - offset) relative to the start of the composing text. When index is a - negative number no cursor is shown. - - This event is handled as part of a following preedit_string event. - - - - - - Notify when text should be inserted into the editor widget. The text to - commit could be either just a single character after a key press or the - result of some composing (pre-edit). It could be also an empty text - when some text should be removed (see delete_surrounding_text) or when - the input cursor should be moved (see cursor_position). - - Any previously set composing text should be removed. - - - - - - - Notify when the cursor or anchor position should be modified. - - This event should be handled as part of a following commit_string - event. - - - - - - - Notify when the text around the current cursor position should be - deleted. - - Index is relative to the current cursor (in bytes). - Length is the length of deleted text (in bytes). - - This event should be handled as part of a following commit_string - event. - - - - - - - Notify when a key event was sent. Key events should not be used - for normal text input operations, which should be done with - commit_string, delete_surrounding_text, etc. The key event follows - the wl_keyboard key event convention. Sym is a XKB keysym, state a - wl_keyboard key_state. Modifiers are a mask for effective modifiers - (where the modifier indices are set by the modifiers_map event) - - - - - - - - - - Sets the language of the input text. The "language" argument is a RFC-3066 - format language tag. - - - - - - - - - - - - Sets the text direction of input text. - - It is mainly needed for showing input cursor on correct side of the - editor when there is no input yet done and making sure neutral - direction text is laid out properly. - - - - - - - - - A factory for text-input objects. This object is a global singleton. - - - - Creates a new text-input object. - - - - - diff --git a/src/plugins/platforms/wayland/client.pro b/src/plugins/platforms/wayland/client.pro index 1219ee3451d..61404eeb98c 100644 --- a/src/plugins/platforms/wayland/client.pro +++ b/src/plugins/platforms/wayland/client.pro @@ -39,7 +39,7 @@ WAYLANDCLIENTSOURCES += \ ../extensions/touch-extension.xml \ ../extensions/qtkey-extension.xml \ ../extensions/windowmanager.xml \ - ../3rdparty/protocol/text.xml \ + ../3rdparty/protocol/text-input-unstable-v2.xml \ ../3rdparty/protocol/xdg-shell.xml \ SOURCES += qwaylandintegration.cpp \ @@ -66,6 +66,7 @@ SOURCES += qwaylandintegration.cpp \ qwaylandqtkey.cpp \ ../shared/qwaylandmimehelper.cpp \ ../shared/qwaylandxkb.cpp \ + ../shared/qwaylandinputmethodeventbuilder.cpp \ qwaylandabstractdecoration.cpp \ qwaylanddecorationfactory.cpp \ qwaylanddecorationplugin.cpp \ @@ -100,6 +101,7 @@ HEADERS += qwaylandintegration_p.h \ qwaylandqtkey_p.h \ ../shared/qwaylandmimehelper.h \ ../shared/qwaylandxkb.h \ + ../shared/qwaylandinputmethodeventbuilder.h \ qwaylandabstractdecoration_p.h \ qwaylanddecorationfactory_p.h \ qwaylanddecorationplugin_p.h \ diff --git a/src/plugins/platforms/wayland/qwaylanddisplay.cpp b/src/plugins/platforms/wayland/qwaylanddisplay.cpp index 7244363cd08..a18b9853faf 100644 --- a/src/plugins/platforms/wayland/qwaylanddisplay.cpp +++ b/src/plugins/platforms/wayland/qwaylanddisplay.cpp @@ -50,6 +50,7 @@ #include "qwaylandxdgshell_p.h" #include "qwaylandxdgsurface_p.h" #include "qwaylandwlshellsurface_p.h" +#include "qwaylandinputcontext_p.h" #include "qwaylandwindowmanagerintegration_p.h" #include "qwaylandshellintegration_p.h" @@ -60,7 +61,7 @@ #include "qwaylandtouch_p.h" #include "qwaylandqtkey_p.h" -#include +#include #include #include @@ -281,8 +282,11 @@ void QWaylandDisplay::registry_global(uint32_t id, const QString &interface, uin mTouchExtension.reset(new QWaylandTouchExtension(this, id)); } else if (interface == QStringLiteral("qt_key_extension")) { mQtKeyExtension.reset(new QWaylandQtKeyExtension(this, id)); - } else if (interface == QStringLiteral("wl_text_input_manager")) { - mTextInputManager.reset(new QtWayland::wl_text_input_manager(registry, id, 1)); + } else if (interface == QStringLiteral("zwp_text_input_manager_v2")) { + mTextInputManager.reset(new QtWayland::zwp_text_input_manager_v2(registry, id, 1)); + foreach (QWaylandInputDevice *inputDevice, mInputDevices) { + inputDevice->setTextInput(new QWaylandTextInput(this, mTextInputManager->get_text_input(inputDevice->wl_seat()))); + } } else if (interface == QStringLiteral("qt_hardware_integration")) { mHardwareIntegration.reset(new QWaylandHardwareIntegration(registry, id)); // make a roundtrip here since we need to receive the events sent by diff --git a/src/plugins/platforms/wayland/qwaylanddisplay_p.h b/src/plugins/platforms/wayland/qwaylanddisplay_p.h index 82d87cb928f..618e57c59a8 100644 --- a/src/plugins/platforms/wayland/qwaylanddisplay_p.h +++ b/src/plugins/platforms/wayland/qwaylanddisplay_p.h @@ -76,7 +76,7 @@ namespace QtWayland { class qt_shell; class qt_sub_surface_extension; class qt_surface_extension; - class wl_text_input_manager; + class zwp_text_input_manager_v2; class xdg_shell; } @@ -147,7 +147,7 @@ public: QtWayland::qt_surface_extension *windowExtension() const { return mWindowExtension.data(); } QWaylandTouchExtension *touchExtension() const { return mTouchExtension.data(); } - QtWayland::wl_text_input_manager *textInputManager() const { return mTextInputManager.data(); } + QtWayland::zwp_text_input_manager_v2 *textInputManager() const { return mTextInputManager.data(); } QWaylandHardwareIntegration *hardwareIntegration() const { return mHardwareIntegration.data(); } struct RegistryGlobal { @@ -207,7 +207,7 @@ private: QScopedPointer mTouchExtension; QScopedPointer mQtKeyExtension; QScopedPointer mWindowManagerIntegration; - QScopedPointer mTextInputManager; + QScopedPointer mTextInputManager; QScopedPointer mHardwareIntegration; QSocketNotifier *mReadNotifier; int mFd; diff --git a/src/plugins/platforms/wayland/qwaylandinputcontext.cpp b/src/plugins/platforms/wayland/qwaylandinputcontext.cpp index 00f6a43038f..aeaf415d2cd 100644 --- a/src/plugins/platforms/wayland/qwaylandinputcontext.cpp +++ b/src/plugins/platforms/wayland/qwaylandinputcontext.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 Klarälvdalens Datakonsult AB (KDAB). +** Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtWaylandClient module of the Qt Toolkit. @@ -40,147 +40,358 @@ #include "qwaylandinputcontext_p.h" -#include -#include -#ifndef QT_NO_WAYLAND_XKB -#include -#endif +#include +#include +#include +#include +#include #include "qwaylanddisplay_p.h" #include "qwaylandinputdevice_p.h" +#include "qwaylandinputmethodeventbuilder.h" #include "qwaylandwindow_p.h" +#include "qwaylandxkb.h" QT_BEGIN_NAMESPACE +Q_LOGGING_CATEGORY(qLcQpaInputMethods, "qt.qpa.input.methods") + namespace QtWaylandClient { -static Qt::Key toQtKey(uint32_t sym) -{ -#ifndef QT_NO_WAYLAND_XKB - switch (static_cast(sym)) { - case XKB_KEY_BackSpace: - return Qt::Key_Backspace; - case XKB_KEY_Return: - return Qt::Key_Return; - case XKB_KEY_Left: - return Qt::Key_Left; - case XKB_KEY_Up: - return Qt::Key_Up; - case XKB_KEY_Right: - return Qt::Key_Right; - case XKB_KEY_Down: - return Qt::Key_Down; - default: - return Qt::Key_unknown; - } -#else - Q_UNUSED(sym) - return Qt::Key_unknown; -#endif +namespace { +const Qt::InputMethodQueries supportedQueries = Qt::ImEnabled | + Qt::ImSurroundingText | + Qt::ImCursorPosition | + Qt::ImAnchorPosition | + Qt::ImHints | + Qt::ImCursorRectangle | + Qt::ImPreferredLanguage; } -static QEvent::Type toQEventType(uint32_t state) -{ - switch (static_cast(state)) { - default: - case WL_KEYBOARD_KEY_STATE_PRESSED: - return QEvent::KeyPress; - case WL_KEYBOARD_KEY_STATE_RELEASED: - return QEvent::KeyRelease; - } -} - -QWaylandTextInput::QWaylandTextInput(struct ::wl_text_input *text_input) - : QtWayland::wl_text_input(text_input) - , m_commit() +QWaylandTextInput::QWaylandTextInput(QWaylandDisplay *display, struct ::zwp_text_input_v2 *text_input) + : QtWayland::zwp_text_input_v2(text_input) + , m_display(display) + , m_builder() , m_serial(0) - , m_resetSerial(0) + , m_surface(nullptr) + , m_preeditCommit() + , m_inputPanelVisible(false) + , m_keyboardRectangle() + , m_locale() + , m_inputDirection(Qt::LayoutDirectionAuto) + , m_resetCallback(nullptr) { } -QString QWaylandTextInput::commitString() const +QWaylandTextInput::~QWaylandTextInput() { - return m_commit; + if (m_resetCallback) + wl_callback_destroy(m_resetCallback); } void QWaylandTextInput::reset() { - wl_text_input::reset(); - updateState(); - m_resetSerial = m_serial; + m_builder.reset(); + m_preeditCommit = QString(); + updateState(Qt::ImQueryAll, update_state_reset); } -void QWaylandTextInput::updateState() +void QWaylandTextInput::commit() +{ + if (QObject *o = QGuiApplication::focusObject()) { + QInputMethodEvent event; + event.setCommitString(m_preeditCommit); + QCoreApplication::sendEvent(o, &event); + } + + reset(); +} + +const wl_callback_listener QWaylandTextInput::callbackListener = { + QWaylandTextInput::resetCallback +}; + +void QWaylandTextInput::resetCallback(void *data, wl_callback *, uint32_t) +{ + QWaylandTextInput *self = static_cast(data); + + if (self->m_resetCallback) { + wl_callback_destroy(self->m_resetCallback); + self->m_resetCallback = nullptr; + } +} + +void QWaylandTextInput::updateState(Qt::InputMethodQueries queries, uint32_t flags) { if (!QGuiApplication::focusObject()) return; - QInputMethodQueryEvent event(Qt::ImQueryAll); + if (!QGuiApplication::focusWindow() || !QGuiApplication::focusWindow()->handle()) + return; + + struct ::wl_surface *surface = static_cast(QGuiApplication::focusWindow()->handle())->object(); + if (!surface || (surface != m_surface)) + return; + + queries &= supportedQueries; + + // Surrounding text, cursor and anchor positions are transferred together + if ((queries & Qt::ImSurroundingText) || (queries & Qt::ImCursorPosition) || (queries & Qt::ImAnchorPosition)) + queries |= Qt::ImSurroundingText | Qt::ImCursorPosition | Qt::ImAnchorPosition; + + QInputMethodQueryEvent event(queries); QCoreApplication::sendEvent(QGuiApplication::focusObject(), &event); - const QString &text = event.value(Qt::ImSurroundingText).toString(); - const int cursor = event.value(Qt::ImCursorPosition).toInt(); - const int anchor = event.value(Qt::ImAnchorPosition).toInt(); + 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(); - set_surrounding_text(text, text.leftRef(cursor).toUtf8().size(), text.leftRef(anchor).toUtf8().size()); + // Make sure text is not too big + if (text.toUtf8().size() > 2048) { + int c = qAbs(cursor - anchor) <= 512 ? qMin(cursor, anchor) + qAbs(cursor - anchor) / 2: cursor; - commit_state(++m_serial); + const int offset = c - qBound(0, c, 512 - qMin(text.size() - c, 256)); + text = text.mid(offset + c - 256, 512); + cursor -= offset; + anchor -= offset; + } + + set_surrounding_text(text, text.leftRef(cursor).toUtf8().size(), text.leftRef(anchor).toUtf8().size()); + } + + if (queries & Qt::ImHints) { + QWaylandInputMethodContentType contentType = QWaylandInputMethodContentType::convert(static_cast(event.value(Qt::ImHints).toInt())); + set_content_type(contentType.hint, contentType.purpose); + } + + if (queries & Qt::ImCursorRectangle) { + const QRect &cRect = event.value(Qt::ImCursorRectangle).toRect(); + const QRect &tRect = QGuiApplication::inputMethod()->inputItemTransform().mapRect(cRect); + set_cursor_rectangle(tRect.x(), tRect.y(), tRect.width(), tRect.height()); + } + + if (queries & Qt::ImPreferredLanguage) { + const QString &language = event.value(Qt::ImPreferredLanguage).toString(); + set_preferred_language(language); + } + + update_state(m_serial, flags); + if (flags != update_state_change) { + if (m_resetCallback) + wl_callback_destroy(m_resetCallback); + m_resetCallback = wl_display_sync(m_display->wl_display()); + wl_callback_add_listener(m_resetCallback, &QWaylandTextInput::callbackListener, this); + } } -void QWaylandTextInput::text_input_preedit_string(uint32_t serial, const QString &text, const QString &commit) +bool QWaylandTextInput::isInputPanelVisible() const { - Q_UNUSED(serial) + return m_inputPanelVisible; +} + +QRectF QWaylandTextInput::keyboardRect() const +{ + return m_keyboardRectangle; +} + +QLocale QWaylandTextInput::locale() const +{ + return m_locale; +} + +Qt::LayoutDirection QWaylandTextInput::inputDirection() const +{ + return m_inputDirection; +} + +void QWaylandTextInput::zwp_text_input_v2_enter(uint32_t serial, ::wl_surface *surface) +{ + m_serial = serial; + m_surface = surface; + + updateState(Qt::ImQueryAll, update_state_enter); +} + +void QWaylandTextInput::zwp_text_input_v2_leave(uint32_t serial, ::wl_surface *surface) +{ + m_serial = serial; + + if (m_surface != surface) { + qCDebug(qLcQpaInputMethods()) << Q_FUNC_INFO << "Got leave event for surface" << surface << "focused surface" << m_surface; + } + + m_surface = nullptr; +} + +void QWaylandTextInput::zwp_text_input_v2_modifiers_map(wl_array *map) +{ + QList modifiersMap = QByteArray::fromRawData(static_cast(map->data), map->size).split('\0'); + + m_modifiersMap.clear(); + + Q_FOREACH (const QByteArray &modifier, modifiersMap) { + if (modifier == "Shift") + m_modifiersMap.append(Qt::ShiftModifier); + else if (modifier == "Control") + m_modifiersMap.append(Qt::ControlModifier); + else if (modifier == "Alt") + m_modifiersMap.append(Qt::AltModifier); + else if (modifier == "Mod1") + m_modifiersMap.append(Qt::AltModifier); + else if (modifier == "Mod4") + m_modifiersMap.append(Qt::MetaModifier); + else + m_modifiersMap.append(Qt::NoModifier); + } +} + +void QWaylandTextInput::zwp_text_input_v2_input_panel_state(uint32_t visible, int32_t x, int32_t y, int32_t width, int32_t height) +{ + const bool inputPanelVisible = (visible == input_panel_visibility_visible); + if (m_inputPanelVisible != inputPanelVisible) { + m_inputPanelVisible = inputPanelVisible; + QGuiApplicationPrivate::platformIntegration()->inputContext()->emitInputPanelVisibleChanged(); + } + const QRectF keyboardRectangle(x, y, width, height); + if (m_keyboardRectangle != keyboardRectangle) { + m_keyboardRectangle = keyboardRectangle; + QGuiApplicationPrivate::platformIntegration()->inputContext()->emitKeyboardRectChanged(); + } +} + +void QWaylandTextInput::zwp_text_input_v2_preedit_string(const QString &text, const QString &commit) +{ + if (m_resetCallback) { + qCDebug(qLcQpaInputMethods()) << "discard preedit_string: reset not confirmed"; + m_builder.reset(); + return; + } + if (!QGuiApplication::focusObject()) return; - m_commit = commit; - QList attributes; - QInputMethodEvent event(text, attributes); + QInputMethodEvent event = m_builder.buildPreedit(text); + + m_builder.reset(); + m_preeditCommit = commit; + QCoreApplication::sendEvent(QGuiApplication::focusObject(), &event); } -void QWaylandTextInput::text_input_commit_string(uint32_t serial, const QString &text) +void QWaylandTextInput::zwp_text_input_v2_preedit_styling(uint32_t index, uint32_t length, uint32_t style) { - Q_UNUSED(serial); + m_builder.addPreeditStyling(index, length, style); +} + +void QWaylandTextInput::zwp_text_input_v2_preedit_cursor(int32_t index) +{ + m_builder.setPreeditCursor(index); +} + +void QWaylandTextInput::zwp_text_input_v2_commit_string(const QString &text) +{ + if (m_resetCallback) { + qCDebug(qLcQpaInputMethods()) << "discard commit_string: reset not confirmed"; + m_builder.reset(); + return; + } + if (!QGuiApplication::focusObject()) return; - QInputMethodEvent event; - event.setCommitString(text); + QInputMethodEvent event = m_builder.buildCommit(text); + + m_builder.reset(); + QCoreApplication::sendEvent(QGuiApplication::focusObject(), &event); - - m_commit = QString(); } -void QWaylandTextInput::text_input_enter(wl_surface *) +void QWaylandTextInput::zwp_text_input_v2_cursor_position(int32_t index, int32_t anchor) { - updateState(); - m_resetSerial = m_serial; + m_builder.setCursorPosition(index, anchor); } -void QWaylandTextInput::text_input_leave() +void QWaylandTextInput::zwp_text_input_v2_delete_surrounding_text(uint32_t before_length, uint32_t after_length) { - if (!m_commit.isEmpty()) - text_input_commit_string(0, m_commit); + m_builder.setDeleteSurroundingText(before_length, after_length); } -void QWaylandTextInput::text_input_keysym(uint32_t serial, uint32_t time, uint32_t sym, uint32_t state, uint32_t modifiers) +void QWaylandTextInput::zwp_text_input_v2_keysym(uint32_t time, uint32_t sym, uint32_t state, uint32_t modifiers) { - Q_UNUSED(serial); - Q_UNUSED(time); - Q_UNUSED(modifiers); - if (!QGuiApplication::focusObject()) + if (m_resetCallback) { + qCDebug(qLcQpaInputMethods()) << "discard keysym: reset not confirmed"; + return; + } + + if (!QGuiApplication::focusWindow()) return; - // TODO: Convert modifiers to Qt::KeyboardModifiers. - QKeyEvent event(toQEventType(state), toQtKey(sym), Qt::NoModifier); - QCoreApplication::sendEvent(qGuiApp->focusWindow(), &event); + Qt::KeyboardModifiers qtModifiers = modifiersToQtModifiers(modifiers); + + QEvent::Type type = QWaylandXkb::toQtEventType(state); + const QString &text = QWaylandXkb::textFromKeysym(sym, qtModifiers); + int qtkey = QWaylandXkb::keysymToQtKey(sym, qtModifiers, text); + + QWindowSystemInterface::handleKeyEvent(QGuiApplication::focusWindow(), + time, type, qtkey, qtModifiers, text); +} + +void QWaylandTextInput::zwp_text_input_v2_language(const QString &language) +{ + if (m_resetCallback) { + qCDebug(qLcQpaInputMethods()) << "discard language: reset not confirmed"; + return; + } + + const QLocale locale(language); + if (m_locale != locale) { + m_locale = locale; + QGuiApplicationPrivate::platformIntegration()->inputContext()->emitLocaleChanged(); + } +} + +void QWaylandTextInput::zwp_text_input_v2_text_direction(uint32_t direction) +{ + if (m_resetCallback) { + qCDebug(qLcQpaInputMethods()) << "discard text_direction: reset not confirmed"; + return; + } + + const Qt::LayoutDirection inputDirection = (direction == text_direction_auto) ? Qt::LayoutDirectionAuto : + (direction == text_direction_ltr) ? Qt::LeftToRight : + (direction == text_direction_rtl) ? Qt::RightToLeft : Qt::LayoutDirectionAuto; + if (m_inputDirection != inputDirection) { + m_inputDirection = inputDirection; + QGuiApplicationPrivate::platformIntegration()->inputContext()->emitInputDirectionChanged(m_inputDirection); + } +} + +void QWaylandTextInput::zwp_text_input_v2_input_method_changed(uint32_t serial, uint32_t flags) +{ + Q_UNUSED(flags); + + m_serial = serial; + updateState(Qt::ImQueryAll, update_state_full); +} + +Qt::KeyboardModifiers QWaylandTextInput::modifiersToQtModifiers(uint32_t modifiers) +{ + Qt::KeyboardModifiers ret = Qt::NoModifier; + for (int i = 0; modifiers >>= 1; ++i) { + ret |= m_modifiersMap[i]; + } + return ret; } QWaylandInputContext::QWaylandInputContext(QWaylandDisplay *display) : QPlatformInputContext() , mDisplay(display) - , mTextInput() + , mCurrentWindow() +{ +} + +QWaylandInputContext::~QWaylandInputContext() { } @@ -191,96 +402,141 @@ bool QWaylandInputContext::isValid() const void QWaylandInputContext::reset() { - if (!ensureTextInput()) + qCDebug(qLcQpaInputMethods) << Q_FUNC_INFO; + + QPlatformInputContext::reset(); + + if (!textInput()) return; - mTextInput->reset(); + textInput()->reset(); } void QWaylandInputContext::commit() { - if (!ensureTextInput()) + qCDebug(qLcQpaInputMethods) << Q_FUNC_INFO; + + if (!textInput()) return; - if (!QGuiApplication::focusObject()) - return; - - QInputMethodEvent event; - event.setCommitString(mTextInput->commitString()); - QCoreApplication::sendEvent(QGuiApplication::focusObject(), &event); - - mTextInput->reset(); + textInput()->commit(); } void QWaylandInputContext::update(Qt::InputMethodQueries queries) { - Q_UNUSED(queries); - if (!ensureTextInput()) + qCDebug(qLcQpaInputMethods) << Q_FUNC_INFO << queries; + + if (!QGuiApplication::focusObject() || !textInput()) return; - mTextInput->updateState(); -} + if (mCurrentWindow && mCurrentWindow->handle() && !inputMethodAccepted()) { + struct ::wl_surface *surface = static_cast(mCurrentWindow->handle())->object(); + textInput()->disable(surface); + mCurrentWindow.clear(); + } else if (!mCurrentWindow && inputMethodAccepted()) { + QWindow *window = QGuiApplication::focusWindow(); + if (window && window->handle()) { + struct ::wl_surface *surface = static_cast(window->handle())->object(); + textInput()->enable(surface); + mCurrentWindow = window; + } + } -void QWaylandInputContext::invokeAction(QInputMethod::Action, int cursorPosition) -{ - if (!ensureTextInput()) - return; - - mTextInput->invoke_action(0, cursorPosition); // FIXME button, to UTF8 cursor position + textInput()->updateState(queries, QtWayland::zwp_text_input_v2::update_state_change); } void QWaylandInputContext::showInputPanel() { - if (!ensureTextInput()) + qCDebug(qLcQpaInputMethods) << Q_FUNC_INFO; + + if (!textInput()) return; - mTextInput->show_input_panel(); + textInput()->show_input_panel(); } void QWaylandInputContext::hideInputPanel() { - if (!ensureTextInput()) + qCDebug(qLcQpaInputMethods) << Q_FUNC_INFO; + + if (!textInput()) return; - mTextInput->hide_input_panel(); + textInput()->hide_input_panel(); } bool QWaylandInputContext::isInputPanelVisible() const { - return false; + qCDebug(qLcQpaInputMethods) << Q_FUNC_INFO; + + if (!textInput()) + return QPlatformInputContext::isInputPanelVisible(); + + return textInput()->isInputPanelVisible(); } -void QWaylandInputContext::setFocusObject(QObject *object) +QRectF QWaylandInputContext::keyboardRect() const { - if (!ensureTextInput()) - return; + qCDebug(qLcQpaInputMethods) << Q_FUNC_INFO; - if (!object) { - mTextInput->deactivate(mDisplay->defaultInputDevice()->wl_seat()); + if (!textInput()) + return QPlatformInputContext::keyboardRect(); + + return textInput()->keyboardRect(); +} + +QLocale QWaylandInputContext::locale() const +{ + qCDebug(qLcQpaInputMethods) << Q_FUNC_INFO; + + if (!textInput()) + return QPlatformInputContext::locale(); + + return textInput()->locale(); +} + +Qt::LayoutDirection QWaylandInputContext::inputDirection() const +{ + qCDebug(qLcQpaInputMethods) << Q_FUNC_INFO; + + if (!textInput()) + return QPlatformInputContext::inputDirection(); + + return textInput()->inputDirection(); +} + +void QWaylandInputContext::setFocusObject(QObject *) +{ + qCDebug(qLcQpaInputMethods) << Q_FUNC_INFO; + + if (!textInput()) return; - } QWindow *window = QGuiApplication::focusWindow(); - if (!window || !window->handle()) - return; - struct ::wl_surface *surface = static_cast(window->handle())->object(); - mTextInput->activate(mDisplay->defaultInputDevice()->wl_seat(), surface); + if (mCurrentWindow && mCurrentWindow->handle()) { + if (mCurrentWindow.data() != window || !inputMethodAccepted()) { + struct ::wl_surface *surface = static_cast(mCurrentWindow->handle())->object(); + textInput()->disable(surface); + mCurrentWindow.clear(); + } + } + + if (window && window->handle() && inputMethodAccepted()) { + if (mCurrentWindow.data() != window) { + struct ::wl_surface *surface = static_cast(window->handle())->object(); + textInput()->enable(surface); + mCurrentWindow = window; + } + textInput()->updateState(Qt::ImQueryAll, QtWayland::zwp_text_input_v2::update_state_enter); + } } -bool QWaylandInputContext::ensureTextInput() +QWaylandTextInput *QWaylandInputContext::textInput() const { - if (mTextInput) - return true; - - if (!isValid()) - return false; - - mTextInput.reset(new QWaylandTextInput(mDisplay->textInputManager()->create_text_input())); - return true; + return mDisplay->defaultInputDevice()->textInput(); } } QT_END_NAMESPACE - diff --git a/src/plugins/platforms/wayland/qwaylandinputcontext_p.h b/src/plugins/platforms/wayland/qwaylandinputcontext_p.h index cdabfcca492..0429dd14e19 100644 --- a/src/plugins/platforms/wayland/qwaylandinputcontext_p.h +++ b/src/plugins/platforms/wayland/qwaylandinputcontext_p.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 Klarälvdalens Datakonsult AB (KDAB). +** Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtWaylandClient module of the Qt Toolkit. @@ -54,36 +54,75 @@ #include -#include +#include +#include +#include +#include + +#include +#include QT_BEGIN_NAMESPACE +Q_DECLARE_LOGGING_CATEGORY(qLcQpaInputMethods) + namespace QtWaylandClient { class QWaylandDisplay; -class QWaylandTextInput : public QtWayland::wl_text_input +class QWaylandTextInput : public QtWayland::zwp_text_input_v2 { public: - QWaylandTextInput(struct ::wl_text_input *text_input); - - QString commitString() const; + QWaylandTextInput(QWaylandDisplay *display, struct ::zwp_text_input_v2 *text_input); + ~QWaylandTextInput(); void reset(); - void updateState(); + void commit(); + void updateState(Qt::InputMethodQueries queries, uint32_t flags); + + bool isInputPanelVisible() const; + QRectF keyboardRect() const; + + QLocale locale() const; + Qt::LayoutDirection inputDirection() const; protected: - void text_input_preedit_string(uint32_t serial, const QString &text, const QString &commit) Q_DECL_OVERRIDE; - void text_input_commit_string(uint32_t serial, const QString &text) Q_DECL_OVERRIDE; - void text_input_enter(wl_surface *surface) Q_DECL_OVERRIDE; - void text_input_leave() Q_DECL_OVERRIDE; - void text_input_keysym(uint32_t serial, uint32_t time, uint32_t sym, uint32_t state, uint32_t modifiers); + void zwp_text_input_v2_enter(uint32_t serial, struct ::wl_surface *surface) Q_DECL_OVERRIDE; + void zwp_text_input_v2_leave(uint32_t serial, struct ::wl_surface *surface) Q_DECL_OVERRIDE; + void zwp_text_input_v2_modifiers_map(wl_array *map) Q_DECL_OVERRIDE; + void zwp_text_input_v2_input_panel_state(uint32_t state, int32_t x, int32_t y, int32_t width, int32_t height) Q_DECL_OVERRIDE; + void zwp_text_input_v2_preedit_string(const QString &text, const QString &commit) Q_DECL_OVERRIDE; + void zwp_text_input_v2_preedit_styling(uint32_t index, uint32_t length, uint32_t style) Q_DECL_OVERRIDE; + void zwp_text_input_v2_preedit_cursor(int32_t index) Q_DECL_OVERRIDE; + void zwp_text_input_v2_commit_string(const QString &text) Q_DECL_OVERRIDE; + void zwp_text_input_v2_cursor_position(int32_t index, int32_t anchor) Q_DECL_OVERRIDE; + void zwp_text_input_v2_delete_surrounding_text(uint32_t before_length, uint32_t after_length) Q_DECL_OVERRIDE; + void zwp_text_input_v2_keysym(uint32_t time, uint32_t sym, uint32_t state, uint32_t modifiers) Q_DECL_OVERRIDE; + void zwp_text_input_v2_language(const QString &language) Q_DECL_OVERRIDE; + void zwp_text_input_v2_text_direction(uint32_t direction) Q_DECL_OVERRIDE; + void zwp_text_input_v2_input_method_changed(uint32_t serial, uint32_t flags) Q_DECL_OVERRIDE; private: - QString m_commit; + Qt::KeyboardModifiers modifiersToQtModifiers(uint32_t modifiers); + + QWaylandDisplay *m_display; + QWaylandInputMethodEventBuilder m_builder; + + QVector m_modifiersMap; uint32_t m_serial; - uint32_t m_resetSerial; + struct ::wl_surface *m_surface; + + QString m_preeditCommit; + + bool m_inputPanelVisible; + QRectF m_keyboardRectangle; + QLocale m_locale; + Qt::LayoutDirection m_inputDirection; + + struct ::wl_callback *m_resetCallback; + static const wl_callback_listener callbackListener; + static void resetCallback(void *data, struct wl_callback *wl_callback, uint32_t time); }; class QWaylandInputContext : public QPlatformInputContext @@ -91,25 +130,29 @@ class QWaylandInputContext : public QPlatformInputContext Q_OBJECT public: explicit QWaylandInputContext(QWaylandDisplay *display); + ~QWaylandInputContext(); bool isValid() const Q_DECL_OVERRIDE; void reset() Q_DECL_OVERRIDE; void commit() Q_DECL_OVERRIDE; void update(Qt::InputMethodQueries) Q_DECL_OVERRIDE; - void invokeAction(QInputMethod::Action, int cursorPosition) Q_DECL_OVERRIDE; void showInputPanel() Q_DECL_OVERRIDE; void hideInputPanel() Q_DECL_OVERRIDE; bool isInputPanelVisible() const Q_DECL_OVERRIDE; + QRectF keyboardRect() const Q_DECL_OVERRIDE; + + QLocale locale() const Q_DECL_OVERRIDE; + Qt::LayoutDirection inputDirection() const Q_DECL_OVERRIDE; void setFocusObject(QObject *object) Q_DECL_OVERRIDE; private: - bool ensureTextInput(); + QWaylandTextInput *textInput() const; QWaylandDisplay *mDisplay; - QScopedPointer mTextInput; + QPointer mCurrentWindow; }; } diff --git a/src/plugins/platforms/wayland/qwaylandinputdevice.cpp b/src/plugins/platforms/wayland/qwaylandinputdevice.cpp index 46c473e7075..cf1c7ac448e 100644 --- a/src/plugins/platforms/wayland/qwaylandinputdevice.cpp +++ b/src/plugins/platforms/wayland/qwaylandinputdevice.cpp @@ -50,6 +50,7 @@ #include "qwaylanddisplay_p.h" #include "qwaylandshmbackingstore_p.h" #include "../shared/qwaylandxkb.h" +#include "qwaylandinputcontext_p.h" #include #include @@ -185,6 +186,7 @@ QWaylandInputDevice::QWaylandInputDevice(QWaylandDisplay *display, int version, , mKeyboard(0) , mPointer(0) , mTouch(0) + , mTextInput(0) , mTime(0) , mSerial(0) , mTouchDevice(0) @@ -193,6 +195,9 @@ QWaylandInputDevice::QWaylandInputDevice(QWaylandDisplay *display, int version, mDataDevice = mQDisplay->dndSelectionHandler()->getDataDevice(this); } + if (mQDisplay->textInputManager()) { + mTextInput = new QWaylandTextInput(mQDisplay, mQDisplay->textInputManager()->get_text_input(wl_seat())); + } } QWaylandInputDevice::~QWaylandInputDevice() @@ -277,6 +282,16 @@ QWaylandDataDevice *QWaylandInputDevice::dataDevice() const return mDataDevice; } +void QWaylandInputDevice::setTextInput(QWaylandTextInput *textInput) +{ + mTextInput = textInput; +} + +QWaylandTextInput *QWaylandInputDevice::textInput() const +{ + return mTextInput; +} + void QWaylandInputDevice::removeMouseButtonFromState(Qt::MouseButton button) { if (mPointer) @@ -693,19 +708,9 @@ void QWaylandInputDevice::Keyboard::keyboard_key(uint32_t serial, uint32_t time, Qt::KeyboardModifiers modifiers = mParent->modifiers(); - uint utf32 = xkb_keysym_to_utf32(sym); - if (utf32) - text = QString::fromUcs4(&utf32, 1); - + text = QWaylandXkb::textFromKeysym(sym, modifiers); qtkey = QWaylandXkb::keysymToQtKey(sym, modifiers, text); - - // Map control + letter to proper text - if (utf32 >= 'A' && utf32 <= '~' && (modifiers & Qt::ControlModifier)) { - utf32 &= ~0x60; - text = QString::fromUcs4(&utf32, 1); - } - sendKey(window->window(), time, type, qtkey, modifiers, code, sym, mNativeModifiers, text); #else // Generic fallback for single hard keys: Assume 'key' is a Qt key code. diff --git a/src/plugins/platforms/wayland/qwaylandinputdevice_p.h b/src/plugins/platforms/wayland/qwaylandinputdevice_p.h index 0da45c38473..82b9d90c346 100644 --- a/src/plugins/platforms/wayland/qwaylandinputdevice_p.h +++ b/src/plugins/platforms/wayland/qwaylandinputdevice_p.h @@ -80,6 +80,7 @@ namespace QtWaylandClient { class QWaylandWindow; class QWaylandDisplay; class QWaylandDataDevice; +class QWaylandTextInput; class Q_WAYLAND_CLIENT_EXPORT QWaylandInputDevice : public QObject @@ -108,6 +109,9 @@ public: void setDataDevice(QWaylandDataDevice *device); QWaylandDataDevice *dataDevice() const; + void setTextInput(QWaylandTextInput *textInput); + QWaylandTextInput *textInput() const; + void removeMouseButtonFromState(Qt::MouseButton button); QWaylandWindow *pointerFocus() const; @@ -138,6 +142,8 @@ private: Pointer *mPointer; Touch *mTouch; + QWaylandTextInput *mTextInput; + uint32_t mTime; uint32_t mSerial; diff --git a/src/plugins/platforms/wayland/shared/qwaylandinputmethodeventbuilder.cpp b/src/plugins/platforms/wayland/shared/qwaylandinputmethodeventbuilder.cpp new file mode 100644 index 00000000000..eb527c15a0a --- /dev/null +++ b/src/plugins/platforms/wayland/shared/qwaylandinputmethodeventbuilder.cpp @@ -0,0 +1,286 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com +** Contact: http://www.qt-project.org/legal +** +** 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 "qwaylandinputmethodeventbuilder.h" + +#include +#include + +#ifdef QT_BUILD_WAYLANDCOMPOSITOR_LIB +#include +#else +#include +#endif + +QT_BEGIN_NAMESPACE + +QWaylandInputMethodEventBuilder::QWaylandInputMethodEventBuilder() + : m_anchor(0) + , m_cursor(0) + , m_deleteBefore(0) + , m_deleteAfter(0) + , m_preeditCursor(0) + , m_preeditStyles() +{ +} + +QWaylandInputMethodEventBuilder::~QWaylandInputMethodEventBuilder() +{ +} + +void QWaylandInputMethodEventBuilder::reset() +{ + m_anchor = 0; + m_cursor = 0; + m_deleteBefore = 0; + m_deleteAfter = 0; + m_preeditCursor = 0; + m_preeditStyles.clear(); +} + +void QWaylandInputMethodEventBuilder::setCursorPosition(int32_t index, int32_t anchor) +{ + m_cursor = index; + m_anchor = anchor; +} + +void QWaylandInputMethodEventBuilder::setDeleteSurroundingText(uint32_t beforeLength, uint32_t afterLength) +{ + m_deleteBefore = beforeLength; + m_deleteAfter = afterLength; +} + +void QWaylandInputMethodEventBuilder::addPreeditStyling(uint32_t index, uint32_t length, uint32_t style) +{ + QTextCharFormat format; + + switch (style) { + case 0: + case 1: + format.setFontUnderline(true); + format.setUnderlineStyle(QTextCharFormat::SingleUnderline); + m_preeditStyles.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, index, length, format)); + break; + case 2: + case 3: + format.setFontWeight(QFont::Bold); + format.setFontUnderline(true); + format.setUnderlineStyle(QTextCharFormat::SingleUnderline); + m_preeditStyles.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, index, length, format)); + break; + case 4: + format.setFontUnderline(true); + format.setUnderlineStyle(QTextCharFormat::SingleUnderline); + m_preeditStyles.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, index, length, format)); + case 5: + format.setFontUnderline(true); + format.setUnderlineStyle(QTextCharFormat::WaveUnderline); + format.setUnderlineColor(QColor(Qt::red)); + m_preeditStyles.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, index, length, format)); + break; +// case QtWayland::wl_text_input::preedit_style_selection: +// case QtWayland::wl_text_input::preedit_style_none: + default: + break; + } +} + +void QWaylandInputMethodEventBuilder::setPreeditCursor(int32_t index) +{ + m_preeditCursor = index; +} + +QInputMethodEvent QWaylandInputMethodEventBuilder::buildCommit(const QString &text) +{ + QList attributes; + + const QPair replacement = replacementForDeleteSurrounding(); + + if (m_cursor != 0 || m_anchor != 0) { + QString surrounding = QInputMethod::queryFocusObject(Qt::ImSurroundingText, QVariant()).toString(); + const int cursor = QInputMethod::queryFocusObject(Qt::ImCursorPosition, QVariant()).toInt(); + const int anchor = QInputMethod::queryFocusObject(Qt::ImAnchorPosition, QVariant()).toInt(); + const int absoluteCursor = QInputMethod::queryFocusObject(Qt::ImAbsolutePosition, QVariant()).toInt(); + + const int absoluteOffset = absoluteCursor - cursor; + + const int cursorAfterCommit = qMin(anchor, cursor) + replacement.first + text.length(); + surrounding.replace(qMin(anchor, cursor) + replacement.first, + qAbs(anchor - cursor) + replacement.second, text); + + attributes.push_back(QInputMethodEvent::Attribute(QInputMethodEvent::Selection, + indexFromWayland(surrounding, m_cursor, cursorAfterCommit) + absoluteOffset, + indexFromWayland(surrounding, m_anchor, cursorAfterCommit) + absoluteOffset, + QVariant())); + } + + QInputMethodEvent event(QString(), attributes); + event.setCommitString(text, replacement.first, replacement.second); + + return event; +} + +QInputMethodEvent QWaylandInputMethodEventBuilder::buildPreedit(const QString &text) +{ + QList attributes; + + if (m_preeditCursor < 0) { + attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, 0, 0, QVariant())); + } else if (m_preeditCursor > 0) { + attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, indexFromWayland(text, m_preeditCursor), 1, QVariant())); + } + + Q_FOREACH (const QInputMethodEvent::Attribute &attr, m_preeditStyles) { + int start = indexFromWayland(text, attr.start); + int length = indexFromWayland(text, attr.start + attr.length) - start; + attributes.append(QInputMethodEvent::Attribute(attr.type, start, length, attr.value)); + } + + QInputMethodEvent event(text, attributes); + + const QPair replacement = replacementForDeleteSurrounding(); + event.setCommitString(QString(), replacement.first, replacement.second); + + return event; +} + +QPair QWaylandInputMethodEventBuilder::replacementForDeleteSurrounding() +{ + if (m_deleteBefore == 0 && m_deleteAfter == 0) + return QPair(0, 0); + + const QString &surrounding = QInputMethod::queryFocusObject(Qt::ImSurroundingText, QVariant()).toString(); + const int cursor = QInputMethod::queryFocusObject(Qt::ImCursorPosition, QVariant()).toInt(); + const int anchor = QInputMethod::queryFocusObject(Qt::ImAnchorPosition, QVariant()).toInt(); + + const int selectionStart = qMin(cursor, anchor); + const int selectionEnd = qMax(cursor, anchor); + + const int deleteBefore = selectionStart - indexFromWayland(surrounding, -m_deleteBefore, selectionStart); + const int deleteAfter = indexFromWayland(surrounding, m_deleteAfter, selectionEnd) - selectionEnd; + + return QPair(-deleteBefore, deleteBefore + deleteAfter); +} + +QWaylandInputMethodContentType QWaylandInputMethodContentType::convert(Qt::InputMethodHints hints) +{ + uint32_t hint = ZWP_TEXT_INPUT_V2_CONTENT_HINT_NONE; + uint32_t purpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_NORMAL; + + if (hints & Qt::ImhHiddenText) { + hint |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_HIDDEN_TEXT; + } + if (hints & Qt::ImhSensitiveData) { + hint |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_SENSITIVE_DATA; + } + if ((hints & Qt::ImhNoAutoUppercase) == 0) { + hint |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_AUTO_CAPITALIZATION; + } + if (hints & Qt::ImhPreferNumbers) { + // Nothing yet + } + if (hints & Qt::ImhPreferUppercase) { + hint |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_UPPERCASE; + } + 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; + } + + if ((hints & Qt::ImhDate) && (hints & Qt::ImhTime) == 0) { + purpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_DATE; + } 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)) { + purpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_TIME; + } + + if (hints & Qt::ImhPreferLatin) { + hint |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_LATIN; + } + + if (hints & Qt::ImhMultiLine) { + hint |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_MULTILINE; + } + + if (hints & Qt::ImhDigitsOnly) { + purpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_DIGITS; + } + if (hints & Qt::ImhFormattedNumbersOnly) { + purpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_NUMBER; + } + if (hints & Qt::ImhUppercaseOnly) { + hint |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_UPPERCASE; + } + if (hints & Qt::ImhLowercaseOnly) { + hint |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_LOWERCASE; + } + if (hints & Qt::ImhDialableCharactersOnly) { + purpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_PHONE; + } + if (hints & Qt::ImhEmailCharactersOnly) { + purpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_EMAIL; + } + if (hints & Qt::ImhUrlCharactersOnly) { + purpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_URL; + } + if (hints & Qt::ImhLatinOnly) { + hint |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_LATIN; + } + return QWaylandInputMethodContentType{hint, purpose}; +} + +int QWaylandInputMethodEventBuilder::indexFromWayland(const QString &str, int utf8Index, int baseIndex) +{ + if (utf8Index == 0) + return baseIndex; + + if (utf8Index < 0) { + const QByteArray &utf8 = str.leftRef(baseIndex).toUtf8(); + return QString::fromUtf8(utf8.left(qMax(utf8.length() + utf8Index, 0))).length(); + } else { + const QByteArray &utf8 = str.midRef(baseIndex).toUtf8(); + return QString::fromUtf8(utf8.left(utf8Index)).length() + baseIndex; + } +} + +QT_END_NAMESPACE + diff --git a/src/plugins/platforms/wayland/shared/qwaylandinputmethodeventbuilder.h b/src/plugins/platforms/wayland/shared/qwaylandinputmethodeventbuilder.h new file mode 100644 index 00000000000..188a6a94b30 --- /dev/null +++ b/src/plugins/platforms/wayland/shared/qwaylandinputmethodeventbuilder.h @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com +** Contact: http://www.qt-project.org/legal +** +** 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 QWAYLANDINPUTMETHODEVENTBUILDER_H +#define QWAYLANDINPUTMETHODEVENTBUILDER_H + +#include + +QT_BEGIN_NAMESPACE + +class QWaylandInputMethodEventBuilder +{ +public: + QWaylandInputMethodEventBuilder(); + ~QWaylandInputMethodEventBuilder(); + + void reset(); + + void setCursorPosition(int32_t index, int32_t anchor); + void setDeleteSurroundingText(uint32_t beforeLength, uint32_t afterLength); + + void addPreeditStyling(uint32_t index, uint32_t length, uint32_t style); + void setPreeditCursor(int32_t index); + + QInputMethodEvent buildCommit(const QString &text); + QInputMethodEvent buildPreedit(const QString &text); + + static int indexFromWayland(const QString &str, int utf8Index, int baseIndex = 0); +private: + QPair replacementForDeleteSurrounding(); + + int32_t m_anchor; + int32_t m_cursor; + uint32_t m_deleteBefore; + uint32_t m_deleteAfter; + + int32_t m_preeditCursor; + QList m_preeditStyles; +}; + +struct QWaylandInputMethodContentType { + uint32_t hint; + uint32_t purpose; + + static QWaylandInputMethodContentType convert(Qt::InputMethodHints hints); +}; + + +QT_END_NAMESPACE + +#endif // QWAYLANDINPUTMETHODEVENTBUILDER_H diff --git a/src/plugins/platforms/wayland/shared/qwaylandxkb.cpp b/src/plugins/platforms/wayland/shared/qwaylandxkb.cpp index f5d0527de39..32d24bd62d1 100644 --- a/src/plugins/platforms/wayland/shared/qwaylandxkb.cpp +++ b/src/plugins/platforms/wayland/shared/qwaylandxkb.cpp @@ -40,6 +40,7 @@ #include "qwaylandxkb.h" +#include #include #ifndef QT_NO_WAYLAND_XKB @@ -282,6 +283,16 @@ static int lookupKeysym(xkb_keysym_t key) return code; } +static xkb_keysym_t toKeysymFromTable(uint32_t key) +{ + for (int i = 0; KeyTbl[i]; i += 2) { + if (key == KeyTbl[i + 1]) + return KeyTbl[i]; + } + + return 0; +} + int QWaylandXkb::keysymToQtKey(xkb_keysym_t keysym, Qt::KeyboardModifiers &modifiers, const QString &text) { int code = 0; @@ -326,6 +337,50 @@ Qt::KeyboardModifiers QWaylandXkb::modifiers(struct xkb_state *state) return modifiers; } +QEvent::Type QWaylandXkb::toQtEventType(uint32_t state) +{ + return state != 0 ? QEvent::KeyPress : QEvent::KeyRelease; +} + +QString QWaylandXkb::textFromKeysym(uint32_t keysym, Qt::KeyboardModifiers modifiers) +{ + uint utf32 = xkb_keysym_to_utf32(keysym); + + // Map control + letter to proper text + if (utf32 >= 'A' && utf32 <= '~' && (modifiers & Qt::ControlModifier)) { + utf32 &= ~0x60; + return QString::fromUcs4(&utf32, 1); + } + + if (utf32) + return QString::fromUcs4(&utf32, 1); + + return QString(); +} + +QVector QWaylandXkb::toKeysym(QKeyEvent *event) +{ + QVector keysyms; + if (event->key() >= Qt::Key_F1 && event->key() <= Qt::Key_F35) { + keysyms.append(XKB_KEY_F1 + (event->key() - Qt::Key_F1)); + } else if (event->modifiers() & Qt::KeypadModifier) { + if (event->key() >= Qt::Key_0 && event->key() <= Qt::Key_9) + keysyms.append(XKB_KEY_KP_0 + (event->key() - Qt::Key_0)); + else + keysyms.append(toKeysymFromTable(event->key())); + } else if (!event->text().isEmpty()) { + // From libxkbcommon keysym-utf.c: + // "We allow to represent any UCS character in the range U-00000000 to + // U-00FFFFFF by a keysym value in the range 0x01000000 to 0x01ffffff." + foreach (uint utf32, event->text().toUcs4()) { + keysyms.append(utf32 | 0x01000000); + } + } else { + keysyms.append(toKeysymFromTable(event->key())); + } + return keysyms; +} + QT_END_NAMESPACE #endif // QT_NO_WAYLAND_XKB diff --git a/src/plugins/platforms/wayland/shared/qwaylandxkb.h b/src/plugins/platforms/wayland/shared/qwaylandxkb.h index bfb38515f5f..9b5c935a5b5 100644 --- a/src/plugins/platforms/wayland/shared/qwaylandxkb.h +++ b/src/plugins/platforms/wayland/shared/qwaylandxkb.h @@ -44,15 +44,22 @@ #ifndef QT_NO_WAYLAND_XKB #include +#include #include QT_BEGIN_NAMESPACE +class QKeyEvent; + class QWaylandXkb { public: static int keysymToQtKey(xkb_keysym_t keysym, Qt::KeyboardModifiers &modifiers, const QString &text); static Qt::KeyboardModifiers modifiers(struct xkb_state *state); + + static QEvent::Type toQtEventType(uint32_t state); + static QString textFromKeysym(uint32_t keysym, Qt::KeyboardModifiers modifiers); + static QVector toKeysym(QKeyEvent *event); }; QT_END_NAMESPACE