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"
|
2021-02-26 15:16:45 +00:00
|
|
|
#include "abstract_client.h"
|
2017-10-06 19:22:50 +00:00
|
|
|
#include "virtualkeyboard_dbus.h"
|
2016-08-03 06:31:58 +00:00
|
|
|
#include "input.h"
|
2017-08-16 19:18:01 +00:00
|
|
|
#include "keyboard_input.h"
|
2016-04-29 13:05:03 +00:00
|
|
|
#include "utils.h"
|
|
|
|
#include "screens.h"
|
|
|
|
#include "wayland_server.h"
|
|
|
|
#include "workspace.h"
|
2020-02-06 09:33:23 +00:00
|
|
|
#include "screenlockerwatcher.h"
|
2021-03-05 17:20:56 +00:00
|
|
|
#include "deleted.h"
|
2016-04-29 13:05:03 +00:00
|
|
|
|
2020-04-29 15:18:41 +00:00
|
|
|
#include <KWaylandServer/display.h>
|
2021-03-10 14:28:50 +00:00
|
|
|
#include <KWaylandServer/keyboard_interface.h>
|
2020-04-29 15:18:41 +00:00
|
|
|
#include <KWaylandServer/seat_interface.h>
|
2020-09-25 06:51:04 +00:00
|
|
|
#include <KWaylandServer/textinput_v3_interface.h>
|
2020-04-29 15:18:41 +00:00
|
|
|
#include <KWaylandServer/surface_interface.h>
|
2020-07-11 16:40:28 +00:00
|
|
|
#include <KWaylandServer/inputmethod_v1_interface.h>
|
2016-04-29 13:05:03 +00:00
|
|
|
|
2021-04-27 15:49:55 +00:00
|
|
|
#include <KShell>
|
2016-04-29 13:05:03 +00:00
|
|
|
#include <KStatusNotifierItem>
|
|
|
|
#include <KLocalizedString>
|
|
|
|
|
|
|
|
#include <QDBusConnection>
|
|
|
|
#include <QDBusPendingCall>
|
|
|
|
#include <QDBusMessage>
|
2021-04-27 19:18:17 +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>
|
|
|
|
#include <xkbcommon/xkbcommon-keysyms.h>
|
2021-04-27 15:49:55 +00:00
|
|
|
#include <unistd.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
|
|
|
|
{
|
|
|
|
|
2020-09-29 14:46:32 +00:00
|
|
|
KWIN_SINGLETON_FACTORY(InputMethod)
|
2016-04-29 13:05:03 +00:00
|
|
|
|
2020-09-29 14:46:32 +00:00
|
|
|
InputMethod::InputMethod(QObject *parent)
|
2016-04-29 13:05:03 +00:00
|
|
|
: QObject(parent)
|
|
|
|
{
|
|
|
|
// 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
|
|
|
}
|
|
|
|
|
2020-09-29 14:46:32 +00:00
|
|
|
InputMethod::~InputMethod() = default;
|
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;
|
|
|
|
});
|
2020-09-29 14:46:32 +00:00
|
|
|
connect(ScreenLockerWatcher::self(), &ScreenLockerWatcher::aboutToLock, this, &InputMethod::hide);
|
2020-02-06 09:33:23 +00:00
|
|
|
|
2016-04-29 13:05:03 +00:00
|
|
|
if (waylandServer()) {
|
2016-08-03 06:31:58 +00:00
|
|
|
m_enabled = !input()->hasAlphaNumericKeyboard();
|
2018-12-10 10:57:24 +00:00
|
|
|
qCDebug(KWIN_VIRTUALKEYBOARD) << "enabled by default: " << m_enabled;
|
2016-08-03 06:31:58 +00:00
|
|
|
connect(input(), &InputRedirection::hasAlphaNumericKeyboardChanged, this,
|
|
|
|
[this] (bool set) {
|
2020-09-29 14:46:32 +00:00
|
|
|
qCDebug(KWIN_VIRTUALKEYBOARD) << "AlphaNumeric Keyboard changed:" << set << "toggling virtual keyboard.";
|
2016-08-03 06:31:58 +00:00
|
|
|
setEnabled(!set);
|
2016-04-29 13:05:03 +00:00
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-12-10 10:57:24 +00:00
|
|
|
qCDebug(KWIN_VIRTUALKEYBOARD) << "Registering the SNI";
|
2021-04-27 19:18:17 +00:00
|
|
|
|
|
|
|
QMenu *sniMenu = new QMenu;
|
|
|
|
sniMenu->addAction(i18n("Configure virtual keyboards..."), this, [] {
|
|
|
|
QProcess::startDetached("systemsettings5", {"kcm_virtualkeyboard"});
|
|
|
|
});
|
|
|
|
|
2016-04-29 13:05:03 +00:00
|
|
|
m_sni = new KStatusNotifierItem(QStringLiteral("kwin-virtual-keyboard"), this);
|
2017-01-26 19:33:59 +00:00
|
|
|
m_sni->setStandardActionsEnabled(false);
|
2016-04-29 13:05:03 +00:00
|
|
|
m_sni->setCategory(KStatusNotifierItem::Hardware);
|
|
|
|
m_sni->setStatus(KStatusNotifierItem::Passive);
|
|
|
|
m_sni->setTitle(i18n("Virtual Keyboard"));
|
2021-04-27 19:18:17 +00:00
|
|
|
m_sni->setToolTipTitle(i18n("Whether to show the virtual keyboard on demand."));
|
|
|
|
m_sni->setStandardActionsEnabled(false);
|
|
|
|
m_sni->setContextMenu(sniMenu);
|
2016-04-29 13:05:03 +00:00
|
|
|
updateSni();
|
|
|
|
connect(m_sni, &KStatusNotifierItem::activateRequested, this,
|
|
|
|
[this] {
|
|
|
|
setEnabled(!m_enabled);
|
|
|
|
}
|
|
|
|
);
|
2020-09-29 14:46:32 +00:00
|
|
|
connect(this, &InputMethod::enabledChanged, this, &InputMethod::updateSni);
|
2017-10-06 19:22:50 +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()) {
|
2020-12-09 21:24:41 +00:00
|
|
|
new TextInputManagerV2Interface(waylandServer()->display());
|
|
|
|
new TextInputManagerV3Interface(waylandServer()->display());
|
2020-09-25 06:51:04 +00:00
|
|
|
|
2020-09-29 14:46:32 +00:00
|
|
|
connect(workspace(), &Workspace::clientAdded, this, &InputMethod::clientAdded);
|
|
|
|
connect(waylandServer()->seat(), &SeatInterface::focusedTextInputSurfaceChanged, this, &InputMethod::handleFocusedSurfaceChanged);
|
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);
|
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);
|
2021-02-19 02:20:54 +00:00
|
|
|
|
|
|
|
if (m_enabled) {
|
|
|
|
connect(textInputV2, &TextInputV2Interface::enabledChanged, this, &InputMethod::textInputInterfaceV2EnabledChanged);
|
|
|
|
connect(textInputV3, &TextInputV3Interface::enabledChanged, this, &InputMethod::textInputInterfaceV3EnabledChanged);
|
|
|
|
}
|
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
|
|
|
{
|
2021-02-26 15:16:45 +00:00
|
|
|
setActive(true);
|
2021-02-23 15:12:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void InputMethod::hide()
|
|
|
|
{
|
2021-02-26 15:16:45 +00:00
|
|
|
setActive(false);
|
2021-02-23 15:12:38 +00:00
|
|
|
}
|
|
|
|
|
2021-02-26 15:16:45 +00:00
|
|
|
void InputMethod::setActive(bool active)
|
2021-02-23 15:12:38 +00:00
|
|
|
{
|
2021-02-26 15:16:45 +00:00
|
|
|
if (m_active) {
|
2021-02-18 04:46:21 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
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-02-26 15:16:45 +00:00
|
|
|
if (m_active != active) {
|
|
|
|
m_active = active;
|
|
|
|
Q_EMIT activeChanged(active);
|
2021-02-23 15:12:38 +00:00
|
|
|
}
|
2016-04-29 13:05:03 +00:00
|
|
|
}
|
|
|
|
|
2020-09-29 14:46:32 +00:00
|
|
|
void InputMethod::clientAdded(AbstractClient* client)
|
2020-09-18 03:17:15 +00:00
|
|
|
{
|
|
|
|
if (!client->isInputMethod()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
m_inputClient = client;
|
2021-04-29 15:53:44 +00:00
|
|
|
connect(client->surface(), &SurfaceInterface::inputChanged, this, &InputMethod::updateInputPanelState);
|
2020-09-18 03:17:15 +00:00
|
|
|
connect(client, &QObject::destroyed, this, [this] {
|
|
|
|
if (m_trackedClient) {
|
|
|
|
m_trackedClient->setVirtualKeyboardGeometry({});
|
|
|
|
}
|
|
|
|
});
|
2021-04-29 15:53:44 +00:00
|
|
|
connect(m_inputClient, &AbstractClient::frameGeometryChanged, this, &InputMethod::updateInputPanelState);
|
2021-03-06 03:37:51 +00:00
|
|
|
// Current code have a assumption that InputMethod started by the kwin is virtual keyboard,
|
|
|
|
// InputMethod::hide sends out a deactivate signal to input-method client, this is not desired
|
|
|
|
// when we support input methods like ibus which can show and hide surfaces/windows as they please
|
|
|
|
// and are not exactly Virtual keyboards.
|
2021-03-05 17:20:56 +00:00
|
|
|
connect(m_inputClient, &AbstractClient::windowHidden, this, &InputMethod::hide);
|
|
|
|
connect(m_inputClient, &AbstractClient::windowClosed, this, &InputMethod::hide);
|
2020-09-18 03:17:15 +00:00
|
|
|
}
|
|
|
|
|
2020-09-29 14:46:32 +00:00
|
|
|
void InputMethod::handleFocusedSurfaceChanged()
|
2020-09-18 03:17:15 +00:00
|
|
|
{
|
2020-09-23 10:02:54 +00:00
|
|
|
SurfaceInterface *focusedSurface = waylandServer()->seat()->focusedTextInputSurface();
|
|
|
|
if (focusedSurface) {
|
|
|
|
AbstractClient *focusedClient = waylandServer()->findClient(focusedSurface);
|
2020-09-18 03:17:15 +00:00
|
|
|
// Reset the old client virtual keybaord geom if necessary
|
|
|
|
// Old and new clients could be the same if focus moves between subsurfaces
|
2020-09-23 10:02:54 +00:00
|
|
|
if (m_trackedClient != focusedClient) {
|
2020-09-18 03:17:15 +00:00
|
|
|
if (m_trackedClient) {
|
|
|
|
m_trackedClient->setVirtualKeyboardGeometry(QRect());
|
2021-05-04 21:07:16 +00:00
|
|
|
disconnect(m_trackedClient, &AbstractClient::frameGeometryChanged, this, &InputMethod::updateInputPanelState);
|
2020-09-18 03:17:15 +00:00
|
|
|
}
|
2020-09-23 10:02:54 +00:00
|
|
|
m_trackedClient = focusedClient;
|
2021-05-09 18:42:51 +00:00
|
|
|
if (m_trackedClient) {
|
|
|
|
connect(m_trackedClient, &AbstractClient::frameGeometryChanged, this, &InputMethod::updateInputPanelState, Qt::QueuedConnection);
|
|
|
|
}
|
2020-09-18 03:17:15 +00:00
|
|
|
}
|
2021-02-23 15:12:38 +00:00
|
|
|
updateInputPanelState();
|
2020-09-18 03:17:15 +00:00
|
|
|
} else {
|
2021-02-26 15:16:45 +00:00
|
|
|
setActive(false);
|
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
|
|
|
{
|
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->sendContentType(t2->contentHints(), t2->contentPurpose());
|
|
|
|
}
|
|
|
|
if (t3 && t3->isEnabled()) {
|
|
|
|
inputContext->sendContentType(t3->contentHints(), t3->contentPurpose());
|
|
|
|
}
|
2020-09-18 03:17:15 +00:00
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
switch (reason) {
|
|
|
|
case KWaylandServer::TextInputV2Interface::UpdateReason::StateChange:
|
|
|
|
inputContext->sendCommitState(serial);
|
|
|
|
break;
|
|
|
|
case KWaylandServer::TextInputV2Interface::UpdateReason::StateEnter:
|
|
|
|
waylandServer()->inputMethod()->sendActivate();
|
|
|
|
inputContext->sendCommitState(serial);
|
|
|
|
break;
|
|
|
|
case KWaylandServer::TextInputV2Interface::UpdateReason::StateFull:
|
|
|
|
adoptInputMethodContext();
|
|
|
|
inputContext->sendCommitState(serial);
|
|
|
|
break;
|
|
|
|
case KWaylandServer::TextInputV2Interface::UpdateReason::StateReset:
|
|
|
|
inputContext->sendReset();
|
|
|
|
inputContext->sendCommitState(serial);
|
|
|
|
break;
|
2020-09-25 06:51:04 +00:00
|
|
|
}
|
2020-09-18 03:17:15 +00:00
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-09-22 03:14:57 +00:00
|
|
|
auto t = waylandServer()->seat()->textInputV2();
|
2020-09-18 03:17:15 +00:00
|
|
|
if (t->isEnabled()) {
|
|
|
|
waylandServer()->inputMethod()->sendActivate();
|
|
|
|
adoptInputMethodContext();
|
|
|
|
} else {
|
|
|
|
hide();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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();
|
|
|
|
if (t3->isEnabled()) {
|
|
|
|
waylandServer()->inputMethod()->sendActivate();
|
|
|
|
} else {
|
|
|
|
waylandServer()->inputMethod()->sendDeactivate();
|
|
|
|
// reset value of preedit when textinput is disabled
|
|
|
|
preedit.text = QString();
|
|
|
|
preedit.begin = 0;
|
|
|
|
preedit.end = 0;
|
|
|
|
}
|
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()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
TextInputV3Interface *textInputV3 = waylandServer()->seat()->textInputV3();
|
|
|
|
if (!textInputV3) {
|
2020-09-18 03:17:15 +00:00
|
|
|
return;
|
|
|
|
}
|
2021-05-10 16:03:14 +00:00
|
|
|
|
|
|
|
if (!isActive() && textInputV3->isEnabled()) {
|
|
|
|
show();
|
|
|
|
}
|
|
|
|
|
|
|
|
auto inputContext = waylandServer()->inputMethod()->context();
|
2020-09-18 03:17:15 +00:00
|
|
|
inputContext->sendCommitState(serial);
|
|
|
|
}
|
|
|
|
|
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;
|
2017-10-06 19:22:50 +00:00
|
|
|
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"),
|
|
|
|
QStringLiteral("virtualKeyboardEnabledChanged")
|
|
|
|
);
|
|
|
|
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
|
|
|
}
|
2016-04-29 13:05:03 +00:00
|
|
|
}
|
|
|
|
|
2020-09-25 06:51:04 +00:00
|
|
|
static quint32 keysymToKeycode(quint32 sym)
|
|
|
|
{
|
|
|
|
switch(sym) {
|
|
|
|
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-03-10 02:12:57 +00:00
|
|
|
void InputMethod::keysymReceived(quint32 serial, quint32 time, quint32 sym, bool pressed, Qt::KeyboardModifiers modifiers)
|
2020-07-11 16:40:28 +00:00
|
|
|
{
|
|
|
|
Q_UNUSED(serial)
|
|
|
|
Q_UNUSED(time)
|
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;
|
|
|
|
}
|
|
|
|
auto t3 = waylandServer()->seat()->textInputV3();
|
|
|
|
if (t3 && t3->isEnabled()) {
|
2021-03-24 10:07:09 +00:00
|
|
|
KWaylandServer::KeyboardKeyState state;
|
2020-07-11 16:40:28 +00:00
|
|
|
if (pressed) {
|
2021-03-24 10:07:09 +00:00
|
|
|
state = KWaylandServer::KeyboardKeyState::Pressed;
|
2020-07-11 16:40:28 +00:00
|
|
|
} else {
|
2021-03-24 10:07:09 +00:00
|
|
|
state = KWaylandServer::KeyboardKeyState::Released;
|
2020-07-11 16:40:28 +00:00
|
|
|
}
|
2021-03-24 10:07:09 +00:00
|
|
|
waylandServer()->seat()->notifyKeyboardKey(keysymToKeycode(sym), state);
|
2020-09-25 06:51:04 +00:00
|
|
|
return;
|
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
|
|
|
{
|
|
|
|
Q_UNUSED(serial)
|
2020-09-25 06:51:04 +00:00
|
|
|
auto t2 = waylandServer()->seat()->textInputV2();
|
|
|
|
if (t2 && t2->isEnabled()) {
|
|
|
|
t2->commitString(text.toUtf8());
|
|
|
|
t2->preEdit({}, {});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
auto t3 = waylandServer()->seat()->textInputV3();
|
|
|
|
if (t3 && t3->isEnabled()) {
|
|
|
|
t3->commitString(text.toUtf8());
|
|
|
|
t3->done();
|
|
|
|
return;
|
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
|
|
|
{
|
2020-09-25 06:51:04 +00:00
|
|
|
auto t2 = waylandServer()->seat()->textInputV2();
|
|
|
|
if (t2 && t2->isEnabled()) {
|
|
|
|
t2->deleteSurroundingText(index, length);
|
|
|
|
}
|
|
|
|
auto t3 = waylandServer()->seat()->textInputV3();
|
|
|
|
if (t3 && t3->isEnabled()) {
|
|
|
|
t3->deleteSurroundingText(index, length);
|
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
|
|
|
{
|
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
|
|
|
{
|
2020-09-25 06:51:04 +00:00
|
|
|
Q_UNUSED(serial)
|
|
|
|
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
|
|
|
{
|
2020-09-25 06:51:04 +00:00
|
|
|
Q_UNUSED(serial)
|
|
|
|
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
|
|
|
{
|
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()) {
|
|
|
|
preedit.begin = index;
|
|
|
|
preedit.end = index;
|
|
|
|
t3->sendPreEditString(preedit.text, preedit.begin, preedit.end);
|
2020-07-11 16:40:28 +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
|
|
|
{
|
|
|
|
Q_UNUSED(serial)
|
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;
|
|
|
|
t3->sendPreEditString(preedit.text, preedit.begin, preedit.end);
|
2020-07-11 16:40:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-29 14:46:32 +00:00
|
|
|
void InputMethod::adoptInputMethodContext()
|
2020-07-11 16:40:28 +00:00
|
|
|
{
|
|
|
|
auto inputContext = waylandServer()->inputMethod()->context();
|
|
|
|
|
2020-09-25 06:51:04 +00:00
|
|
|
TextInputV2Interface *t2 = waylandServer()->seat()->textInputV2();
|
|
|
|
TextInputV3Interface *t3 = waylandServer()->seat()->textInputV3();
|
|
|
|
|
|
|
|
if (t2 && t2->isEnabled()) {
|
|
|
|
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);
|
2020-09-25 06:51:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (t3 && t3->isEnabled()) {
|
|
|
|
inputContext->sendSurroundingText(t3->surroundingText(), t3->surroundingTextCursorPosition(), t3->surroundingTextSelectionAnchor());
|
|
|
|
inputContext->sendContentType(t3->contentHints(), t3->contentPurpose());
|
|
|
|
}
|
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);
|
|
|
|
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-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);
|
2020-07-11 16:40:28 +00:00
|
|
|
}
|
|
|
|
|
2020-09-29 14:46:32 +00:00
|
|
|
void InputMethod::updateSni()
|
2016-04-29 13:05:03 +00:00
|
|
|
{
|
|
|
|
if (!m_sni) {
|
|
|
|
return;
|
|
|
|
}
|
2021-04-27 19:18:17 +00:00
|
|
|
if (m_inputMethodCommand.isEmpty()) {
|
|
|
|
m_sni->setIconByName(QStringLiteral("input-keyboard-virtual-on"));
|
|
|
|
m_sni->setOverlayIconByName(QStringLiteral("emblem-unavailable"));
|
|
|
|
m_sni->setTitle(i18n("No Virtual Keyboard configured"));
|
|
|
|
} else if (m_enabled) {
|
2016-04-29 13:05:03 +00:00
|
|
|
m_sni->setIconByName(QStringLiteral("input-keyboard-virtual-on"));
|
2019-03-19 20:54:21 +00:00
|
|
|
m_sni->setTitle(i18n("Virtual Keyboard: enabled"));
|
2021-04-27 19:18:17 +00:00
|
|
|
m_sni->setOverlayIconByName({});
|
2016-04-29 13:05:03 +00:00
|
|
|
} else {
|
|
|
|
m_sni->setIconByName(QStringLiteral("input-keyboard-virtual-off"));
|
2019-03-19 20:54:21 +00:00
|
|
|
m_sni->setTitle(i18n("Virtual Keyboard: disabled"));
|
2021-04-27 19:18:17 +00:00
|
|
|
m_sni->setOverlayIconByName({});
|
2016-04-29 13:05:03 +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;
|
|
|
|
}
|
|
|
|
|
2021-04-29 15:53:44 +00:00
|
|
|
QRect overlap = QRect(0, 0, 0, 0);
|
2020-09-21 07:35:34 +00:00
|
|
|
if (m_trackedClient) {
|
|
|
|
m_trackedClient->setVirtualKeyboardGeometry(m_inputClient ? m_inputClient->inputGeometry() : QRect());
|
2021-04-29 15:53:44 +00:00
|
|
|
|
|
|
|
if (m_inputClient) {
|
|
|
|
overlap = m_trackedClient->frameGeometry() & m_inputClient->inputGeometry();
|
|
|
|
overlap.moveTo(m_trackedClient->mapToLocal(overlap.topLeft()));
|
|
|
|
}
|
2020-09-21 07:35:34 +00:00
|
|
|
}
|
2021-04-29 15:53:44 +00:00
|
|
|
t->setInputPanelState(m_inputClient && m_inputClient->isShown(false), overlap);
|
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-04-27 19:18:17 +00:00
|
|
|
updateSni();
|
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();
|
|
|
|
}
|
|
|
|
if (waylandServer()) {
|
|
|
|
waylandServer()->destroyInputMethodConnection();
|
|
|
|
}
|
|
|
|
m_inputMethodProcess->deleteLater();
|
|
|
|
m_inputMethodProcess = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
void InputMethod::startInputMethod()
|
|
|
|
{
|
|
|
|
stopInputMethod();
|
|
|
|
if (m_inputMethodCommand.isEmpty() || kwinApp()->isTerminating()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
connect(waylandServer(), &WaylandServer::terminatingInternalClientConnection, this, &InputMethod::stopInputMethod, Qt::UniqueConnection);
|
|
|
|
|
|
|
|
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"));
|
|
|
|
environment.remove("DISPLAY");
|
|
|
|
environment.remove("WAYLAND_DISPLAY");
|
|
|
|
environment.remove("XAUTHORITY");
|
|
|
|
|
|
|
|
m_inputMethodProcess = new Process(this);
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2016-04-29 13:05:03 +00:00
|
|
|
}
|