New input method protocol for use with Qt clients

We introduce an alternative input-method protocol, which
is a one-to-one mapping to Qt's input method API. Input
methods such as the virtual keyboard's hunspell
integration is quite sensitive to the inner workings of
the input method handling, both in terms of when state
is updated and which updates are delivered when.

With a one-to-one mapping we are able to match these
expectations and keep a well-synchronized state.

Task-number: QTBUG-85135
Task-number: QTBUG-85134
Change-Id: Id69c22a7b0885ea59f39fdcc8d663749af56c7ce
Reviewed-by: Paul Olav Tvete <paul.tvete@qt.io>
This commit is contained in:
Eskil Abrahamsen Blomfeldt 2020-08-14 09:51:07 +02:00
parent a8b91863d3
commit 6578795ab5
11 changed files with 924 additions and 4 deletions

View File

@ -0,0 +1,301 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="qt_text_input_method_unstable_v1">
<copyright>
Copyright © 2020 The Qt Company Ltd.
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>
<interface name="qt_text_input_method_v1" version="1">
<description summary="text input">
The qt_text_input_method interface represents input method events
associated with a seat, and is intended to exactly match the
internal events of the Qt framework.
</description>
<request name="destroy" type="destructor">
<description summary="Destroy the qt_text_input_method">
Destroy the qt_text_input_method object.
</description>
</request>
<request name="enable">
<description summary="enable input methods for surface">
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.
</description>
<arg name="surface" type="object" interface="wl_surface"/>
</request>
<request name="disable">
<description summary="disable input methods for surface">
Disable text input in a surface (typically when there is no focus on any
text entry inside the surface).
</description>
<arg name="surface" type="object" interface="wl_surface"/>
</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.
</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 leave notification 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="key">
<description summary="key event">
A QKeyEvent has been sent from the input method.
</description>
<arg name="type" type="int" />
<arg name="key" type="int" />
<arg name="modifiers" type="int" />
<arg name="auto_repeat" type="int" />
<arg name="count" type="int" />
<arg name="native_scan_code" type="int" />
<arg name="native_virtual_key" type="int" />
<arg name="native_modifiers" type="int" />
<arg name="text" type="string" />
</event>
<event name="start_input_method_event">
<description summary="input method event">
Starts an input method event. This can be followed by
any number of input_method_event_attribute events
and will always be finished by an end_input_method_event.
</description>
<arg name="serial" type="uint" />
<arg name="surrounding_text_offset" type="int" />
</event>
<event name="input_method_event_attribute">
<description summary="input method event attribute">
Appends an attribute to the input method event with
the given serial. Must be preceded by a start_input_method_event
and concluded by a an end_input_method_event. See documentation
of QInputMethodEvent for details on the attributes.
</description>
<arg name="serial" type="uint" />
<arg name="type" type="int" />
<arg name="start" type="int" />
<arg name="length" type="int" />
<arg name="value" type="string" />
</event>
<event name="end_input_method_event">
<description summary="input method event end">
Concludes a previously started input method event. Together with
the preceding input_method_event_attribute events with the same
serial, this should be converted into a QInputMethodEvent.
</description>
<arg name="serial" type="uint" />
<arg name="commit_string" type="string" />
<arg name="preedit_string" type="string" />
<arg name="replacement_start" type="int" />
<arg name="replacement_length" type="int" />
</event>
<event name="visible_changed">
<description summary="visible changed">
Event to notify client that the visibility of the input method has
been changed.
</description>
<arg name="visible" type="int" />
</event>
<event name="keyboard_rectangle_changed">
<description summary="keyboard rectangle changed">
Event to notify client that the keyboard rectangle of the input method
has been changed.
</description>
<arg name="x" type="fixed" />
<arg name="y" type="fixed" />
<arg name="width" type="fixed" />
<arg name="height" type="fixed" />
</event>
<event name="locale_changed">
<description summary="keyboard rectangle changed">
Event to notify client that the locale of the input method
has been changed.
</description>
<arg name="locale_name" type="string" />
</event>
<event name="input_direction_changed">
<description summary="input direction changed">
Event to notify client that the input direction of the input method
has been changed.
</description>
<arg name="input_direction" type="int" />
</event>
<request name="reset">
<description summary="reset">
Request for the input method to reset. Corresponds to QInputMethod::reset().
</description>
</request>
<request name="commit">
<description summary="commit">
Request for the input method to commit its current content. Corresponds to QInputMethod::commit().
</description>
</request>
<request name="invoke_action">
<description summary="invoke action">
Passes a mouse click or context menu request from the client to the server. Corresponds to QInputMethod::invokeAction().
</description>
<arg name="type" type="int" />
<arg name="cursor_position" type="int" />
</request>
<request name="update_hints">
<description summary="update hints">
Notifies the server of the client's current input method hints.
</description>
<arg name="hints" type="int" />
</request>
<request name="update_cursor_rectangle">
<description summary="update cursor rectangle">
Notifies the server of the client's current cursor rectangle.
</description>
<arg name="x" type="int" />
<arg name="y" type="int" />
<arg name="width" type="int" />
<arg name="height" type="int" />
</request>
<request name="update_cursor_position">
<description summary="update cursor position">
Notifies the server of the client's current cursor position.
</description>
<arg name="cursor_position" type="int" />
</request>
<request name="update_surrounding_text">
<description summary="update surrounding text">
Notifies the server of the client's current surrounding text and its offset in the
complete text.
</description>
<arg name="surrounding_text" type="string" />
<arg name="text_offset" type="int" />
</request>
<request name="update_anchor_position">
<description summary="update anchor position">
Notifies the server of the client's current anchor position.
</description>
<arg name="anchor_position" type="int" />
</request>
<request name="update_absolute_position">
<description summary="update absolute position">
Notifies the server of the client's current absolute cursor position.
</description>
<arg name="absolute_position" type="int" />
</request>
<request name="update_preferred_language">
<description summary="update preferred language">
Notifies the server of the client's current preferred language.
</description>
<arg name="preferred_language" type="string" />
</request>
<request name="start_update">
<description summary="start update">
Starts an update sequence to notify the server that the client's state has
changed. This is followed by any number of update requests for specific
parts of the state and concluded by an end_update request.
</description>
<arg name="queries" type="int" />
</request>
<request name="end_update">
<description summary="end update">
Concludes the previously started update request.
</description>
</request>
<request name="show_input_panel">
<description summary="show input panel">
Requests that the input panel of the input method is visible.
</description>
</request>
<request name="hide_input_panel">
<description summary="hide input panel">
Requests that the input panel of the input method is not visible.
</description>
</request>
<request name="acknowledge_input_method">
<description summary="acknowledge input method">
Sent on receipt of an end_input_method_event to acknowledge that
the client has received and handled the event.
</description>
</request>
</interface>
<interface name="qt_text_input_method_manager_v1" version="1">
<description summary="qt text input method manager">
Manages qt_text_input_method objects.
</description>
<request name="destroy" type="destructor">
<description summary="Destroy the qt_text_input_method_manager">
Destroy the qt_text_input_method_manager object.
</description>
</request>
<request name="get_text_input_method">
<description summary="create a new text input method object">
Creates a new text-input-method object for a given seat.
</description>
<arg name="id" type="new_id" interface="qt_text_input_method_v1" />
<arg name="seat" type="object" interface="wl_seat" />
</request>
</interface>
</protocol>

View File

@ -30,6 +30,7 @@ qt_add_module(WaylandClient
qwaylandextendedsurface.cpp qwaylandextendedsurface_p.h qwaylandextendedsurface.cpp qwaylandextendedsurface_p.h
qwaylandinputcontext.cpp qwaylandinputcontext_p.h qwaylandinputcontext.cpp qwaylandinputcontext_p.h
qwaylandinputdevice.cpp qwaylandinputdevice_p.h qwaylandinputdevice.cpp qwaylandinputdevice_p.h
qwaylandinputmethodcontext.cpp qwaylandinputmethodcontext_p.h
qwaylandintegration.cpp qwaylandintegration_p.h qwaylandintegration.cpp qwaylandintegration_p.h
qwaylandnativeinterface.cpp qwaylandnativeinterface_p.h qwaylandnativeinterface.cpp qwaylandnativeinterface_p.h
qwaylandqtkey.cpp qwaylandqtkey_p.h qwaylandqtkey.cpp qwaylandqtkey_p.h
@ -56,7 +57,6 @@ qt_add_module(WaylandClient
LIBRARIES LIBRARIES
Qt::CorePrivate Qt::CorePrivate
Qt::GuiPrivate Qt::GuiPrivate
Qt::PlatformHeadersPrivate
PUBLIC_LIBRARIES PUBLIC_LIBRARIES
Qt::Core Qt::Core
Qt::Gui Qt::Gui
@ -74,6 +74,7 @@ qt6_generate_wayland_protocol_client_sources(WaylandClient
${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/wp-primary-selection-unstable-v1.xml ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/wp-primary-selection-unstable-v1.xml
${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/xdg-output-unstable-v1.xml ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/xdg-output-unstable-v1.xml
${CMAKE_CURRENT_SOURCE_DIR}/../extensions/qt-key-unstable-v1.xml ${CMAKE_CURRENT_SOURCE_DIR}/../extensions/qt-key-unstable-v1.xml
${CMAKE_CURRENT_SOURCE_DIR}/../extensions/qt-text-input-method-unstable-v1.xml
${CMAKE_CURRENT_SOURCE_DIR}/../extensions/qt-windowmanager.xml ${CMAKE_CURRENT_SOURCE_DIR}/../extensions/qt-windowmanager.xml
${CMAKE_CURRENT_SOURCE_DIR}/../extensions/surface-extension.xml ${CMAKE_CURRENT_SOURCE_DIR}/../extensions/surface-extension.xml
${CMAKE_CURRENT_SOURCE_DIR}/../extensions/touch-extension.xml ${CMAKE_CURRENT_SOURCE_DIR}/../extensions/touch-extension.xml

View File

@ -30,6 +30,7 @@ qt_add_module(WaylandClient
qwaylandextendedsurface.cpp qwaylandextendedsurface_p.h qwaylandextendedsurface.cpp qwaylandextendedsurface_p.h
qwaylandinputcontext.cpp qwaylandinputcontext_p.h qwaylandinputcontext.cpp qwaylandinputcontext_p.h
qwaylandinputdevice.cpp qwaylandinputdevice_p.h qwaylandinputdevice.cpp qwaylandinputdevice_p.h
qwaylandinputmethodcontext.cpp qwaylandinputmethodcontext_p.h
qwaylandintegration.cpp qwaylandintegration_p.h qwaylandintegration.cpp qwaylandintegration_p.h
qwaylandnativeinterface.cpp qwaylandnativeinterface_p.h qwaylandnativeinterface.cpp qwaylandnativeinterface_p.h
qwaylandqtkey.cpp qwaylandqtkey_p.h qwaylandqtkey.cpp qwaylandqtkey_p.h
@ -73,6 +74,7 @@ qt6_generate_wayland_protocol_client_sources(WaylandClient
${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/wp-primary-selection-unstable-v1.xml ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/wp-primary-selection-unstable-v1.xml
${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/xdg-output-unstable-v1.xml ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/xdg-output-unstable-v1.xml
${CMAKE_CURRENT_SOURCE_DIR}/../extensions/qt-key-unstable-v1.xml ${CMAKE_CURRENT_SOURCE_DIR}/../extensions/qt-key-unstable-v1.xml
${CMAKE_CURRENT_SOURCE_DIR}/../extensions/qt-text-input-method-unstable-v1.xml
${CMAKE_CURRENT_SOURCE_DIR}/../extensions/qt-windowmanager.xml ${CMAKE_CURRENT_SOURCE_DIR}/../extensions/qt-windowmanager.xml
${CMAKE_CURRENT_SOURCE_DIR}/../extensions/surface-extension.xml ${CMAKE_CURRENT_SOURCE_DIR}/../extensions/surface-extension.xml
${CMAKE_CURRENT_SOURCE_DIR}/../extensions/touch-extension.xml ${CMAKE_CURRENT_SOURCE_DIR}/../extensions/touch-extension.xml

View File

@ -22,6 +22,7 @@ WAYLANDCLIENTSOURCES += \
../extensions/touch-extension.xml \ ../extensions/touch-extension.xml \
../extensions/qt-key-unstable-v1.xml \ ../extensions/qt-key-unstable-v1.xml \
../extensions/qt-windowmanager.xml \ ../extensions/qt-windowmanager.xml \
../extensions/qt-text-input-method-unstable-v1.xml \
../3rdparty/protocol/wp-primary-selection-unstable-v1.xml \ ../3rdparty/protocol/wp-primary-selection-unstable-v1.xml \
../3rdparty/protocol/tablet-unstable-v2.xml \ ../3rdparty/protocol/tablet-unstable-v2.xml \
../3rdparty/protocol/text-input-unstable-v2.xml \ ../3rdparty/protocol/text-input-unstable-v2.xml \
@ -50,6 +51,7 @@ SOURCES += qwaylandintegration.cpp \
qwaylanddecorationplugin.cpp \ qwaylanddecorationplugin.cpp \
qwaylandwindowmanagerintegration.cpp \ qwaylandwindowmanagerintegration.cpp \
qwaylandinputcontext.cpp \ qwaylandinputcontext.cpp \
qwaylandinputmethodcontext.cpp \
qwaylandshm.cpp \ qwaylandshm.cpp \
qwaylandbuffer.cpp \ qwaylandbuffer.cpp \
@ -74,6 +76,7 @@ HEADERS += qwaylandintegration_p.h \
qwaylanddecorationplugin_p.h \ qwaylanddecorationplugin_p.h \
qwaylandwindowmanagerintegration_p.h \ qwaylandwindowmanagerintegration_p.h \
qwaylandinputcontext_p.h \ qwaylandinputcontext_p.h \
qwaylandinputmethodcontext_p.h \
qwaylandshm_p.h \ qwaylandshm_p.h \
qtwaylandclientglobal.h \ qtwaylandclientglobal.h \
qtwaylandclientglobal_p.h \ qtwaylandclientglobal_p.h \

View File

@ -61,6 +61,7 @@
#endif #endif
#include "qwaylandhardwareintegration_p.h" #include "qwaylandhardwareintegration_p.h"
#include "qwaylandinputcontext_p.h" #include "qwaylandinputcontext_p.h"
#include "qwaylandinputmethodcontext_p.h"
#include "qwaylandwindowmanagerintegration_p.h" #include "qwaylandwindowmanagerintegration_p.h"
#include "qwaylandshellintegration_p.h" #include "qwaylandshellintegration_p.h"
@ -74,6 +75,7 @@
#include <QtWaylandClient/private/qwayland-text-input-unstable-v2.h> #include <QtWaylandClient/private/qwayland-text-input-unstable-v2.h>
#include <QtWaylandClient/private/qwayland-wp-primary-selection-unstable-v1.h> #include <QtWaylandClient/private/qwayland-wp-primary-selection-unstable-v1.h>
#include <QtWaylandClient/private/qwayland-qt-text-input-method-unstable-v1.h>
#include <QtCore/private/qcore_unix_p.h> #include <QtCore/private/qcore_unix_p.h>
@ -339,6 +341,11 @@ void QWaylandDisplay::registry_global(uint32_t id, const QString &interface, uin
} else if (interface == QStringLiteral("zwp_primary_selection_device_manager_v1")) { } else if (interface == QStringLiteral("zwp_primary_selection_device_manager_v1")) {
mPrimarySelectionManager.reset(new QWaylandPrimarySelectionDeviceManagerV1(this, id, 1)); mPrimarySelectionManager.reset(new QWaylandPrimarySelectionDeviceManagerV1(this, id, 1));
#endif #endif
} else if (interface == QStringLiteral("qt_text_input_method_manager_v1") && !mClientSideInputContextRequested) {
mTextInputMethodManager.reset(new QtWayland::qt_text_input_method_manager_v1(registry, id, 1));
for (QWaylandInputDevice *inputDevice : qAsConst(mInputDevices))
inputDevice->setTextInputMethod(new QWaylandTextInputMethod(this, mTextInputMethodManager->get_text_input_method(inputDevice->wl_seat())));
mWaylandIntegration->reconfigureInputContext();
} else if (interface == QStringLiteral("zwp_text_input_manager_v2") && !mClientSideInputContextRequested) { } else if (interface == QStringLiteral("zwp_text_input_manager_v2") && !mClientSideInputContextRequested) {
mTextInputManager.reset(new QtWayland::zwp_text_input_manager_v2(registry, id, 1)); mTextInputManager.reset(new QtWayland::zwp_text_input_manager_v2(registry, id, 1));
for (QWaylandInputDevice *inputDevice : qAsConst(mInputDevices)) for (QWaylandInputDevice *inputDevice : qAsConst(mInputDevices))
@ -396,6 +403,12 @@ void QWaylandDisplay::registry_global_remove(uint32_t id)
inputDevice->setTextInput(nullptr); inputDevice->setTextInput(nullptr);
mWaylandIntegration->reconfigureInputContext(); mWaylandIntegration->reconfigureInputContext();
} }
if (global.interface == QStringLiteral("qt_text_input_method_manager_v1")) {
mTextInputMethodManager.reset();
for (QWaylandInputDevice *inputDevice : qAsConst(mInputDevices))
inputDevice->setTextInputMethod(nullptr);
mWaylandIntegration->reconfigureInputContext();
}
mGlobals.removeAt(i); mGlobals.removeAt(i);
break; break;
} }

View File

@ -81,6 +81,7 @@ class QPlatformPlaceholderScreen;
namespace QtWayland { namespace QtWayland {
class qt_surface_extension; class qt_surface_extension;
class zwp_text_input_manager_v2; class zwp_text_input_manager_v2;
class qt_text_input_method_manager_v1;
} }
namespace QtWaylandClient { namespace QtWaylandClient {
@ -163,6 +164,7 @@ public:
QtWayland::qt_surface_extension *windowExtension() const { return mWindowExtension.data(); } QtWayland::qt_surface_extension *windowExtension() const { return mWindowExtension.data(); }
QWaylandTabletManagerV2 *tabletManager() const { return mTabletManager.data(); } QWaylandTabletManagerV2 *tabletManager() const { return mTabletManager.data(); }
QWaylandTouchExtension *touchExtension() const { return mTouchExtension.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 *textInputManager() const { return mTextInputManager.data(); }
QWaylandHardwareIntegration *hardwareIntegration() const { return mHardwareIntegration.data(); } QWaylandHardwareIntegration *hardwareIntegration() const { return mHardwareIntegration.data(); }
QWaylandXdgOutputManagerV1 *xdgOutputManager() const { return mXdgOutputManager.data(); } QWaylandXdgOutputManagerV1 *xdgOutputManager() const { return mXdgOutputManager.data(); }
@ -268,6 +270,7 @@ private:
#if QT_CONFIG(wayland_client_primary_selection) #if QT_CONFIG(wayland_client_primary_selection)
QScopedPointer<QWaylandPrimarySelectionDeviceManagerV1> mPrimarySelectionManager; QScopedPointer<QWaylandPrimarySelectionDeviceManagerV1> mPrimarySelectionManager;
#endif #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> mTextInputManager;
QScopedPointer<QWaylandHardwareIntegration> mHardwareIntegration; QScopedPointer<QWaylandHardwareIntegration> mHardwareIntegration;
QScopedPointer<QWaylandXdgOutputManagerV1> mXdgOutputManager; QScopedPointer<QWaylandXdgOutputManagerV1> mXdgOutputManager;

View File

@ -57,6 +57,7 @@
#include "qwaylanddisplay_p.h" #include "qwaylanddisplay_p.h"
#include "qwaylandshmbackingstore_p.h" #include "qwaylandshmbackingstore_p.h"
#include "qwaylandinputcontext_p.h" #include "qwaylandinputcontext_p.h"
#include "qwaylandinputmethodcontext_p.h"
#include <QtGui/private/qpixmap_raster_p.h> #include <QtGui/private/qpixmap_raster_p.h>
#include <QtGui/private/qguiapplication_p.h> #include <QtGui/private/qguiapplication_p.h>
@ -420,6 +421,9 @@ QWaylandInputDevice::QWaylandInputDevice(QWaylandDisplay *display, int version,
if (mQDisplay->textInputManager()) if (mQDisplay->textInputManager())
mTextInput.reset(new QWaylandTextInput(mQDisplay, mQDisplay->textInputManager()->get_text_input(wl_seat()))); mTextInput.reset(new QWaylandTextInput(mQDisplay, mQDisplay->textInputManager()->get_text_input(wl_seat())));
if (mQDisplay->textInputMethodManager())
mTextInputMethod.reset(new QWaylandTextInputMethod(mQDisplay, mQDisplay->textInputMethodManager()->get_text_input_method(wl_seat())));
if (auto *tm = mQDisplay->tabletManager()) if (auto *tm = mQDisplay->tabletManager())
mTabletSeat.reset(new QWaylandTabletSeatV2(tm, this)); mTabletSeat.reset(new QWaylandTabletSeatV2(tm, this));
} }
@ -535,11 +539,21 @@ void QWaylandInputDevice::setTextInput(QWaylandTextInput *textInput)
mTextInput.reset(textInput); mTextInput.reset(textInput);
} }
void QWaylandInputDevice::setTextInputMethod(QWaylandTextInputMethod *textInputMethod)
{
mTextInputMethod.reset(textInputMethod);
}
QWaylandTextInput *QWaylandInputDevice::textInput() const QWaylandTextInput *QWaylandInputDevice::textInput() const
{ {
return mTextInput.data(); return mTextInput.data();
} }
QWaylandTextInputMethod *QWaylandInputDevice::textInputMethod() const
{
return mTextInputMethod.data();
}
void QWaylandInputDevice::removeMouseButtonFromState(Qt::MouseButton button) void QWaylandInputDevice::removeMouseButtonFromState(Qt::MouseButton button)
{ {
if (mPointer) if (mPointer)

View File

@ -91,6 +91,7 @@ class QWaylandPrimarySelectionDeviceV1;
#endif #endif
class QWaylandTabletSeatV2; class QWaylandTabletSeatV2;
class QWaylandTextInput; class QWaylandTextInput;
class QWaylandTextInputMethod;
#if QT_CONFIG(cursor) #if QT_CONFIG(cursor)
class QWaylandCursorTheme; class QWaylandCursorTheme;
class CursorSurface; class CursorSurface;
@ -134,6 +135,9 @@ public:
void setTextInput(QWaylandTextInput *textInput); void setTextInput(QWaylandTextInput *textInput);
QWaylandTextInput *textInput() const; QWaylandTextInput *textInput() const;
void setTextInputMethod(QWaylandTextInputMethod *textInputMethod);
QWaylandTextInputMethod *textInputMethod() const;
void removeMouseButtonFromState(Qt::MouseButton button); void removeMouseButtonFromState(Qt::MouseButton button);
QWaylandWindow *pointerFocus() const; QWaylandWindow *pointerFocus() const;
@ -187,6 +191,7 @@ private:
Touch *mTouch = nullptr; Touch *mTouch = nullptr;
QScopedPointer<QWaylandTextInput> mTextInput; QScopedPointer<QWaylandTextInput> mTextInput;
QScopedPointer<QWaylandTextInputMethod> mTextInputMethod;
QScopedPointer<QWaylandTabletSeatV2> mTabletSeat; QScopedPointer<QWaylandTabletSeatV2> mTabletSeat;
uint32_t mTime = 0; uint32_t mTime = 0;

View File

@ -0,0 +1,421 @@
/****************************************************************************
**
** Copyright (C) 2020 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 "qwaylandinputmethodcontext_p.h"
#include "qwaylanddisplay_p.h"
#include "qwaylandinputdevice_p.h"
#include <QtGui/qguiapplication.h>
#include <QtGui/qtextformat.h>
QT_BEGIN_NAMESPACE
Q_DECLARE_LOGGING_CATEGORY(qLcQpaInputMethods)
namespace QtWaylandClient {
static constexpr int maxStringSize = 1000; // actual max is 4096/3
QWaylandTextInputMethod::QWaylandTextInputMethod(QWaylandDisplay *display, struct ::qt_text_input_method_v1 *textInputMethod)
: QtWayland::qt_text_input_method_v1(textInputMethod)
, m_display(display)
{
}
QWaylandTextInputMethod::~QWaylandTextInputMethod()
{
}
void QWaylandTextInputMethod::text_input_method_v1_visible_changed(int32_t visible)
{
m_isVisible = visible;
}
void QWaylandTextInputMethod::text_input_method_v1_locale_changed(const QString &localeName)
{
m_locale = QLocale(localeName);
}
void QWaylandTextInputMethod::text_input_method_v1_input_direction_changed(int32_t inputDirection)
{
m_layoutDirection = Qt::LayoutDirection(inputDirection);
}
void QWaylandTextInputMethod::text_input_method_v1_keyboard_rectangle_changed(wl_fixed_t x, wl_fixed_t y, wl_fixed_t width, wl_fixed_t height)
{
m_keyboardRect = QRectF(wl_fixed_to_double(x),
wl_fixed_to_double(y),
wl_fixed_to_double(width),
wl_fixed_to_double(height));
}
void QWaylandTextInputMethod::text_input_method_v1_start_input_method_event(uint32_t serial, int32_t surrounding_text_offset)
{
if (m_pendingInputMethodEvents.contains(serial)) {
qCWarning(qLcQpaInputMethods) << "Input method event with serial" << serial << "already started";
return;
}
m_pendingInputMethodEvents[serial] = QList<QInputMethodEvent::Attribute>{};
m_offsetFromCompositor[serial] = surrounding_text_offset;
}
// We need to keep surrounding text below maxStringSize characters, with cursorPos centered in that substring
static int calculateOffset(const QString &text, int cursorPos)
{
int size = text.size();
int halfSize = maxStringSize/2;
if (size <= maxStringSize || cursorPos < halfSize)
return 0;
if (cursorPos > size - halfSize)
return size - maxStringSize;
return cursorPos - halfSize;
}
static QString mapSurroundingTextToCompositor(const QString &s, int offset)
{
return s.mid(offset, maxStringSize);
}
static int mapPositionToCompositor(int pos, int offset)
{
return pos - offset;
}
static int mapPositionFromCompositor(int pos, int offset)
{
return pos + offset;
}
void QWaylandTextInputMethod::text_input_method_v1_input_method_event_attribute(uint32_t serial, int32_t type, int32_t start, int32_t length, const QString &value)
{
if (!m_pendingInputMethodEvents.contains(serial)) {
qCWarning(qLcQpaInputMethods) << "Input method event with serial" << serial << "does not exist";
return;
}
int startMapped = mapPositionFromCompositor(start, m_offsetFromCompositor[serial]);
QList<QInputMethodEvent::Attribute> &attributes = m_pendingInputMethodEvents[serial];
switch (type) {
case QInputMethodEvent::Selection:
attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::AttributeType(type), startMapped, length));
break;
case QInputMethodEvent::Cursor:
attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::AttributeType(type), start, length, QColor(value)));
break;
case QInputMethodEvent::TextFormat:
{
QTextCharFormat textFormat;
textFormat.setProperty(QTextFormat::FontUnderline, true);
textFormat.setProperty(QTextFormat::TextUnderlineStyle, QTextCharFormat::SingleUnderline);
attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::AttributeType(type), start, length, textFormat));
break;
}
case QInputMethodEvent::Language:
case QInputMethodEvent::Ruby:
attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::AttributeType(type), start, length, value));
break;
};
}
void QWaylandTextInputMethod::sendInputState(QInputMethodQueryEvent *event, Qt::InputMethodQueries queries)
{
int cursorPosition = event->value(Qt::ImCursorPosition).toInt();
int anchorPosition = event->value(Qt::ImAnchorPosition).toInt();
QString surroundingText = event->value(Qt::ImSurroundingText).toString();
int offset = calculateOffset(surroundingText, cursorPosition);
if (queries & Qt::ImCursorPosition)
update_cursor_position(mapPositionToCompositor(cursorPosition, offset));
if (queries & Qt::ImSurroundingText)
update_surrounding_text(mapSurroundingTextToCompositor(surroundingText, offset), offset);
if (queries & Qt::ImAnchorPosition)
update_anchor_position(mapPositionToCompositor(anchorPosition, offset));
if (queries & Qt::ImAbsolutePosition)
update_absolute_position(event->value(Qt::ImAbsolutePosition).toInt()); // do not map: this is the position in the whole document
}
void QWaylandTextInputMethod::text_input_method_v1_end_input_method_event(uint32_t serial, const QString &commitString, const QString &preeditString, int32_t replacementStart, int32_t replacementLength)
{
if (!m_pendingInputMethodEvents.contains(serial)) {
qCWarning(qLcQpaInputMethods) << "Input method event with serial" << serial << "does not exist";
return;
}
QList<QInputMethodEvent::Attribute> attributes = m_pendingInputMethodEvents.take(serial);
m_offsetFromCompositor.remove(serial);
if (QGuiApplication::focusObject() != nullptr) {
QInputMethodEvent event(preeditString, attributes);
event.setCommitString(commitString, replacementStart, replacementLength);
QCoreApplication::sendEvent(QGuiApplication::focusObject(), &event);
}
// Send current state to make sure it matches
if (QGuiApplication::focusObject() != nullptr) {
QInputMethodQueryEvent event(Qt::ImCursorPosition | Qt::ImSurroundingText | Qt::ImAnchorPosition | Qt::ImAbsolutePosition);
QCoreApplication::sendEvent(QGuiApplication::focusObject(), &event);
sendInputState(&event);
}
acknowledge_input_method();
}
void QWaylandTextInputMethod::text_input_method_v1_key(int32_t type,
int32_t key,
int32_t modifiers,
int32_t autoRepeat,
int32_t count,
int32_t nativeScanCode,
int32_t nativeVirtualKey,
int32_t nativeModifiers,
const QString &text)
{
if (QGuiApplication::focusObject() != nullptr) {
QKeyEvent event(QKeyEvent::Type(type),
key,
Qt::KeyboardModifiers(modifiers),
nativeScanCode,
nativeVirtualKey,
nativeModifiers,
text,
autoRepeat,
count);
QCoreApplication::sendEvent(QGuiApplication::focusObject(), &event);
}
}
void QWaylandTextInputMethod::text_input_method_v1_enter(struct ::wl_surface *surface)
{
m_surface = surface;
}
void QWaylandTextInputMethod::text_input_method_v1_leave(struct ::wl_surface *surface)
{
if (surface != m_surface) {
qCWarning(qLcQpaInputMethods) << "Got leave event for surface without corresponding enter";
} else {
m_surface = nullptr;
}
}
QWaylandInputMethodContext::QWaylandInputMethodContext(QWaylandDisplay *display)
: m_display(display)
{
}
QWaylandInputMethodContext::~QWaylandInputMethodContext()
{
}
bool QWaylandInputMethodContext::isValid() const
{
return m_display->textInputMethodManager() != nullptr;
}
void QWaylandInputMethodContext::reset()
{
QWaylandTextInputMethod *inputMethod = textInputMethod();
if (inputMethod != nullptr)
inputMethod->reset();
}
void QWaylandInputMethodContext::commit()
{
QWaylandTextInputMethod *inputMethod = textInputMethod();
if (inputMethod != nullptr)
inputMethod->commit();
m_display->forceRoundTrip();
}
void QWaylandInputMethodContext::update(Qt::InputMethodQueries queries)
{
wl_surface *currentSurface = m_currentWindow != nullptr && m_currentWindow->handle() != nullptr
? static_cast<QWaylandWindow *>(m_currentWindow->handle())->wlSurface()
: nullptr;
if (currentSurface != nullptr && !inputMethodAccepted()) {
textInputMethod()->disable(currentSurface);
m_currentWindow.clear();
} else if (currentSurface == nullptr && inputMethodAccepted()) {
QWindow *window = QGuiApplication::focusWindow();
currentSurface = window != nullptr && window->handle() != nullptr
? static_cast<QWaylandWindow *>(window->handle())->wlSurface()
: nullptr;
if (currentSurface != nullptr) {
textInputMethod()->disable(currentSurface);
m_currentWindow = window;
}
}
queries &= (Qt::ImEnabled
| Qt::ImHints
| Qt::ImCursorRectangle
| Qt::ImCursorPosition
| Qt::ImSurroundingText
| Qt::ImCurrentSelection
| Qt::ImAnchorPosition
| Qt::ImTextAfterCursor
| Qt::ImTextBeforeCursor
| Qt::ImPreferredLanguage);
const Qt::InputMethodQueries queriesNeedingOffset = Qt::ImCursorPosition | Qt::ImSurroundingText | Qt::ImAnchorPosition;
if (queries & queriesNeedingOffset)
queries |= queriesNeedingOffset;
QWaylandTextInputMethod *inputMethod = textInputMethod();
if (inputMethod != nullptr && QGuiApplication::focusObject() != nullptr) {
QInputMethodQueryEvent event(queries);
QCoreApplication::sendEvent(QGuiApplication::focusObject(), &event);
inputMethod->start_update(int(queries));
if (queries & Qt::ImHints)
inputMethod->update_hints(event.value(Qt::ImHints).toInt());
if (queries & Qt::ImCursorRectangle) {
QRect rect = event.value(Qt::ImCursorRectangle).toRect();
inputMethod->update_cursor_rectangle(rect.x(), rect.y(), rect.width(), rect.height());
}
inputMethod->sendInputState(&event, queries);
if (queries & Qt::ImPreferredLanguage)
inputMethod->update_preferred_language(event.value(Qt::ImPreferredLanguage).toString());
inputMethod->end_update();
// ### Should we do a display sync here and ignore all events until it is received?
}
}
void QWaylandInputMethodContext::invokeAction(QInputMethod::Action action, int cursorPosition)
{
QWaylandTextInputMethod *inputMethod = textInputMethod();
if (inputMethod != nullptr)
inputMethod->invoke_action(int(action), cursorPosition);
}
void QWaylandInputMethodContext::showInputPanel()
{
QWaylandTextInputMethod *inputMethod = textInputMethod();
if (inputMethod != nullptr)
inputMethod->show_input_panel();
}
void QWaylandInputMethodContext::hideInputPanel()
{
QWaylandTextInputMethod *inputMethod = textInputMethod();
if (inputMethod != nullptr)
inputMethod->hide_input_panel();
}
bool QWaylandInputMethodContext::isInputPanelVisible() const
{
QWaylandTextInputMethod *inputMethod = textInputMethod();
if (inputMethod != nullptr)
return inputMethod->isVisible();
else
return false;
}
QRectF QWaylandInputMethodContext::keyboardRect() const
{
QWaylandTextInputMethod *inputMethod = textInputMethod();
if (inputMethod != nullptr)
return inputMethod->keyboardRect();
else
return QRectF();
}
QLocale QWaylandInputMethodContext::locale() const
{
QWaylandTextInputMethod *inputMethod = textInputMethod();
if (inputMethod != nullptr)
return inputMethod->locale();
else
return QLocale();
}
Qt::LayoutDirection QWaylandInputMethodContext::inputDirection() const
{
QWaylandTextInputMethod *inputMethod = textInputMethod();
if (inputMethod != nullptr)
return inputMethod->inputDirection();
else
return Qt::LeftToRight;
}
void QWaylandInputMethodContext::setFocusObject(QObject *)
{
QWaylandTextInputMethod *inputMethod = textInputMethod();
if (inputMethod == nullptr)
return;
QWindow *window = QGuiApplication::focusWindow();
if (m_currentWindow != nullptr && m_currentWindow->handle() != nullptr) {
if (m_currentWindow.data() != window || !inputMethodAccepted()) {
auto *surface = static_cast<QWaylandWindow *>(m_currentWindow->handle())->wlSurface();
if (surface)
inputMethod->disable(surface);
m_currentWindow.clear();
}
}
if (window != nullptr && window->handle() != nullptr && inputMethodAccepted()) {
if (m_currentWindow.data() != window) {
auto *surface = static_cast<QWaylandWindow *>(window->handle())->wlSurface();
if (surface != nullptr) {
inputMethod->enable(surface);
m_currentWindow = window;
}
}
}
}
QWaylandTextInputMethod *QWaylandInputMethodContext::textInputMethod() const
{
return m_display->defaultInputDevice()->textInputMethod();
}
} // QtWaylandClient
QT_END_NAMESPACE

View File

@ -0,0 +1,152 @@
/****************************************************************************
**
** Copyright (C) 2020 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 QWAYLANDINPUTMETHODCONTEXT_P_H
#define QWAYLANDINPUTMETHODCONTEXT_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 <QtGui/qpa/qplatforminputcontext.h>
#include <QtGui/qevent.h>
#include <QtCore/qlocale.h>
#include <QtCore/qpointer.h>
#include <QtWaylandClient/private/qwayland-qt-text-input-method-unstable-v1.h>
QT_BEGIN_NAMESPACE
namespace QtWaylandClient {
class QWaylandDisplay;
class QWaylandTextInputMethod : public QtWayland::qt_text_input_method_v1
{
public:
QWaylandTextInputMethod(QWaylandDisplay *display, struct ::qt_text_input_method_v1 *textInputMethod);
~QWaylandTextInputMethod() override;
void text_input_method_v1_visible_changed(int32_t visible) override;
void text_input_method_v1_enter(struct ::wl_surface *surface) override;
void text_input_method_v1_leave(struct ::wl_surface *surface) override;
void text_input_method_v1_locale_changed(const QString &localeName) override;
void text_input_method_v1_input_direction_changed(int32_t inputDirection) override;
void text_input_method_v1_keyboard_rectangle_changed(wl_fixed_t x, wl_fixed_t y, wl_fixed_t width, wl_fixed_t height) override;
void text_input_method_v1_key(int32_t type, int32_t key, int32_t modifiers, int32_t autoRepeat, int32_t count, int32_t nativeScanCode, int32_t nativeVirtualKey, int32_t nativeModifiers, const QString &text) override;
void text_input_method_v1_start_input_method_event(uint32_t serial, int32_t surrounding_text_offset) override;
void text_input_method_v1_end_input_method_event(uint32_t serial, const QString &commitString, const QString &preeditString, int32_t replacementStart, int32_t replacementLength) override;
void text_input_method_v1_input_method_event_attribute(uint32_t serial, int32_t type, int32_t start, int32_t length, const QString &value) override;
inline bool isVisible() const
{
return m_isVisible;
}
inline QRectF keyboardRect() const
{
return m_keyboardRect;
}
inline QLocale locale() const
{
return m_locale;
}
inline Qt::LayoutDirection inputDirection() const
{
return m_layoutDirection;
}
void sendInputState(QInputMethodQueryEvent *state, Qt::InputMethodQueries queries = Qt::ImQueryInput);
private:
QWaylandDisplay *m_display;
QHash<int, QList<QInputMethodEvent::Attribute> > m_pendingInputMethodEvents;
QHash<int,int> m_offsetFromCompositor;
struct ::wl_surface *m_surface;
// Cached state
bool m_isVisible = false;
QRectF m_keyboardRect;
QLocale m_locale;
Qt::LayoutDirection m_layoutDirection;
};
class QWaylandInputMethodContext : public QPlatformInputContext
{
public:
QWaylandInputMethodContext(QWaylandDisplay *display);
~QWaylandInputMethodContext() override;
bool isValid() const override;
void reset() override;
void commit() override;
void update(Qt::InputMethodQueries) override;
void invokeAction(QInputMethod::Action, int cursorPosition) override;
void showInputPanel() override;
void hideInputPanel() override;
bool isInputPanelVisible() const override;
QRectF keyboardRect() const override;
QLocale locale() const override;
Qt::LayoutDirection inputDirection() const override;
void setFocusObject(QObject *object) override;
private:
QWaylandTextInputMethod *textInputMethod() const;
QWaylandDisplay *m_display;
QPointer<QWindow> m_currentWindow;
};
} // QtWaylandClient
QT_END_NAMESPACE
#endif // QWAYLANDINPUTMETHODCONTEXT_P_H

View File

@ -43,6 +43,7 @@
#include "qwaylandshmwindow_p.h" #include "qwaylandshmwindow_p.h"
#include "qwaylandinputdevice_p.h" #include "qwaylandinputdevice_p.h"
#include "qwaylandinputcontext_p.h" #include "qwaylandinputcontext_p.h"
#include "qwaylandinputmethodcontext_p.h"
#include "qwaylandshmbackingstore_p.h" #include "qwaylandshmbackingstore_p.h"
#include "qwaylandnativeinterface_p.h" #include "qwaylandnativeinterface_p.h"
#if QT_CONFIG(clipboard) #if QT_CONFIG(clipboard)
@ -465,10 +466,14 @@ void QWaylandIntegration::reconfigureInputContext()
qCWarning(lcQpaWayland) << "qtvirtualkeyboard currently is not supported at client-side," qCWarning(lcQpaWayland) << "qtvirtualkeyboard currently is not supported at client-side,"
" use QT_IM_MODULE=qtvirtualkeyboard at compositor-side."; " use QT_IM_MODULE=qtvirtualkeyboard at compositor-side.";
if (requested.isNull()) if (requested.isNull()) {
mInputContext.reset(new QWaylandInputContext(mDisplay.data())); if (mDisplay->textInputMethodManager() != nullptr)
else mInputContext.reset(new QWaylandInputMethodContext(mDisplay.data()));
else
mInputContext.reset(new QWaylandInputContext(mDisplay.data()));
} else {
mInputContext.reset(QPlatformInputContextFactory::create(requested)); mInputContext.reset(QPlatformInputContextFactory::create(requested));
}
const QString defaultInputContext(QStringLiteral("compose")); const QString defaultInputContext(QStringLiteral("compose"));
if ((!mInputContext || !mInputContext->isValid()) && requested != defaultInputContext) if ((!mInputContext || !mInputContext->isValid()) && requested != defaultInputContext)