Integrate input-method-unstable-v1

This commit is contained in:
Aleix Pol 2020-07-11 18:40:28 +02:00 committed by Aleix Pol Gonzalez
parent 494c89a2d9
commit 7aabd45df9
7 changed files with 416 additions and 28 deletions

View file

@ -468,6 +468,7 @@ set(kwin_SRCS
input.cpp
input_event.cpp
input_event_spy.cpp
inputpanelv1client.cpp
internal_client.cpp
keyboard_input.cpp
keyboard_layout.cpp

152
inputpanelv1client.cpp Normal file
View file

@ -0,0 +1,152 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez <aleixpol@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "inputpanelv1client.h"
#include "deleted.h"
#include "wayland_server.h"
#include "workspace.h"
#include "abstract_wayland_output.h"
#include "platform.h"
#include <KWaylandServer/output_interface.h>
#include <KWaylandServer/seat_interface.h>
#include <KWaylandServer/surface_interface.h>
#include <KWaylandServer/textinput_interface.h>
using namespace KWin;
using namespace KWaylandServer;
InputPanelV1Client::InputPanelV1Client(InputPanelSurfaceV1Interface *panelSurface)
: WaylandClient(panelSurface->surface())
, m_panelSurface(panelSurface)
{
setSkipPager(true);
setSkipTaskbar(true);
setKeepAbove(true);
setupCompositing();
connect(surface(), &SurfaceInterface::aboutToBeDestroyed, this, &InputPanelV1Client::destroyClient);
connect(surface(), &SurfaceInterface::sizeChanged, this, &InputPanelV1Client::reposition);
connect(surface(), &SurfaceInterface::mapped, this, &InputPanelV1Client::updateDepth);
connect(surface(), &SurfaceInterface::damaged, this, QOverload<const QRegion &>::of(&WaylandClient::addRepaint));
connect(panelSurface, &InputPanelSurfaceV1Interface::topLevel, this, &InputPanelV1Client::showTopLevel);
connect(panelSurface, &InputPanelSurfaceV1Interface::overlayPanel, this, &InputPanelV1Client::showOverlayPanel);
connect(panelSurface, &InputPanelSurfaceV1Interface::destroyed, this, &InputPanelV1Client::destroyClient);
}
void InputPanelV1Client::showOverlayPanel()
{
setOutput(nullptr);
m_mode = Overlay;
reposition();
setReadyForPainting();
}
void InputPanelV1Client::showTopLevel(OutputInterface *output, InputPanelSurfaceV1Interface::Position position)
{
Q_UNUSED(position);
m_mode = Toplevel;
setOutput(output);
reposition();
setReadyForPainting();
}
void KWin::InputPanelV1Client::reposition()
{
switch (m_mode) {
case Toplevel: {
if (m_output) {
const QSize panelSize = surface()->size();
if (!panelSize.isValid() || panelSize.isEmpty()) {
return;
}
const auto outputGeometry = m_output->geometry();
QRect geo(outputGeometry.topLeft(), panelSize);
geo.translate((outputGeometry.width() - panelSize.width())/2, outputGeometry.height() - panelSize.height());
setFrameGeometry(geo);
}
} break;
case Overlay: {
auto textClient = waylandServer()->findClient(waylandServer()->seat()->focusedTextInputSurface());
auto textInput = waylandServer()->seat()->focusedTextInput();
if (textClient && textInput) {
const auto cursorRectangle = textInput->cursorRectangle();
setFrameGeometry({textClient->pos() + textClient->clientPos() + cursorRectangle.bottomLeft(), surface()->size()});
}
} break;
}
}
void InputPanelV1Client::setFrameGeometry(const QRect &geometry, ForceGeometry_t force)
{
Q_UNUSED(force);
if (m_frameGeometry != geometry) {
const QRect oldFrameGeometry = m_frameGeometry;
m_frameGeometry = geometry;
m_clientGeometry = geometry;
emit frameGeometryChanged(this, oldFrameGeometry);
emit clientGeometryChanged(this, oldFrameGeometry);
emit bufferGeometryChanged(this, oldFrameGeometry);
emit geometryShapeChanged(this, oldFrameGeometry);
addRepaintDuringGeometryUpdates();
}
}
void InputPanelV1Client::destroyClient()
{
markAsZombie();
Deleted *deleted = Deleted::create(this);
emit windowClosed(this, deleted);
StackingUpdatesBlocker blocker(workspace());
waylandServer()->removeClient(this);
deleted->unrefWindow();
delete this;
}
void InputPanelV1Client::debug(QDebug &stream) const
{
stream << "InputPanelClient(" << static_cast<const void*>(this) << "," << resourceClass() << m_frameGeometry << ')';
}
NET::WindowType InputPanelV1Client::windowType(bool, int) const
{
return NET::Utility;
}
void InputPanelV1Client::hideClient(bool hide)
{
m_visible = !hide;
if (hide) {
workspace()->clientHidden(this);
addWorkspaceRepaint(visibleRect());
Q_EMIT windowHidden(this);
} else {
reposition();
addRepaintFull();
Q_EMIT windowShown(this);
}
}
void InputPanelV1Client::setOutput(OutputInterface *outputIface)
{
if (m_output) {
disconnect(m_output, &AbstractWaylandOutput::geometryChanged, this, &InputPanelV1Client::reposition);
}
m_output = waylandServer()->findOutput(outputIface);
if (m_output) {
connect(m_output, &AbstractWaylandOutput::geometryChanged, this, &InputPanelV1Client::reposition);
}
}

71
inputpanelv1client.h Normal file
View file

@ -0,0 +1,71 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez <aleixpol@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "waylandclient.h"
#include <QPointer>
#include <KWaylandServer/inputmethod_v1_interface.h>
namespace KWin
{
class AbstractWaylandOutput;
class InputPanelV1Client : public WaylandClient
{
Q_OBJECT
public:
InputPanelV1Client(KWaylandServer::InputPanelSurfaceV1Interface *panelSurface);
enum Mode {
Toplevel,
Overlay,
};
Q_ENUM(Mode)
void setFrameGeometry(const QRect &geometry, KWin::AbstractClient::ForceGeometry_t force = NormalGeometrySet) override;
void destroyClient() override;
QRect bufferGeometry() const override { return frameGeometry(); }
bool isCloseable() const override { return false; }
bool noBorder() const override { return true; }
bool isResizable() const override { return false; }
bool isMovable() const override { return false; }
bool isMovableAcrossScreens() const override { return false; }
bool userCanSetNoBorder() const override { return false; }
bool acceptsFocus() const override { return false; }
void showOnScreenEdge() override {}
bool supportsWindowRules() const override { return false; }
void closeWindow() override {}
void hideClient(bool hide) override;
bool isHiddenInternal() const override { return !m_visible; }
bool takeFocus() override { return false; }
void updateColorScheme() override {}
bool wantsInput() const override { return false; }
bool isInputMethod() const override { return true; }
bool isShown(bool /*shaded_is_shown*/) const override { return m_visible && !isZombie(); }
bool isInitialPositionSet() const override { return true; }
void updateDecoration(bool /*check_workspace_pos*/, bool /*force*/) override {}
void setNoBorder(bool /*set*/) override {}
NET::WindowType windowType(bool /*direct*/, int /*supported_types*/) const override;
void debug(QDebug & stream) const override;
private:
void showTopLevel(KWaylandServer::OutputInterface *output, KWaylandServer::InputPanelSurfaceV1Interface::Position position);
void showOverlayPanel();
void reposition();
void setOutput(KWaylandServer::OutputInterface* output);
QPointer<AbstractWaylandOutput> m_output;
Mode m_mode = Toplevel;
const QPointer<KWaylandServer::InputPanelSurfaceV1Interface> m_panelSurface;
bool m_visible = true;
};
}

View file

@ -21,6 +21,7 @@
#include <KWaylandServer/seat_interface.h>
#include <KWaylandServer/textinput_interface.h>
#include <KWaylandServer/surface_interface.h>
#include <KWaylandServer/inputmethod_v1_interface.h>
#include <KStatusNotifierItem>
#include <KLocalizedString>
@ -115,6 +116,33 @@ void VirtualKeyboard::init()
t->create();
auto t2 = waylandServer()->display()->createTextInputManager(TextInputInterfaceVersion::UnstableV2, waylandServer()->display());
t2->create();
auto inputPanel = waylandServer()->display()->createInputPanelInterface(this);
connect(inputPanel, &InputPanelV1Interface::inputPanelSurfaceAdded, this, [this, inputPanel] (InputPanelSurfaceV1Interface *surface) {
m_inputClient = waylandServer()->createInputPanelClient(surface);
auto refreshFrame = [this] {
const QRect inputGeometry = m_inputClient->surface()->input().boundingRect();
if (!m_trackedClient || inputGeometry.isEmpty()) {
return;
}
m_trackedClient->setVirtualKeyboardGeometry(inputGeometry);
};
connect(surface->surface(), &SurfaceInterface::inputChanged, this, refreshFrame);
connect(surface->surface(), &SurfaceInterface::inputChanged, this, refreshFrame);
connect(this, &VirtualKeyboard::hide, m_inputClient, [this] {
m_inputClient->hideClient(true);
});
connect(this, &VirtualKeyboard::show, m_inputClient, [this] {
m_inputClient->hideClient(false);
});
connect(surface->surface(), &SurfaceInterface::unmapped, this, [this, inputPanel, surface] {
m_inputClient->destroyClient();
inputPanel->inputPanelSurfaceAdded(surface);
});
refreshFrame();
});
connect(waylandServer()->seat(), &SeatInterface::focusedTextInputChanged, this,
[this] {
disconnect(m_waylandShowConnection);
@ -137,12 +165,27 @@ void VirtualKeyboard::init()
qApp->inputMethod()->update(Qt::ImHints);
}
);
m_waylandResetConnection = connect(t, &TextInputInterface::requestReset, qApp->inputMethod(), &QInputMethod::reset);
m_waylandEnabledConnection = connect(t, &TextInputInterface::enabledChanged, this,
[] {
qApp->inputMethod()->update(Qt::ImQueryAll);
m_waylandResetConnection = connect(t, &TextInputInterface::requestReset, this, [t] {
auto inputContext = waylandServer()->inputMethod()->context();
if (!inputContext) {
return;
}
);
inputContext->sendReset();
inputContext->sendSurroundingText(QString::fromUtf8(t->surroundingText()), t->surroundingTextCursorPosition(), t->surroundingTextSelectionAnchor());
inputContext->sendPreferredLanguage(QString::fromUtf8(t->preferredLanguage()));
});
m_waylandEnabledConnection = connect(t, &TextInputInterface::enabledChanged, this, [t, this] {
if (t->isEnabled()) {
waylandServer()->inputMethod()->sendDeactivate();
waylandServer()->inputMethod()->sendActivate();
adoptInputMethodContext();
} else {
waylandServer()->inputMethod()->sendDeactivate();
Q_EMIT hide();
}
qApp->inputMethod()->update(Qt::ImQueryAll);
});
auto newClient = waylandServer()->findClient(waylandServer()->seat()->focusedTextInputSurface());
// Reset the old client virtual keybaord geom if necessary
@ -154,7 +197,6 @@ void VirtualKeyboard::init()
m_trackedClient = newClient;
}
m_trackedClient = waylandServer()->findClient(waylandServer()->seat()->focusedTextInputSurface());
updateInputPanelState();
} else {
@ -211,6 +253,99 @@ void VirtualKeyboard::setEnabled(bool enabled)
QDBusConnection::sessionBus().asyncCall(msg);
}
static void keysymReceived(quint32 serial, quint32 time, quint32 sym, bool pressed, Qt::KeyboardModifiers modifiers)
{
Q_UNUSED(serial)
Q_UNUSED(time)
auto t = waylandServer()->seat()->focusedTextInput();
if (t && t->isEnabled()) {
if (pressed) {
t->keysymPressed(sym, modifiers);
} else {
t->keysymReleased(sym, modifiers);
}
}
}
static void commitString(qint32 serial, const QString &text)
{
Q_UNUSED(serial)
auto t = waylandServer()->seat()->focusedTextInput();
if (t && t->isEnabled()) {
t->commit(text.toUtf8());
t->preEdit({}, {});
}
}
static void setPreeditCursor(qint32 index)
{
auto t = waylandServer()->seat()->focusedTextInput();
if (t && t->isEnabled()) {
t->setPreEditCursor(index);
}
}
static void setPreeditString(uint32_t serial, const QString &text, const QString &commit)
{
Q_UNUSED(serial)
auto t = waylandServer()->seat()->focusedTextInput();
if (t && t->isEnabled()) {
t->preEdit(text.toUtf8(), commit.toUtf8());
}
}
static void deleteSurroundingText(int32_t index, uint32_t length)
{
auto t = waylandServer()->seat()->focusedTextInput();
if (t && t->isEnabled()) {
t->deleteSurroundingText(index, length);
}
}
static void setCursorPosition(qint32 index, qint32 anchor)
{
auto t = waylandServer()->seat()->focusedTextInput();
if (t && t->isEnabled()) {
t->setCursorPosition(index, anchor);
}
}
static void setLanguage(uint32_t serial, const QString &language)
{
Q_UNUSED(serial)
auto t = waylandServer()->seat()->focusedTextInput();
if (t && t->isEnabled()) {
t->setLanguage(language.toUtf8());
}
}
static void setTextDirection(uint32_t serial, Qt::LayoutDirection direction)
{
Q_UNUSED(serial)
auto t = waylandServer()->seat()->focusedTextInput();
if (t && t->isEnabled()) {
t->setTextDirection(direction);
}
}
void VirtualKeyboard::adoptInputMethodContext()
{
auto inputContext = waylandServer()->inputMethod()->context();
TextInputInterface *ti = waylandServer()->seat()->focusedTextInput();
inputContext->sendSurroundingText(QString::fromUtf8(ti->surroundingText()), ti->surroundingTextCursorPosition(), ti->surroundingTextSelectionAnchor());
inputContext->sendPreferredLanguage(QString::fromUtf8(ti->preferredLanguage()));
connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::keysym, waylandServer(), &keysymReceived);
connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::commitString, waylandServer(), &commitString);
connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::preeditCursor, waylandServer(), &setPreeditCursor);
connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::preeditString, waylandServer(), &setPreeditString);
connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::deleteSurroundingText, waylandServer(), &deleteSurroundingText);
connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::cursorPosition, waylandServer(), &setCursorPosition);
connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::language, waylandServer(), &setLanguage);
connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::textDirection, waylandServer(), &setTextDirection);
}
void VirtualKeyboard::updateSni()
{
if (!m_sni) {
@ -234,35 +369,42 @@ void VirtualKeyboard::updateInputPanelState()
auto t = waylandServer()->seat()->focusedTextInput();
if (!t || !m_inputWindow) {
if (!t) {
return;
}
const bool inputPanelHasBeenClosed = m_inputWindow->isVisible() && !qApp->inputMethod()->isVisible();
if (inputPanelHasBeenClosed && m_floodTimer->isActive()) {
return;
}
m_floodTimer->start();
if (m_inputWindow) {
const bool inputPanelHasBeenClosed = m_inputWindow->isVisible() && !qApp->inputMethod()->isVisible();
if (inputPanelHasBeenClosed && m_floodTimer->isActive()) {
return;
}
m_floodTimer->start();
m_inputWindow->setVisible(qApp->inputMethod()->isVisible());
m_inputWindow->setVisible(qApp->inputMethod()->isVisible());
if (qApp->inputMethod()->isVisible()) {
m_inputWindow->setMask(m_inputWindow->rootObject()->childrenRect().toRect());
}
if (m_inputWindow->isVisible() && m_trackedClient && m_inputWindow->rootObject()) {
const QRect inputPanelGeom = m_inputWindow->rootObject()->childrenRect().toRect().translated(m_inputWindow->geometry().topLeft());
m_trackedClient->setVirtualKeyboardGeometry(inputPanelGeom);
t->setInputPanelState(true, QRect(0, 0, 0, 0));
} else {
if (inputPanelHasBeenClosed && m_trackedClient) {
m_trackedClient->setVirtualKeyboardGeometry(QRect());
if (qApp->inputMethod()->isVisible()) {
m_inputWindow->setMask(m_inputWindow->rootObject()->childrenRect().toRect());
}
t->setInputPanelState(false, QRect(0, 0, 0, 0));
if (m_inputWindow->isVisible() && m_trackedClient && m_inputWindow->rootObject()) {
const QRect inputPanelGeom = m_inputWindow->rootObject()->childrenRect().toRect().translated(m_inputWindow->geometry().topLeft());
m_trackedClient->setVirtualKeyboardGeometry(inputPanelGeom);
t->setInputPanelState(true, QRect(0, 0, 0, 0));
} else {
if (inputPanelHasBeenClosed && m_trackedClient) {
m_trackedClient->setVirtualKeyboardGeometry(QRect());
}
t->setInputPanelState(false, QRect(0, 0, 0, 0));
}
}
if (m_inputClient) {
m_trackedClient->setVirtualKeyboardGeometry(m_inputClient->frameGeometry());
t->setInputPanelState(true, QRect(0, 0, 0, 0));
}
}

View file

@ -46,10 +46,12 @@ private:
void setEnabled(bool enable);
void updateSni();
void updateInputPanelState();
void adoptInputMethodContext();
bool m_enabled = false;
KStatusNotifierItem *m_sni = nullptr;
QScopedPointer<QQuickView> m_inputWindow;
QPointer<AbstractClient> m_inputClient;
QPointer<AbstractClient> m_trackedClient;
// If a surface loses focus immediately after being resized by the keyboard, don't react to it to avoid resize loops
QTimer *m_floodTimer;

View file

@ -12,6 +12,7 @@
#include "platform.h"
#include "composite.h"
#include "idle_inhibition.h"
#include "inputpanelv1client.h"
#include "screens.h"
#include "waylandxdgshellintegration.h"
#include "workspace.h"
@ -57,6 +58,7 @@
#include <KWaylandServer/keystate_interface.h>
#include <KWaylandServer/filtered_display.h>
#include <KWaylandServer/keyboard_shortcuts_inhibit_v1_interface.h>
#include <KWaylandServer/inputmethod_v1_interface.h>
// Qt
#include <QCryptographicHash>
@ -225,6 +227,13 @@ AbstractWaylandOutput *WaylandServer::findOutput(KWaylandServer::OutputInterface
return outputFound;
}
AbstractClient *WaylandServer::createInputPanelClient(KWaylandServer::InputPanelSurfaceV1Interface *surface)
{
auto *client = new InputPanelV1Client(surface);
registerShellClient(client);
return client;
}
class KWinDisplay : public KWaylandServer::FilteredDisplay
{
public:
@ -475,6 +484,8 @@ bool WaylandServer::init(const QByteArray &socketName, InitializationFlags flags
m_keyState = m_display->createKeyStateInterface(m_display);
m_keyState->create();
m_inputMethod = m_display->createInputMethodInterface(m_display);
return true;
}

View file

@ -38,6 +38,8 @@ class CompositorInterface;
class Display;
class DataDeviceInterface;
class IdleInterface;
class InputMethodV1Interface;
class InputPanelSurfaceV1Interface;
class SeatInterface;
class DataDeviceManagerInterface;
class ServerSideDecorationManagerInterface;
@ -133,6 +135,10 @@ public:
KWaylandServer::LinuxDmabufUnstableV1Interface *linuxDmabuf();
KWaylandServer::InputMethodV1Interface *inputMethod() const {
return m_inputMethod;
}
QList<AbstractClient *> clients() const {
return m_clients;
}
@ -235,6 +241,8 @@ public:
void removeLinuxDmabufBuffer(KWaylandServer::LinuxDmabufUnstableV1Buffer *buffer) {
m_linuxDmabufBuffers.remove(buffer);
}
AbstractClient *
createInputPanelClient(KWaylandServer::InputPanelSurfaceV1Interface *surface);
AbstractWaylandOutput *findOutput(KWaylandServer::OutputInterface *output) const;
@ -274,6 +282,7 @@ private:
KWaylandServer::KeyboardShortcutsInhibitManagerV1Interface *m_keyboardShortcutsInhibitManager = nullptr;
QSet<KWaylandServer::LinuxDmabufUnstableV1Buffer*> m_linuxDmabufBuffers;
QPointer<KWaylandServer::ClientConnection> m_xwaylandConnection;
KWaylandServer::InputMethodV1Interface *m_inputMethod = nullptr;
KWaylandServer::ClientConnection *m_inputMethodServerConnection = nullptr;
KWaylandServer::ClientConnection *m_screenLockerClientConnection = nullptr;
struct {