2020-08-02 22:22:19 +00:00
|
|
|
/*
|
|
|
|
KWin - the KDE window manager
|
|
|
|
This file is part of the KDE project.
|
2016-04-29 13:05:03 +00:00
|
|
|
|
2020-08-02 22:22:19 +00:00
|
|
|
SPDX-FileCopyrightText: 2016 Martin Gräßlin <mgraesslin@kde.org>
|
2016-04-29 13:05:03 +00:00
|
|
|
|
2020-08-02 22:22:19 +00:00
|
|
|
SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
*/
|
2020-09-29 14:46:32 +00:00
|
|
|
#include "inputmethod.h"
|
2022-03-23 10:13:38 +00:00
|
|
|
|
|
|
|
#include <config-kwin.h>
|
|
|
|
|
2016-08-03 06:31:58 +00:00
|
|
|
#include "input.h"
|
2022-04-22 17:42:35 +00:00
|
|
|
#include "inputpanelv1window.h"
|
2017-08-16 19:18:01 +00:00
|
|
|
#include "keyboard_input.h"
|
2022-03-23 10:13:38 +00:00
|
|
|
#include "utils/common.h"
|
|
|
|
#include "virtualkeyboard_dbus.h"
|
2016-04-29 13:05:03 +00:00
|
|
|
#include "wayland_server.h"
|
2022-04-22 17:39:12 +00:00
|
|
|
#include "window.h"
|
2016-04-29 13:05:03 +00:00
|
|
|
#include "workspace.h"
|
2022-02-28 18:58:35 +00:00
|
|
|
#if KWIN_BUILD_SCREENLOCKER
|
2020-02-06 09:33:23 +00:00
|
|
|
#include "screenlockerwatcher.h"
|
2022-02-23 13:27:05 +00:00
|
|
|
#endif
|
2022-02-11 18:12:48 +00:00
|
|
|
#include "tablet_input.h"
|
2022-03-23 10:13:38 +00:00
|
|
|
#include "touch_input.h"
|
2022-04-22 09:27:33 +00:00
|
|
|
#include "wayland/display.h"
|
|
|
|
#include "wayland/inputmethod_v1_interface.h"
|
|
|
|
#include "wayland/keyboard_interface.h"
|
|
|
|
#include "wayland/seat_interface.h"
|
|
|
|
#include "wayland/surface_interface.h"
|
2023-01-07 20:56:49 +00:00
|
|
|
#include "wayland/textinput_v1_interface.h"
|
2022-04-22 09:27:33 +00:00
|
|
|
#include "wayland/textinput_v3_interface.h"
|
2022-10-31 21:14:40 +00:00
|
|
|
#include "xkb.h"
|
2016-04-29 13:05:03 +00:00
|
|
|
|
2022-03-23 10:13:38 +00:00
|
|
|
#include <KLocalizedString>
|
|
|
|
#include <KShell>
|
2022-05-17 11:53:13 +00:00
|
|
|
#include <KKeyServer>
|
2016-04-29 13:05:03 +00:00
|
|
|
|
|
|
|
#include <QDBusConnection>
|
|
|
|
#include <QDBusMessage>
|
2022-03-23 10:13:38 +00:00
|
|
|
#include <QDBusPendingCall>
|
2021-06-01 16:15:47 +00:00
|
|
|
#include <QKeyEvent>
|
2022-03-23 10:13:38 +00:00
|
|
|
#include <QMenu>
|
2016-04-29 13:05:03 +00:00
|
|
|
|
2020-09-25 06:51:04 +00:00
|
|
|
#include <linux/input-event-codes.h>
|
2021-04-27 15:49:55 +00:00
|
|
|
#include <unistd.h>
|
2022-03-23 10:13:38 +00:00
|
|
|
#include <xkbcommon/xkbcommon-keysyms.h>
|
2020-09-25 06:51:04 +00:00
|
|
|
|
2020-04-29 15:18:41 +00:00
|
|
|
using namespace KWaylandServer;
|
2016-04-29 13:05:03 +00:00
|
|
|
|
|
|
|
namespace KWin
|
|
|
|
{
|
|
|
|
|
2022-05-17 11:53:13 +00:00
|
|
|
static std::vector<quint32> textToKey(const QString &text)
|
|
|
|
{
|
|
|
|
if (text.isEmpty()) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
auto sequence = QKeySequence::fromString(text);
|
|
|
|
if (sequence.isEmpty()) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
int sym;
|
|
|
|
if (!KKeyServer::keyQtToSymX(sequence[0], &sym)) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
auto keyCode = KWin::input()->keyboard()->xkb()->keycodeFromKeysym(sym);
|
|
|
|
if (!keyCode) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (text.isUpper()) {
|
|
|
|
return {KEY_LEFTSHIFT, quint32(keyCode.value())};
|
|
|
|
}
|
|
|
|
|
|
|
|
return {quint32(keyCode.value())};
|
|
|
|
}
|
|
|
|
|
2022-07-20 10:14:27 +00:00
|
|
|
InputMethod::InputMethod()
|
2016-04-29 13:05:03 +00:00
|
|
|
{
|
2021-07-07 00:42:06 +00:00
|
|
|
m_enabled = kwinApp()->config()->group("Wayland").readEntry("VirtualKeyboardEnabled", true);
|
2016-04-29 13:05:03 +00:00
|
|
|
// this is actually too late. Other processes are started before init,
|
|
|
|
// so might miss the availability of text input
|
|
|
|
// but without Workspace we don't have the window listed at all
|
2021-04-27 15:49:55 +00:00
|
|
|
if (workspace()) {
|
|
|
|
init();
|
|
|
|
} else {
|
|
|
|
connect(kwinApp(), &Application::workspaceCreated, this, &InputMethod::init);
|
|
|
|
}
|
2016-04-29 13:05:03 +00:00
|
|
|
}
|
|
|
|
|
2021-10-14 07:40:08 +00:00
|
|
|
InputMethod::~InputMethod()
|
|
|
|
{
|
|
|
|
stopInputMethod();
|
|
|
|
}
|
2016-04-29 13:05:03 +00:00
|
|
|
|
2020-09-29 14:46:32 +00:00
|
|
|
void InputMethod::init()
|
2016-04-29 13:05:03 +00:00
|
|
|
{
|
2021-04-27 15:49:55 +00:00
|
|
|
// Stop restarting the input method if it starts crashing very frequently
|
|
|
|
m_inputMethodCrashTimer.setInterval(20000);
|
|
|
|
m_inputMethodCrashTimer.setSingleShot(true);
|
|
|
|
connect(&m_inputMethodCrashTimer, &QTimer::timeout, this, [this] {
|
|
|
|
m_inputMethodCrashes = 0;
|
|
|
|
});
|
2022-02-28 18:58:35 +00:00
|
|
|
#if KWIN_BUILD_SCREENLOCKER
|
2022-07-31 12:40:08 +00:00
|
|
|
connect(kwinApp()->screenLockerWatcher(), &ScreenLockerWatcher::aboutToLock, this, &InputMethod::hide);
|
2022-02-23 13:27:05 +00:00
|
|
|
#endif
|
2020-02-06 09:33:23 +00:00
|
|
|
|
2021-02-26 15:16:45 +00:00
|
|
|
new VirtualKeyboardDBus(this);
|
2018-12-10 10:57:24 +00:00
|
|
|
qCDebug(KWIN_VIRTUALKEYBOARD) << "Registering the DBus interface";
|
2016-04-29 13:05:03 +00:00
|
|
|
|
|
|
|
if (waylandServer()) {
|
2023-02-17 01:32:55 +00:00
|
|
|
new TextInputManagerV1Interface(waylandServer()->display(), this);
|
|
|
|
new TextInputManagerV2Interface(waylandServer()->display(), this);
|
|
|
|
new TextInputManagerV3Interface(waylandServer()->display(), this);
|
2020-09-25 06:51:04 +00:00
|
|
|
|
2020-09-29 14:46:32 +00:00
|
|
|
connect(waylandServer()->seat(), &SeatInterface::focusedTextInputSurfaceChanged, this, &InputMethod::handleFocusedSurfaceChanged);
|
2020-09-23 10:02:54 +00:00
|
|
|
|
2023-01-07 20:56:49 +00:00
|
|
|
TextInputV1Interface *textInputV1 = waylandServer()->seat()->textInputV1();
|
|
|
|
connect(textInputV1, &TextInputV1Interface::requestShowInputPanel, this, &InputMethod::show);
|
|
|
|
connect(textInputV1, &TextInputV1Interface::requestHideInputPanel, this, &InputMethod::hide);
|
|
|
|
connect(textInputV1, &TextInputV1Interface::surroundingTextChanged, this, &InputMethod::surroundingTextChanged);
|
|
|
|
connect(textInputV1, &TextInputV1Interface::contentTypeChanged, this, &InputMethod::contentTypeChanged);
|
|
|
|
connect(textInputV1, &TextInputV1Interface::stateUpdated, this, &InputMethod::textInputInterfaceV1StateUpdated);
|
|
|
|
connect(textInputV1, &TextInputV1Interface::reset, this, &InputMethod::textInputInterfaceV1Reset);
|
|
|
|
connect(textInputV1, &TextInputV1Interface::invokeAction, this, &InputMethod::invokeAction);
|
|
|
|
connect(textInputV1, &TextInputV1Interface::enabledChanged, this, &InputMethod::textInputInterfaceV1EnabledChanged);
|
|
|
|
|
2020-09-23 10:02:54 +00:00
|
|
|
TextInputV2Interface *textInputV2 = waylandServer()->seat()->textInputV2();
|
2020-09-29 14:46:32 +00:00
|
|
|
connect(textInputV2, &TextInputV2Interface::requestShowInputPanel, this, &InputMethod::show);
|
|
|
|
connect(textInputV2, &TextInputV2Interface::requestHideInputPanel, this, &InputMethod::hide);
|
|
|
|
connect(textInputV2, &TextInputV2Interface::surroundingTextChanged, this, &InputMethod::surroundingTextChanged);
|
|
|
|
connect(textInputV2, &TextInputV2Interface::contentTypeChanged, this, &InputMethod::contentTypeChanged);
|
2020-10-07 04:46:46 +00:00
|
|
|
connect(textInputV2, &TextInputV2Interface::stateUpdated, this, &InputMethod::textInputInterfaceV2StateUpdated);
|
2022-03-08 01:59:56 +00:00
|
|
|
connect(textInputV2, &TextInputV2Interface::enabledChanged, this, &InputMethod::textInputInterfaceV2EnabledChanged);
|
2020-09-25 06:51:04 +00:00
|
|
|
|
|
|
|
TextInputV3Interface *textInputV3 = waylandServer()->seat()->textInputV3();
|
2020-09-29 14:46:32 +00:00
|
|
|
connect(textInputV3, &TextInputV3Interface::surroundingTextChanged, this, &InputMethod::surroundingTextChanged);
|
|
|
|
connect(textInputV3, &TextInputV3Interface::contentTypeChanged, this, &InputMethod::contentTypeChanged);
|
|
|
|
connect(textInputV3, &TextInputV3Interface::stateCommitted, this, &InputMethod::stateCommitted);
|
2022-03-08 01:59:56 +00:00
|
|
|
connect(textInputV3, &TextInputV3Interface::enabledChanged, this, &InputMethod::textInputInterfaceV3EnabledChanged);
|
2022-10-24 23:38:40 +00:00
|
|
|
connect(textInputV3, &TextInputV3Interface::enableRequested, this, &InputMethod::textInputInterfaceV3EnableRequested);
|
2021-12-13 17:26:01 +00:00
|
|
|
|
2021-12-29 17:19:43 +00:00
|
|
|
connect(input()->keyboard()->xkb(), &Xkb::modifierStateChanged, this, [this]() {
|
|
|
|
m_hasPendingModifiers = true;
|
|
|
|
});
|
2016-04-29 13:05:03 +00:00
|
|
|
}
|
2020-07-27 19:30:49 +00:00
|
|
|
}
|
virtualkeyboard: resize the focused window to make room for the keyboard
Summary:
alternative approach: try to resize the winidow to make room for the keyboard.
the new input wayland protocol doesn't have anymore the overlap rectangle (and it would not be going to work with qwidget apps anyways)
in the future will probably be needed anextension to the input protocol v3 which partially gets back this, tough window resizing is needed regardless
what's missing: the resize should be "temporary" and the window should be restored to its previous geometry when the keyboard closes
Test Plan: tested with test QML code
Reviewers: #plasma, #kwin, bshah, graesslin, romangg, davidedmundson
Reviewed By: #plasma, #kwin, romangg, davidedmundson
Subscribers: nicolasfella, mart, kwin, davidedmundson, graesslin
Tags: #kwin
Maniphest Tasks: T9815
Differential Revision: https://phabricator.kde.org/D18818
2019-03-20 10:04:51 +00:00
|
|
|
|
2020-09-29 14:46:32 +00:00
|
|
|
void InputMethod::show()
|
2020-07-27 19:30:49 +00:00
|
|
|
{
|
2022-09-21 12:17:06 +00:00
|
|
|
m_shouldShowPanel = true;
|
2022-04-23 08:33:23 +00:00
|
|
|
if (m_panel) {
|
2022-09-21 12:17:06 +00:00
|
|
|
m_panel->show();
|
2021-12-22 07:03:31 +00:00
|
|
|
updateInputPanelState();
|
2023-03-09 03:45:55 +00:00
|
|
|
} else if (isActive()) {
|
|
|
|
adoptInputMethodContext();
|
2021-12-22 07:03:31 +00:00
|
|
|
}
|
2021-02-23 15:12:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void InputMethod::hide()
|
|
|
|
{
|
2022-09-21 12:17:06 +00:00
|
|
|
m_shouldShowPanel = false;
|
2022-04-23 08:33:23 +00:00
|
|
|
if (m_panel) {
|
2022-09-21 12:17:06 +00:00
|
|
|
m_panel->hide();
|
2021-12-22 07:03:31 +00:00
|
|
|
updateInputPanelState();
|
|
|
|
}
|
2021-02-23 15:12:38 +00:00
|
|
|
}
|
|
|
|
|
2021-11-30 14:52:47 +00:00
|
|
|
bool InputMethod::shouldShowOnActive() const
|
2021-02-23 15:12:38 +00:00
|
|
|
{
|
2022-07-07 14:43:49 +00:00
|
|
|
static bool alwaysShowIm = qEnvironmentVariableIntValue("KWIN_IM_SHOW_ALWAYS") != 0;
|
|
|
|
return alwaysShowIm || input()->touch() == input()->lastInputHandler()
|
2022-02-11 18:12:48 +00:00
|
|
|
|| input()->tablet() == input()->lastInputHandler();
|
2021-10-13 16:24:59 +00:00
|
|
|
}
|
|
|
|
|
2022-10-16 05:03:32 +00:00
|
|
|
void InputMethod::refreshActive()
|
|
|
|
{
|
|
|
|
auto seat = waylandServer()->seat();
|
2023-01-07 20:56:49 +00:00
|
|
|
auto t1 = seat->textInputV1();
|
2022-10-16 05:03:32 +00:00
|
|
|
auto t2 = seat->textInputV2();
|
|
|
|
auto t3 = seat->textInputV3();
|
|
|
|
|
|
|
|
bool active = false;
|
|
|
|
if (auto focusedSurface = seat->focusedTextInputSurface()) {
|
|
|
|
auto client = focusedSurface->client();
|
2023-01-07 20:56:49 +00:00
|
|
|
if ((t1->clientSupportsTextInput(client) && t1->isEnabled()) || (t2->clientSupportsTextInput(client) && t2->isEnabled()) || (t3->clientSupportsTextInput(client) && t3->isEnabled())) {
|
2022-10-16 05:03:32 +00:00
|
|
|
active = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
setActive(active);
|
|
|
|
}
|
|
|
|
|
2021-10-13 16:24:59 +00:00
|
|
|
void InputMethod::setActive(bool active)
|
|
|
|
{
|
2021-07-20 23:41:38 +00:00
|
|
|
const bool wasActive = waylandServer()->inputMethod()->context();
|
|
|
|
if (wasActive && !active) {
|
|
|
|
waylandServer()->inputMethod()->sendDeactivate();
|
|
|
|
}
|
2021-02-19 02:20:54 +00:00
|
|
|
|
2021-02-26 15:16:45 +00:00
|
|
|
if (active) {
|
2021-02-23 15:12:38 +00:00
|
|
|
if (!m_enabled) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-07-20 23:41:38 +00:00
|
|
|
if (!wasActive) {
|
|
|
|
waylandServer()->inputMethod()->sendActivate();
|
|
|
|
}
|
2021-04-27 07:05:26 +00:00
|
|
|
adoptInputMethodContext();
|
2021-02-23 15:12:38 +00:00
|
|
|
} else {
|
|
|
|
updateInputPanelState();
|
2021-02-18 04:46:21 +00:00
|
|
|
}
|
2020-07-27 19:30:49 +00:00
|
|
|
|
2021-05-21 15:57:21 +00:00
|
|
|
if (wasActive != isActive()) {
|
2021-02-26 15:16:45 +00:00
|
|
|
Q_EMIT activeChanged(active);
|
2021-02-23 15:12:38 +00:00
|
|
|
}
|
2016-04-29 13:05:03 +00:00
|
|
|
}
|
|
|
|
|
2022-04-22 17:42:35 +00:00
|
|
|
InputPanelV1Window *InputMethod::panel() const
|
2022-02-09 13:53:59 +00:00
|
|
|
{
|
2022-04-23 08:33:23 +00:00
|
|
|
return m_panel;
|
2022-02-09 13:53:59 +00:00
|
|
|
}
|
|
|
|
|
2022-04-23 08:33:23 +00:00
|
|
|
void InputMethod::setPanel(InputPanelV1Window *panel)
|
2020-09-18 03:17:15 +00:00
|
|
|
{
|
2022-04-23 08:33:23 +00:00
|
|
|
Q_ASSERT(panel->isInputMethod());
|
|
|
|
if (m_panel) {
|
|
|
|
qCWarning(KWIN_VIRTUALKEYBOARD) << "Replacing input panel" << m_panel << "with" << panel;
|
|
|
|
disconnect(m_panel, nullptr, this, nullptr);
|
2021-06-07 00:45:57 +00:00
|
|
|
}
|
|
|
|
|
2022-04-23 08:33:23 +00:00
|
|
|
m_panel = panel;
|
2023-03-22 13:59:34 +00:00
|
|
|
connect(panel, &Window::closed, this, [this]() {
|
2022-04-23 08:33:23 +00:00
|
|
|
if (m_trackedWindow) {
|
|
|
|
m_trackedWindow->setVirtualKeyboardGeometry({});
|
2020-09-18 03:17:15 +00:00
|
|
|
}
|
|
|
|
});
|
2022-04-23 08:33:23 +00:00
|
|
|
connect(m_panel, &Window::frameGeometryChanged, this, &InputMethod::updateInputPanelState);
|
|
|
|
connect(m_panel, &Window::windowHidden, this, &InputMethod::updateInputPanelState);
|
2023-03-13 19:21:11 +00:00
|
|
|
connect(m_panel, &Window::closed, this, &InputMethod::updateInputPanelState);
|
2022-04-23 08:33:23 +00:00
|
|
|
connect(m_panel, &Window::windowShown, this, &InputMethod::visibleChanged);
|
|
|
|
connect(m_panel, &Window::windowHidden, this, &InputMethod::visibleChanged);
|
2023-03-13 19:21:11 +00:00
|
|
|
connect(m_panel, &Window::closed, this, &InputMethod::visibleChanged);
|
2021-07-21 00:04:33 +00:00
|
|
|
Q_EMIT visibleChanged();
|
2021-06-07 00:44:36 +00:00
|
|
|
updateInputPanelState();
|
2022-02-09 13:53:59 +00:00
|
|
|
Q_EMIT panelChanged();
|
2022-09-21 12:17:06 +00:00
|
|
|
|
|
|
|
if (m_shouldShowPanel) {
|
|
|
|
show();
|
|
|
|
}
|
2020-09-18 03:17:15 +00:00
|
|
|
}
|
|
|
|
|
2022-04-23 08:33:23 +00:00
|
|
|
void InputMethod::setTrackedWindow(Window *trackedWindow)
|
2021-05-21 16:26:49 +00:00
|
|
|
{
|
2022-04-23 08:33:23 +00:00
|
|
|
// Reset the old window virtual keybaord geom if necessary
|
|
|
|
// Old and new windows could be the same if focus moves between subsurfaces
|
|
|
|
if (m_trackedWindow == trackedWindow) {
|
2021-05-21 16:26:49 +00:00
|
|
|
return;
|
|
|
|
}
|
2022-04-23 08:33:23 +00:00
|
|
|
if (m_trackedWindow) {
|
|
|
|
m_trackedWindow->setVirtualKeyboardGeometry(QRect());
|
|
|
|
disconnect(m_trackedWindow, &Window::frameGeometryChanged, this, &InputMethod::updateInputPanelState);
|
2021-05-21 16:26:49 +00:00
|
|
|
}
|
2022-04-23 08:33:23 +00:00
|
|
|
m_trackedWindow = trackedWindow;
|
2022-10-23 21:51:22 +00:00
|
|
|
m_shouldShowPanel = false;
|
2022-04-23 08:33:23 +00:00
|
|
|
if (m_trackedWindow) {
|
|
|
|
connect(m_trackedWindow, &Window::frameGeometryChanged, this, &InputMethod::updateInputPanelState, Qt::QueuedConnection);
|
2021-05-21 16:26:49 +00:00
|
|
|
}
|
|
|
|
updateInputPanelState();
|
|
|
|
}
|
|
|
|
|
2020-09-29 14:46:32 +00:00
|
|
|
void InputMethod::handleFocusedSurfaceChanged()
|
2020-09-18 03:17:15 +00:00
|
|
|
{
|
2022-09-06 22:56:19 +00:00
|
|
|
auto seat = waylandServer()->seat();
|
|
|
|
SurfaceInterface *focusedSurface = seat->focusedTextInputSurface();
|
|
|
|
|
2022-04-23 08:33:23 +00:00
|
|
|
setTrackedWindow(waylandServer()->findWindow(focusedSurface));
|
2022-09-06 22:56:19 +00:00
|
|
|
|
|
|
|
const auto client = focusedSurface ? focusedSurface->client() : nullptr;
|
|
|
|
bool ret = seat->textInputV2()->clientSupportsTextInput(client)
|
|
|
|
|| seat->textInputV3()->clientSupportsTextInput(client);
|
|
|
|
if (ret != m_activeClientSupportsTextInput) {
|
|
|
|
m_activeClientSupportsTextInput = ret;
|
|
|
|
Q_EMIT activeClientSupportsTextInputChanged();
|
|
|
|
}
|
2020-09-18 03:17:15 +00:00
|
|
|
}
|
|
|
|
|
2020-09-29 14:46:32 +00:00
|
|
|
void InputMethod::surroundingTextChanged()
|
2020-09-18 03:17:15 +00:00
|
|
|
{
|
2020-09-25 06:51:04 +00:00
|
|
|
auto t2 = waylandServer()->seat()->textInputV2();
|
|
|
|
auto t3 = waylandServer()->seat()->textInputV3();
|
2020-09-18 03:17:15 +00:00
|
|
|
auto inputContext = waylandServer()->inputMethod()->context();
|
|
|
|
if (!inputContext) {
|
|
|
|
return;
|
|
|
|
}
|
2020-09-25 06:51:04 +00:00
|
|
|
if (t2 && t2->isEnabled()) {
|
|
|
|
inputContext->sendSurroundingText(t2->surroundingText(), t2->surroundingTextCursorPosition(), t2->surroundingTextSelectionAnchor());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (t3 && t3->isEnabled()) {
|
|
|
|
inputContext->sendSurroundingText(t3->surroundingText(), t3->surroundingTextCursorPosition(), t3->surroundingTextSelectionAnchor());
|
|
|
|
return;
|
|
|
|
}
|
2020-09-18 03:17:15 +00:00
|
|
|
}
|
|
|
|
|
2020-09-29 14:46:32 +00:00
|
|
|
void InputMethod::contentTypeChanged()
|
2020-09-18 03:17:15 +00:00
|
|
|
{
|
2023-01-07 20:56:49 +00:00
|
|
|
auto t1 = waylandServer()->seat()->textInputV1();
|
2020-09-25 06:51:04 +00:00
|
|
|
auto t2 = waylandServer()->seat()->textInputV2();
|
|
|
|
auto t3 = waylandServer()->seat()->textInputV3();
|
2020-09-18 03:17:15 +00:00
|
|
|
auto inputContext = waylandServer()->inputMethod()->context();
|
|
|
|
if (!inputContext) {
|
|
|
|
return;
|
|
|
|
}
|
2023-01-07 20:56:49 +00:00
|
|
|
if (t1 && t1->isEnabled()) {
|
|
|
|
inputContext->sendContentType(t1->contentHints(), t1->contentPurpose());
|
|
|
|
}
|
2020-09-25 06:51:04 +00:00
|
|
|
if (t2 && t2->isEnabled()) {
|
|
|
|
inputContext->sendContentType(t2->contentHints(), t2->contentPurpose());
|
|
|
|
}
|
|
|
|
if (t3 && t3->isEnabled()) {
|
|
|
|
inputContext->sendContentType(t3->contentHints(), t3->contentPurpose());
|
|
|
|
}
|
2020-09-18 03:17:15 +00:00
|
|
|
}
|
|
|
|
|
2023-01-07 20:56:49 +00:00
|
|
|
void InputMethod::textInputInterfaceV1Reset()
|
|
|
|
{
|
|
|
|
if (!m_enabled) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
auto t1 = waylandServer()->seat()->textInputV1();
|
|
|
|
auto inputContext = waylandServer()->inputMethod()->context();
|
|
|
|
if (!inputContext) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!t1 || !t1->isEnabled()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
inputContext->sendReset();
|
|
|
|
}
|
|
|
|
|
|
|
|
void InputMethod::invokeAction(quint32 button, quint32 index)
|
|
|
|
{
|
|
|
|
if (!m_enabled) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
auto t1 = waylandServer()->seat()->textInputV1();
|
|
|
|
auto inputContext = waylandServer()->inputMethod()->context();
|
|
|
|
if (!inputContext) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!t1 || !t1->isEnabled()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
inputContext->sendInvokeAction(button, index);
|
|
|
|
}
|
|
|
|
|
|
|
|
void InputMethod::textInputInterfaceV1StateUpdated(quint32 serial)
|
|
|
|
{
|
|
|
|
if (!m_enabled) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
auto t1 = waylandServer()->seat()->textInputV1();
|
|
|
|
auto inputContext = waylandServer()->inputMethod()->context();
|
|
|
|
if (!inputContext) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!t1 || !t1->isEnabled()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
inputContext->sendCommitState(serial);
|
|
|
|
}
|
|
|
|
|
2020-10-07 04:46:46 +00:00
|
|
|
void InputMethod::textInputInterfaceV2StateUpdated(quint32 serial, KWaylandServer::TextInputV2Interface::UpdateReason reason)
|
2020-09-18 03:17:15 +00:00
|
|
|
{
|
2021-04-27 15:49:55 +00:00
|
|
|
if (!m_enabled) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-09-25 06:51:04 +00:00
|
|
|
auto t2 = waylandServer()->seat()->textInputV2();
|
2020-09-18 03:17:15 +00:00
|
|
|
auto inputContext = waylandServer()->inputMethod()->context();
|
|
|
|
if (!inputContext) {
|
|
|
|
return;
|
|
|
|
}
|
2020-10-07 04:46:46 +00:00
|
|
|
if (!t2 || !t2->isEnabled()) {
|
|
|
|
return;
|
|
|
|
}
|
2022-04-23 08:33:23 +00:00
|
|
|
if (m_panel && shouldShowOnActive()) {
|
|
|
|
m_panel->allow();
|
2021-10-13 16:24:59 +00:00
|
|
|
}
|
2020-10-07 04:46:46 +00:00
|
|
|
switch (reason) {
|
|
|
|
case KWaylandServer::TextInputV2Interface::UpdateReason::StateChange:
|
|
|
|
break;
|
|
|
|
case KWaylandServer::TextInputV2Interface::UpdateReason::StateEnter:
|
|
|
|
case KWaylandServer::TextInputV2Interface::UpdateReason::StateFull:
|
|
|
|
adoptInputMethodContext();
|
|
|
|
break;
|
|
|
|
case KWaylandServer::TextInputV2Interface::UpdateReason::StateReset:
|
|
|
|
inputContext->sendReset();
|
|
|
|
break;
|
2020-09-25 06:51:04 +00:00
|
|
|
}
|
2020-09-18 03:17:15 +00:00
|
|
|
}
|
|
|
|
|
2023-01-07 20:56:49 +00:00
|
|
|
void InputMethod::textInputInterfaceV1EnabledChanged()
|
|
|
|
{
|
|
|
|
if (!m_enabled) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
refreshActive();
|
|
|
|
}
|
|
|
|
|
2020-09-29 14:46:32 +00:00
|
|
|
void InputMethod::textInputInterfaceV2EnabledChanged()
|
2020-09-18 03:17:15 +00:00
|
|
|
{
|
2021-04-27 15:49:55 +00:00
|
|
|
if (!m_enabled) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-10-16 05:03:32 +00:00
|
|
|
refreshActive();
|
2020-09-18 03:17:15 +00:00
|
|
|
}
|
|
|
|
|
2020-09-29 14:46:32 +00:00
|
|
|
void InputMethod::textInputInterfaceV3EnabledChanged()
|
2020-09-25 06:51:04 +00:00
|
|
|
{
|
2021-04-27 15:49:55 +00:00
|
|
|
if (!m_enabled) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-09-25 06:51:04 +00:00
|
|
|
auto t3 = waylandServer()->seat()->textInputV3();
|
2022-10-16 05:03:32 +00:00
|
|
|
refreshActive();
|
2022-10-14 01:18:05 +00:00
|
|
|
if (t3->isEnabled()) {
|
|
|
|
show();
|
|
|
|
} else {
|
2020-09-25 06:51:04 +00:00
|
|
|
// reset value of preedit when textinput is disabled
|
2021-12-23 08:42:00 +00:00
|
|
|
resetPendingPreedit();
|
2020-09-25 06:51:04 +00:00
|
|
|
}
|
2020-10-07 04:46:46 +00:00
|
|
|
auto context = waylandServer()->inputMethod()->context();
|
|
|
|
if (context) {
|
|
|
|
context->sendReset();
|
|
|
|
adoptInputMethodContext();
|
|
|
|
}
|
2020-09-25 06:51:04 +00:00
|
|
|
}
|
|
|
|
|
2020-09-29 14:46:32 +00:00
|
|
|
void InputMethod::stateCommitted(uint32_t serial)
|
2020-09-18 03:17:15 +00:00
|
|
|
{
|
2021-05-10 16:03:14 +00:00
|
|
|
if (!isEnabled()) {
|
2021-05-11 11:14:01 +00:00
|
|
|
return;
|
2021-05-10 16:03:14 +00:00
|
|
|
}
|
|
|
|
TextInputV3Interface *textInputV3 = waylandServer()->seat()->textInputV3();
|
|
|
|
if (!textInputV3) {
|
2020-09-18 03:17:15 +00:00
|
|
|
return;
|
|
|
|
}
|
2021-05-10 16:03:14 +00:00
|
|
|
|
2021-05-13 14:04:03 +00:00
|
|
|
if (auto inputContext = waylandServer()->inputMethod()->context()) {
|
|
|
|
inputContext->sendCommitState(serial);
|
2021-05-10 16:03:14 +00:00
|
|
|
}
|
2020-09-18 03:17:15 +00:00
|
|
|
}
|
|
|
|
|
2020-09-29 14:46:32 +00:00
|
|
|
void InputMethod::setEnabled(bool enabled)
|
2016-04-29 13:05:03 +00:00
|
|
|
{
|
|
|
|
if (m_enabled == enabled) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
m_enabled = enabled;
|
2021-06-08 07:02:14 +00:00
|
|
|
Q_EMIT enabledChanged(m_enabled);
|
2016-04-29 13:05:03 +00:00
|
|
|
|
|
|
|
// send OSD message
|
|
|
|
QDBusMessage msg = QDBusMessage::createMethodCall(
|
|
|
|
QStringLiteral("org.kde.plasmashell"),
|
|
|
|
QStringLiteral("/org/kde/osdService"),
|
|
|
|
QStringLiteral("org.kde.osdService"),
|
2022-03-23 10:13:38 +00:00
|
|
|
QStringLiteral("virtualKeyboardEnabledChanged"));
|
2016-04-29 13:05:03 +00:00
|
|
|
msg.setArguments({enabled});
|
|
|
|
QDBusConnection::sessionBus().asyncCall(msg);
|
2021-04-27 15:49:55 +00:00
|
|
|
if (!m_enabled) {
|
2021-02-19 02:20:54 +00:00
|
|
|
hide();
|
2021-04-27 15:49:55 +00:00
|
|
|
stopInputMethod();
|
|
|
|
} else {
|
|
|
|
startInputMethod();
|
2021-02-19 02:20:54 +00:00
|
|
|
}
|
2021-07-07 00:42:06 +00:00
|
|
|
// save value into config
|
|
|
|
kwinApp()->config()->group("Wayland").writeEntry("VirtualKeyboardEnabled", m_enabled);
|
|
|
|
kwinApp()->config()->sync();
|
2016-04-29 13:05:03 +00:00
|
|
|
}
|
|
|
|
|
2020-09-25 06:51:04 +00:00
|
|
|
static quint32 keysymToKeycode(quint32 sym)
|
|
|
|
{
|
2022-03-23 10:13:38 +00:00
|
|
|
switch (sym) {
|
2020-09-25 06:51:04 +00:00
|
|
|
case XKB_KEY_BackSpace:
|
|
|
|
return KEY_BACKSPACE;
|
|
|
|
case XKB_KEY_Return:
|
|
|
|
return KEY_ENTER;
|
|
|
|
case XKB_KEY_Left:
|
|
|
|
return KEY_LEFT;
|
|
|
|
case XKB_KEY_Right:
|
|
|
|
return KEY_RIGHT;
|
|
|
|
case XKB_KEY_Up:
|
|
|
|
return KEY_UP;
|
|
|
|
case XKB_KEY_Down:
|
|
|
|
return KEY_DOWN;
|
|
|
|
default:
|
|
|
|
return KEY_UNKNOWN;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-30 21:04:45 +00:00
|
|
|
void InputMethod::keysymReceived(quint32 serial, quint32 time, quint32 sym, bool pressed, quint32 modifiers)
|
2020-07-11 16:40:28 +00:00
|
|
|
{
|
2023-01-07 20:56:49 +00:00
|
|
|
if (auto t1 = waylandServer()->seat()->textInputV1(); t1 && t1->isEnabled()) {
|
|
|
|
if (pressed) {
|
|
|
|
t1->keysymPressed(time, sym, modifiers);
|
|
|
|
} else {
|
|
|
|
t1->keysymReleased(time, sym, modifiers);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-09-25 06:51:04 +00:00
|
|
|
auto t2 = waylandServer()->seat()->textInputV2();
|
|
|
|
if (t2 && t2->isEnabled()) {
|
|
|
|
if (pressed) {
|
|
|
|
t2->keysymPressed(sym, modifiers);
|
|
|
|
} else {
|
|
|
|
t2->keysymReleased(sym, modifiers);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
2022-05-17 11:53:13 +00:00
|
|
|
|
|
|
|
KWaylandServer::KeyboardKeyState state;
|
|
|
|
if (pressed) {
|
|
|
|
state = KWaylandServer::KeyboardKeyState::Pressed;
|
|
|
|
} else {
|
|
|
|
state = KWaylandServer::KeyboardKeyState::Released;
|
2020-07-11 16:40:28 +00:00
|
|
|
}
|
2022-05-17 11:53:13 +00:00
|
|
|
waylandServer()->seat()->notifyKeyboardKey(keysymToKeycode(sym), state);
|
2020-07-11 16:40:28 +00:00
|
|
|
}
|
|
|
|
|
2021-03-10 02:12:57 +00:00
|
|
|
void InputMethod::commitString(qint32 serial, const QString &text)
|
2020-07-11 16:40:28 +00:00
|
|
|
{
|
2023-01-07 20:56:49 +00:00
|
|
|
if (auto t1 = waylandServer()->seat()->textInputV1(); t1 && t1->isEnabled()) {
|
|
|
|
t1->commitString(text.toUtf8());
|
2023-03-08 06:10:19 +00:00
|
|
|
t1->setPreEditCursor(0);
|
2023-01-07 20:56:49 +00:00
|
|
|
t1->preEdit({}, {});
|
|
|
|
return;
|
|
|
|
}
|
2021-05-22 00:37:50 +00:00
|
|
|
if (auto t2 = waylandServer()->seat()->textInputV2(); t2 && t2->isEnabled()) {
|
2020-09-25 06:51:04 +00:00
|
|
|
t2->commitString(text.toUtf8());
|
2023-03-08 06:10:19 +00:00
|
|
|
t2->setPreEditCursor(0);
|
2020-09-25 06:51:04 +00:00
|
|
|
t2->preEdit({}, {});
|
|
|
|
return;
|
2021-05-22 00:37:50 +00:00
|
|
|
} else if (auto t3 = waylandServer()->seat()->textInputV3(); t3 && t3->isEnabled()) {
|
2020-09-25 06:51:04 +00:00
|
|
|
t3->commitString(text.toUtf8());
|
|
|
|
t3->done();
|
|
|
|
return;
|
2021-05-22 00:37:50 +00:00
|
|
|
} else {
|
2022-05-17 11:53:13 +00:00
|
|
|
// The application has no way of communicating with the input method.
|
|
|
|
// So instead, try to convert what we get from the input method into
|
|
|
|
// keycodes and send those as fake input to the client.
|
|
|
|
auto keys = textToKey(text);
|
|
|
|
if (keys.empty()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// First, send all the extracted keys as pressed keys to the client.
|
|
|
|
for (const auto &key : keys) {
|
|
|
|
waylandServer()->seat()->notifyKeyboardKey(key, KWaylandServer::KeyboardKeyState::Pressed);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Then, send key release for those keys in reverse.
|
|
|
|
for (auto itr = keys.rbegin(); itr != keys.rend(); ++itr) {
|
|
|
|
// Since we are faking key events, we do not have distinct press/release
|
|
|
|
// events. So instead, just queue the button release so it gets sent
|
|
|
|
// a few moments after the press.
|
|
|
|
auto key = *itr;
|
|
|
|
QMetaObject::invokeMethod(
|
|
|
|
this, [key]() {
|
|
|
|
waylandServer()->seat()->notifyKeyboardKey(key, KWaylandServer::KeyboardKeyState::Released);
|
|
|
|
},
|
|
|
|
Qt::QueuedConnection);
|
|
|
|
}
|
2020-07-11 16:40:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-10 02:12:57 +00:00
|
|
|
void InputMethod::deleteSurroundingText(int32_t index, uint32_t length)
|
2020-07-11 16:40:28 +00:00
|
|
|
{
|
2021-12-24 01:31:58 +00:00
|
|
|
// zwp_input_method_v1 Delete surrounding text interface is designed for text-input-v1.
|
|
|
|
// The parameter has different meaning in text-input-v{2,3}.
|
|
|
|
// Current cursor is at index 0.
|
|
|
|
// The actualy deleted text range is [index, index + length].
|
|
|
|
// In v{2,3}'s before/after style, text to be deleted with v{2,3} interface is [-before, after].
|
|
|
|
// And before/after are all unsigned, which make it impossible to do certain things.
|
|
|
|
// Those request will be ignored.
|
|
|
|
|
|
|
|
// Verify we can handle such request.
|
|
|
|
if (index > 0 || index + static_cast<ssize_t>(length) < 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const quint32 before = -index;
|
|
|
|
const quint32 after = index + length;
|
|
|
|
|
2023-01-07 20:56:49 +00:00
|
|
|
auto t1 = waylandServer()->seat()->textInputV1();
|
|
|
|
if (t1 && t1->isEnabled()) {
|
|
|
|
t1->deleteSurroundingText(before, after);
|
|
|
|
}
|
2020-09-25 06:51:04 +00:00
|
|
|
auto t2 = waylandServer()->seat()->textInputV2();
|
|
|
|
if (t2 && t2->isEnabled()) {
|
2021-12-24 01:31:58 +00:00
|
|
|
t2->deleteSurroundingText(before, after);
|
2020-09-25 06:51:04 +00:00
|
|
|
}
|
|
|
|
auto t3 = waylandServer()->seat()->textInputV3();
|
|
|
|
if (t3 && t3->isEnabled()) {
|
2021-12-24 01:31:58 +00:00
|
|
|
t3->deleteSurroundingText(before, after);
|
2021-12-19 08:12:31 +00:00
|
|
|
t3->done();
|
2020-07-11 16:40:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-10 02:12:57 +00:00
|
|
|
void InputMethod::setCursorPosition(qint32 index, qint32 anchor)
|
2020-07-11 16:40:28 +00:00
|
|
|
{
|
2023-01-07 20:56:49 +00:00
|
|
|
auto t1 = waylandServer()->seat()->textInputV1();
|
|
|
|
if (t1 && t1->isEnabled()) {
|
|
|
|
t1->setCursorPosition(index, anchor);
|
|
|
|
}
|
2020-09-25 06:51:04 +00:00
|
|
|
auto t2 = waylandServer()->seat()->textInputV2();
|
|
|
|
if (t2 && t2->isEnabled()) {
|
|
|
|
t2->setCursorPosition(index, anchor);
|
2020-07-11 16:40:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-10 02:12:57 +00:00
|
|
|
void InputMethod::setLanguage(uint32_t serial, const QString &language)
|
2020-07-11 16:40:28 +00:00
|
|
|
{
|
2023-01-07 20:56:49 +00:00
|
|
|
auto t1 = waylandServer()->seat()->textInputV1();
|
|
|
|
if (t1 && t1->isEnabled()) {
|
|
|
|
t1->setLanguage(language.toUtf8());
|
|
|
|
}
|
2020-09-25 06:51:04 +00:00
|
|
|
auto t2 = waylandServer()->seat()->textInputV2();
|
|
|
|
if (t2 && t2->isEnabled()) {
|
|
|
|
t2->setLanguage(language.toUtf8());
|
2020-07-11 16:40:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-10 02:12:57 +00:00
|
|
|
void InputMethod::setTextDirection(uint32_t serial, Qt::LayoutDirection direction)
|
2020-07-11 16:40:28 +00:00
|
|
|
{
|
2023-01-07 20:56:49 +00:00
|
|
|
auto t1 = waylandServer()->seat()->textInputV1();
|
|
|
|
if (t1 && t1->isEnabled()) {
|
|
|
|
t1->setTextDirection(direction);
|
|
|
|
}
|
2020-09-25 06:51:04 +00:00
|
|
|
auto t2 = waylandServer()->seat()->textInputV2();
|
|
|
|
if (t2 && t2->isEnabled()) {
|
|
|
|
t2->setTextDirection(direction);
|
2020-07-11 16:40:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-29 14:46:32 +00:00
|
|
|
void InputMethod::setPreeditCursor(qint32 index)
|
2020-07-11 16:40:28 +00:00
|
|
|
{
|
2023-01-07 20:56:49 +00:00
|
|
|
auto t1 = waylandServer()->seat()->textInputV1();
|
|
|
|
if (t1 && t1->isEnabled()) {
|
|
|
|
t1->setPreEditCursor(index);
|
|
|
|
}
|
2020-09-25 06:51:04 +00:00
|
|
|
auto t2 = waylandServer()->seat()->textInputV2();
|
|
|
|
if (t2 && t2->isEnabled()) {
|
|
|
|
t2->setPreEditCursor(index);
|
|
|
|
}
|
|
|
|
auto t3 = waylandServer()->seat()->textInputV3();
|
|
|
|
if (t3 && t3->isEnabled()) {
|
2021-12-23 08:42:00 +00:00
|
|
|
preedit.cursor = index;
|
2020-07-11 16:40:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-20 11:33:41 +00:00
|
|
|
void InputMethod::setPreeditStyling(quint32 index, quint32 length, quint32 style)
|
|
|
|
{
|
2023-01-07 20:56:49 +00:00
|
|
|
auto t1 = waylandServer()->seat()->textInputV1();
|
|
|
|
if (t1 && t1->isEnabled()) {
|
|
|
|
t1->preEditStyling(index, length, style);
|
|
|
|
}
|
2021-12-20 11:33:41 +00:00
|
|
|
auto t2 = waylandServer()->seat()->textInputV2();
|
|
|
|
if (t2 && t2->isEnabled()) {
|
|
|
|
t2->preEditStyling(index, length, style);
|
|
|
|
}
|
2021-12-23 08:42:00 +00:00
|
|
|
auto t3 = waylandServer()->seat()->textInputV3();
|
|
|
|
if (t3 && t3->isEnabled()) {
|
|
|
|
// preedit style: highlight(4) or selection(6)
|
|
|
|
if (style == 4 || style == 6) {
|
|
|
|
preedit.highlightRanges.emplace_back(index, index + length);
|
|
|
|
}
|
|
|
|
}
|
2021-12-20 11:33:41 +00:00
|
|
|
}
|
2020-09-25 06:51:04 +00:00
|
|
|
|
2020-09-29 14:46:32 +00:00
|
|
|
void InputMethod::setPreeditString(uint32_t serial, const QString &text, const QString &commit)
|
2020-07-11 16:40:28 +00:00
|
|
|
{
|
2023-01-07 20:56:49 +00:00
|
|
|
auto t1 = waylandServer()->seat()->textInputV1();
|
|
|
|
if (t1 && t1->isEnabled()) {
|
|
|
|
t1->preEdit(text.toUtf8(), commit.toUtf8());
|
|
|
|
}
|
2020-09-25 06:51:04 +00:00
|
|
|
auto t2 = waylandServer()->seat()->textInputV2();
|
|
|
|
if (t2 && t2->isEnabled()) {
|
|
|
|
t2->preEdit(text.toUtf8(), commit.toUtf8());
|
|
|
|
}
|
|
|
|
auto t3 = waylandServer()->seat()->textInputV3();
|
|
|
|
if (t3 && t3->isEnabled()) {
|
|
|
|
preedit.text = text;
|
2021-12-22 23:00:09 +00:00
|
|
|
if (!preedit.text.isEmpty()) {
|
2021-12-23 08:42:00 +00:00
|
|
|
quint32 cursor = 0, cursorEnd = 0;
|
|
|
|
if (preedit.cursor > 0) {
|
|
|
|
cursor = cursorEnd = preedit.cursor;
|
|
|
|
}
|
|
|
|
// Check if we can convert highlight style to a range of selection.
|
|
|
|
if (!preedit.highlightRanges.empty()) {
|
|
|
|
std::sort(preedit.highlightRanges.begin(), preedit.highlightRanges.end());
|
|
|
|
// Check if starting point matches.
|
|
|
|
if (preedit.highlightRanges.front().first == cursor) {
|
|
|
|
quint32 end = preedit.highlightRanges.front().second;
|
|
|
|
bool nonContinousHighlight = false;
|
2022-03-23 10:13:38 +00:00
|
|
|
for (size_t i = 1; i < preedit.highlightRanges.size(); i++) {
|
2021-12-23 08:42:00 +00:00
|
|
|
if (end >= preedit.highlightRanges[i].first) {
|
|
|
|
end = std::max(end, preedit.highlightRanges[i].second);
|
|
|
|
} else {
|
|
|
|
nonContinousHighlight = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!nonContinousHighlight) {
|
|
|
|
cursorEnd = end;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
t3->sendPreEditString(preedit.text, cursor, cursorEnd);
|
2021-12-22 23:00:09 +00:00
|
|
|
}
|
2021-12-19 08:12:31 +00:00
|
|
|
t3->done();
|
2020-07-11 16:40:28 +00:00
|
|
|
}
|
2021-12-23 08:42:00 +00:00
|
|
|
resetPendingPreedit();
|
2020-07-11 16:40:28 +00:00
|
|
|
}
|
|
|
|
|
2020-10-15 08:02:33 +00:00
|
|
|
void InputMethod::key(quint32 /*serial*/, quint32 /*time*/, quint32 keyCode, bool pressed)
|
|
|
|
{
|
2022-01-27 19:14:53 +00:00
|
|
|
waylandServer()->seat()->notifyKeyboardKey(keyCode,
|
|
|
|
pressed ? KWaylandServer::KeyboardKeyState::Pressed : KWaylandServer::KeyboardKeyState::Released);
|
2020-10-15 08:02:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void InputMethod::modifiers(quint32 serial, quint32 mods_depressed, quint32 mods_latched, quint32 mods_locked, quint32 group)
|
|
|
|
{
|
|
|
|
auto xkb = input()->keyboard()->xkb();
|
|
|
|
xkb->updateModifiers(mods_depressed, mods_latched, mods_locked, group);
|
|
|
|
}
|
|
|
|
|
2021-12-29 17:19:43 +00:00
|
|
|
void InputMethod::forwardModifiers(ForwardModifiersForce force)
|
2021-12-13 17:26:01 +00:00
|
|
|
{
|
2021-12-29 17:19:43 +00:00
|
|
|
const bool sendModifiers = m_hasPendingModifiers || force == Force;
|
|
|
|
m_hasPendingModifiers = false;
|
|
|
|
if (!sendModifiers) {
|
|
|
|
return;
|
|
|
|
}
|
2021-12-13 17:26:01 +00:00
|
|
|
auto xkb = input()->keyboard()->xkb();
|
|
|
|
if (m_keyboardGrab) {
|
|
|
|
m_keyboardGrab->sendModifiers(waylandServer()->display()->nextSerial(),
|
|
|
|
xkb->modifierState().depressed,
|
|
|
|
xkb->modifierState().latched,
|
|
|
|
xkb->modifierState().locked,
|
|
|
|
xkb->currentLayout());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-29 14:46:32 +00:00
|
|
|
void InputMethod::adoptInputMethodContext()
|
2020-07-11 16:40:28 +00:00
|
|
|
{
|
|
|
|
auto inputContext = waylandServer()->inputMethod()->context();
|
|
|
|
|
2023-01-07 20:56:49 +00:00
|
|
|
TextInputV1Interface *t1 = waylandServer()->seat()->textInputV1();
|
2020-09-25 06:51:04 +00:00
|
|
|
TextInputV2Interface *t2 = waylandServer()->seat()->textInputV2();
|
|
|
|
TextInputV3Interface *t3 = waylandServer()->seat()->textInputV3();
|
|
|
|
|
2023-01-07 20:56:49 +00:00
|
|
|
if (t1 && t1->isEnabled()) {
|
|
|
|
inputContext->sendSurroundingText(t1->surroundingText(), t1->surroundingTextCursorPosition(), t1->surroundingTextSelectionAnchor());
|
|
|
|
inputContext->sendPreferredLanguage(t1->preferredLanguage());
|
|
|
|
inputContext->sendContentType(t1->contentHints(), t2->contentPurpose());
|
|
|
|
connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::language, this, &InputMethod::setLanguage);
|
|
|
|
connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::textDirection, this, &InputMethod::setTextDirection);
|
|
|
|
} else if (t2 && t2->isEnabled()) {
|
2020-09-25 06:51:04 +00:00
|
|
|
inputContext->sendSurroundingText(t2->surroundingText(), t2->surroundingTextCursorPosition(), t2->surroundingTextSelectionAnchor());
|
|
|
|
inputContext->sendPreferredLanguage(t2->preferredLanguage());
|
|
|
|
inputContext->sendContentType(t2->contentHints(), t2->contentPurpose());
|
2021-03-10 02:12:57 +00:00
|
|
|
connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::language, this, &InputMethod::setLanguage);
|
|
|
|
connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::textDirection, this, &InputMethod::setTextDirection);
|
2022-05-17 11:53:13 +00:00
|
|
|
} else if (t3 && t3->isEnabled()) {
|
2020-09-25 06:51:04 +00:00
|
|
|
inputContext->sendSurroundingText(t3->surroundingText(), t3->surroundingTextCursorPosition(), t3->surroundingTextSelectionAnchor());
|
|
|
|
inputContext->sendContentType(t3->contentHints(), t3->contentPurpose());
|
2022-05-17 11:53:13 +00:00
|
|
|
} else {
|
|
|
|
// When we have neither text-input-v2 nor text-input-v3 we can only send
|
|
|
|
// fake key events, not more complex text. So ask the input method to
|
|
|
|
// only send basic characters without any pre-editing.
|
|
|
|
inputContext->sendContentType(KWaylandServer::TextInputContentHint::Latin, KWaylandServer::TextInputContentPurpose::Normal);
|
2020-09-25 06:51:04 +00:00
|
|
|
}
|
2022-05-17 11:53:13 +00:00
|
|
|
|
2021-05-24 12:16:14 +00:00
|
|
|
inputContext->sendCommitState(m_serial++);
|
2020-07-11 16:40:28 +00:00
|
|
|
|
2021-03-10 02:12:57 +00:00
|
|
|
connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::keysym, this, &InputMethod::keysymReceived, Qt::UniqueConnection);
|
2020-10-15 08:02:33 +00:00
|
|
|
connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::key, this, &InputMethod::key, Qt::UniqueConnection);
|
|
|
|
connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::modifiers, this, &InputMethod::modifiers, Qt::UniqueConnection);
|
2021-03-10 02:12:57 +00:00
|
|
|
connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::commitString, this, &InputMethod::commitString, Qt::UniqueConnection);
|
|
|
|
connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::deleteSurroundingText, this, &InputMethod::deleteSurroundingText, Qt::UniqueConnection);
|
|
|
|
connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::cursorPosition, this, &InputMethod::setCursorPosition, Qt::UniqueConnection);
|
2021-12-20 11:33:41 +00:00
|
|
|
connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::preeditStyling, this, &InputMethod::setPreeditStyling, Qt::UniqueConnection);
|
2021-02-18 04:46:21 +00:00
|
|
|
connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::preeditString, this, &InputMethod::setPreeditString, Qt::UniqueConnection);
|
|
|
|
connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::preeditCursor, this, &InputMethod::setPreeditCursor, Qt::UniqueConnection);
|
2021-06-01 16:15:47 +00:00
|
|
|
connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::keyboardGrabRequested, this, &InputMethod::installKeyboardGrab, Qt::UniqueConnection);
|
2021-09-30 22:04:15 +00:00
|
|
|
connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::modifiersMap, this, &InputMethod::updateModifiersMap, Qt::UniqueConnection);
|
2020-07-11 16:40:28 +00:00
|
|
|
}
|
|
|
|
|
2020-09-29 14:46:32 +00:00
|
|
|
void InputMethod::updateInputPanelState()
|
virtualkeyboard: resize the focused window to make room for the keyboard
Summary:
alternative approach: try to resize the winidow to make room for the keyboard.
the new input wayland protocol doesn't have anymore the overlap rectangle (and it would not be going to work with qwidget apps anyways)
in the future will probably be needed anextension to the input protocol v3 which partially gets back this, tough window resizing is needed regardless
what's missing: the resize should be "temporary" and the window should be restored to its previous geometry when the keyboard closes
Test Plan: tested with test QML code
Reviewers: #plasma, #kwin, bshah, graesslin, romangg, davidedmundson
Reviewed By: #plasma, #kwin, romangg, davidedmundson
Subscribers: nicolasfella, mart, kwin, davidedmundson, graesslin
Tags: #kwin
Maniphest Tasks: T9815
Differential Revision: https://phabricator.kde.org/D18818
2019-03-20 10:04:51 +00:00
|
|
|
{
|
|
|
|
if (!waylandServer()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-09-22 03:14:57 +00:00
|
|
|
auto t = waylandServer()->seat()->textInputV2();
|
virtualkeyboard: resize the focused window to make room for the keyboard
Summary:
alternative approach: try to resize the winidow to make room for the keyboard.
the new input wayland protocol doesn't have anymore the overlap rectangle (and it would not be going to work with qwidget apps anyways)
in the future will probably be needed anextension to the input protocol v3 which partially gets back this, tough window resizing is needed regardless
what's missing: the resize should be "temporary" and the window should be restored to its previous geometry when the keyboard closes
Test Plan: tested with test QML code
Reviewers: #plasma, #kwin, bshah, graesslin, romangg, davidedmundson
Reviewed By: #plasma, #kwin, romangg, davidedmundson
Subscribers: nicolasfella, mart, kwin, davidedmundson, graesslin
Tags: #kwin
Maniphest Tasks: T9815
Differential Revision: https://phabricator.kde.org/D18818
2019-03-20 10:04:51 +00:00
|
|
|
|
2020-07-11 16:40:28 +00:00
|
|
|
if (!t) {
|
virtualkeyboard: resize the focused window to make room for the keyboard
Summary:
alternative approach: try to resize the winidow to make room for the keyboard.
the new input wayland protocol doesn't have anymore the overlap rectangle (and it would not be going to work with qwidget apps anyways)
in the future will probably be needed anextension to the input protocol v3 which partially gets back this, tough window resizing is needed regardless
what's missing: the resize should be "temporary" and the window should be restored to its previous geometry when the keyboard closes
Test Plan: tested with test QML code
Reviewers: #plasma, #kwin, bshah, graesslin, romangg, davidedmundson
Reviewed By: #plasma, #kwin, romangg, davidedmundson
Subscribers: nicolasfella, mart, kwin, davidedmundson, graesslin
Tags: #kwin
Maniphest Tasks: T9815
Differential Revision: https://phabricator.kde.org/D18818
2019-03-20 10:04:51 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-04-23 08:33:23 +00:00
|
|
|
if (m_panel && shouldShowOnActive()) {
|
|
|
|
m_panel->allow();
|
2021-10-13 16:24:59 +00:00
|
|
|
}
|
|
|
|
|
2022-05-16 20:13:39 +00:00
|
|
|
QRectF overlap = QRectF(0, 0, 0, 0);
|
2022-04-23 08:33:23 +00:00
|
|
|
if (m_trackedWindow) {
|
2022-10-14 01:18:05 +00:00
|
|
|
const bool bottomKeyboard = m_panel && m_panel->mode() != InputPanelV1Window::Mode::Overlay && m_panel->isShown();
|
2023-03-31 14:07:01 +00:00
|
|
|
m_trackedWindow->setVirtualKeyboardGeometry(bottomKeyboard ? m_panel->frameGeometry() : QRectF());
|
2021-04-29 15:53:44 +00:00
|
|
|
|
2022-10-14 01:18:05 +00:00
|
|
|
if (m_panel && m_panel->mode() != InputPanelV1Window::Mode::Overlay) {
|
2023-03-31 14:07:01 +00:00
|
|
|
overlap = m_trackedWindow->frameGeometry() & m_panel->frameGeometry();
|
2022-04-23 08:33:23 +00:00
|
|
|
overlap.moveTo(m_trackedWindow->mapToLocal(overlap.topLeft()));
|
2021-04-29 15:53:44 +00:00
|
|
|
}
|
2020-09-21 07:35:34 +00:00
|
|
|
}
|
2022-05-16 20:13:39 +00:00
|
|
|
t->setInputPanelState(m_panel && m_panel->isShown(), overlap.toRect());
|
2016-04-29 13:05:03 +00:00
|
|
|
}
|
|
|
|
|
2021-04-27 15:49:55 +00:00
|
|
|
void InputMethod::setInputMethodCommand(const QString &command)
|
|
|
|
{
|
|
|
|
if (m_inputMethodCommand == command) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_inputMethodCommand = command;
|
|
|
|
|
|
|
|
if (m_enabled) {
|
|
|
|
startInputMethod();
|
|
|
|
}
|
2021-07-22 18:12:54 +00:00
|
|
|
Q_EMIT availableChanged();
|
2021-04-27 15:49:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void InputMethod::stopInputMethod()
|
|
|
|
{
|
|
|
|
if (!m_inputMethodProcess) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
disconnect(m_inputMethodProcess, nullptr, this, nullptr);
|
|
|
|
|
|
|
|
m_inputMethodProcess->terminate();
|
|
|
|
if (!m_inputMethodProcess->waitForFinished()) {
|
|
|
|
m_inputMethodProcess->kill();
|
|
|
|
m_inputMethodProcess->waitForFinished();
|
|
|
|
}
|
|
|
|
m_inputMethodProcess->deleteLater();
|
|
|
|
m_inputMethodProcess = nullptr;
|
2021-10-14 07:40:08 +00:00
|
|
|
|
|
|
|
waylandServer()->destroyInputMethodConnection();
|
2021-04-27 15:49:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void InputMethod::startInputMethod()
|
|
|
|
{
|
|
|
|
stopInputMethod();
|
|
|
|
if (m_inputMethodCommand.isEmpty() || kwinApp()->isTerminating()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
QStringList arguments = KShell::splitArgs(m_inputMethodCommand);
|
|
|
|
if (arguments.isEmpty()) {
|
|
|
|
qWarning("Failed to launch the input method server: %s is an invalid command", qPrintable(m_inputMethodCommand));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const QString program = arguments.takeFirst();
|
|
|
|
int socket = waylandServer()->createInputMethodConnection();
|
|
|
|
if (socket < 0) {
|
|
|
|
qWarning("Failed to create the input method connection");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
socket = dup(socket);
|
|
|
|
|
|
|
|
QProcessEnvironment environment = kwinApp()->processStartupEnvironment();
|
|
|
|
environment.insert(QStringLiteral("WAYLAND_SOCKET"), QByteArray::number(socket));
|
|
|
|
environment.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("wayland"));
|
2022-02-22 13:34:57 +00:00
|
|
|
// When we use Maliit as virtual keyboard, we want KWin to handle the animation
|
|
|
|
// since that works a lot better. So we need to tell Maliit to not do client side
|
|
|
|
// animation.
|
|
|
|
environment.insert(QStringLiteral("MALIIT_ENABLE_ANIMATIONS"), "0");
|
2021-04-27 15:49:55 +00:00
|
|
|
|
2022-01-10 20:30:37 +00:00
|
|
|
m_inputMethodProcess = new QProcess(this);
|
2021-04-27 15:49:55 +00:00
|
|
|
m_inputMethodProcess->setProcessChannelMode(QProcess::ForwardedErrorChannel);
|
|
|
|
m_inputMethodProcess->setProcessEnvironment(environment);
|
|
|
|
m_inputMethodProcess->setProgram(program);
|
|
|
|
m_inputMethodProcess->setArguments(arguments);
|
|
|
|
m_inputMethodProcess->start();
|
|
|
|
close(socket);
|
|
|
|
connect(m_inputMethodProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus) {
|
|
|
|
if (exitStatus == QProcess::CrashExit) {
|
|
|
|
m_inputMethodCrashes++;
|
|
|
|
m_inputMethodCrashTimer.start();
|
|
|
|
qWarning() << "Input Method crashed" << m_inputMethodProcess->program() << m_inputMethodProcess->arguments() << exitCode << exitStatus;
|
|
|
|
if (m_inputMethodCrashes < 5) {
|
|
|
|
startInputMethod();
|
|
|
|
} else {
|
|
|
|
qWarning() << "Input Method keeps crashing, please fix" << m_inputMethodProcess->program() << m_inputMethodProcess->arguments();
|
|
|
|
stopInputMethod();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2021-05-21 15:57:21 +00:00
|
|
|
bool InputMethod::isActive() const
|
|
|
|
{
|
|
|
|
return waylandServer()->inputMethod()->context();
|
|
|
|
}
|
|
|
|
|
2021-12-13 17:23:23 +00:00
|
|
|
KWaylandServer::InputMethodGrabV1 *InputMethod::keyboardGrab()
|
|
|
|
{
|
|
|
|
return isActive() ? m_keyboardGrab : nullptr;
|
|
|
|
}
|
2021-06-01 16:15:47 +00:00
|
|
|
|
|
|
|
void InputMethod::installKeyboardGrab(KWaylandServer::InputMethodGrabV1 *keyboardGrab)
|
|
|
|
{
|
|
|
|
auto xkb = input()->keyboard()->xkb();
|
2021-12-13 17:23:23 +00:00
|
|
|
m_keyboardGrab = keyboardGrab;
|
2021-06-01 16:15:47 +00:00
|
|
|
keyboardGrab->sendKeymap(xkb->keymapContents());
|
2021-12-29 17:19:43 +00:00
|
|
|
forwardModifiers(Force);
|
2021-06-01 16:15:47 +00:00
|
|
|
}
|
|
|
|
|
2021-09-30 22:04:15 +00:00
|
|
|
void InputMethod::updateModifiersMap(const QByteArray &modifiers)
|
|
|
|
{
|
|
|
|
TextInputV2Interface *t2 = waylandServer()->seat()->textInputV2();
|
|
|
|
|
|
|
|
if (t2 && t2->isEnabled()) {
|
|
|
|
t2->setModifiersMap(modifiers);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-21 00:04:33 +00:00
|
|
|
bool InputMethod::isVisible() const
|
|
|
|
{
|
2023-03-20 20:21:50 +00:00
|
|
|
return m_panel && m_panel->isShown() && m_panel->readyForPainting();
|
2016-04-29 13:05:03 +00:00
|
|
|
}
|
2021-05-21 15:57:21 +00:00
|
|
|
|
2021-07-22 15:38:13 +00:00
|
|
|
bool InputMethod::isAvailable() const
|
|
|
|
{
|
2021-07-22 18:12:54 +00:00
|
|
|
return !m_inputMethodCommand.isEmpty();
|
2021-07-22 15:38:13 +00:00
|
|
|
}
|
|
|
|
|
2022-03-23 10:13:38 +00:00
|
|
|
void InputMethod::resetPendingPreedit()
|
|
|
|
{
|
2021-12-23 08:42:00 +00:00
|
|
|
preedit.text = QString();
|
|
|
|
preedit.cursor = 0;
|
|
|
|
preedit.highlightRanges.clear();
|
|
|
|
}
|
|
|
|
|
2022-09-06 22:56:19 +00:00
|
|
|
bool InputMethod::activeClientSupportsTextInput() const
|
|
|
|
{
|
|
|
|
return m_activeClientSupportsTextInput;
|
|
|
|
}
|
|
|
|
|
|
|
|
void InputMethod::forceActivate()
|
|
|
|
{
|
|
|
|
setActive(true);
|
|
|
|
show();
|
|
|
|
}
|
|
|
|
|
2022-10-24 23:38:40 +00:00
|
|
|
void InputMethod::textInputInterfaceV3EnableRequested()
|
|
|
|
{
|
|
|
|
refreshActive();
|
|
|
|
show();
|
|
|
|
}
|
2021-07-21 00:04:33 +00:00
|
|
|
}
|