diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -414,6 +414,7 @@ shell_client.cpp wayland_server.cpp wayland_cursor_theme.cpp + virtualkeyboard.cpp ) if(KWIN_BUILD_TABBOX) diff --git a/main_wayland.cpp b/main_wayland.cpp --- a/main_wayland.cpp +++ b/main_wayland.cpp @@ -19,6 +19,7 @@ *********************************************************************/ #include "main_wayland.h" #include "composite.h" +#include "virtualkeyboard.h" #include "workspace.h" #include // kwin @@ -124,6 +125,7 @@ // try creating the Wayland Backend createInput(); + VirtualKeyboard::create(this); createBackend(); } @@ -444,7 +446,7 @@ setenv("QT_QPA_PLATFORM", "wayland-org.kde.kwin.qpa", true); qunsetenv("QT_DEVICE_PIXEL_RATIO"); - qunsetenv("QT_IM_MODULE"); + qputenv("QT_IM_MODULE", "qtvirtualkeyboard"); qputenv("QSG_RENDER_LOOP", "basic"); #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling); diff --git a/plugins/qpa/integration.h b/plugins/qpa/integration.h --- a/plugins/qpa/integration.h +++ b/plugins/qpa/integration.h @@ -60,6 +60,7 @@ QPlatformOpenGLContext *createPlatformOpenGLContext(QOpenGLContext *context) const override; void initialize() override; + QPlatformInputContext *inputContext() const override; KWayland::Client::Compositor *compositor() const; EGLDisplay eglDisplay() const; @@ -77,6 +78,7 @@ KWayland::Client::Shell *m_shell = nullptr; EGLDisplay m_eglDisplay = EGL_NO_DISPLAY; Screen *m_dummyScreen = nullptr; + QScopedPointer m_inputContext; }; } diff --git a/plugins/qpa/integration.cpp b/plugins/qpa/integration.cpp --- a/plugins/qpa/integration.cpp +++ b/plugins/qpa/integration.cpp @@ -26,6 +26,7 @@ #include "screen.h" #include "sharingplatformcontext.h" #include "window.h" +#include "../../virtualkeyboard.h" #include "../../main.h" #include "../../wayland_server.h" @@ -41,6 +42,9 @@ #include #include +#include +#include +#include #include #include #include @@ -57,6 +61,7 @@ , QPlatformIntegration() , m_fontDb(new QGenericUnixFontDatabase()) , m_nativeInterface(new NativeInterface(this)) + , m_inputContext() { } @@ -90,6 +95,31 @@ QPlatformIntegration::initialize(); m_dummyScreen = new Screen(nullptr); screenAdded(m_dummyScreen); + m_inputContext.reset(QPlatformInputContextFactory::create(QStringLiteral("qtvirtualkeyboard"))); + qunsetenv("QT_IM_MODULE"); + connect(qApp, &QGuiApplication::focusObjectChanged, this, + [this] { + if (VirtualKeyboard::self() && qApp->focusObject() != VirtualKeyboard::self()) { + m_inputContext->setFocusObject(VirtualKeyboard::self()); + } + } + ); + connect(kwinApp(), &Application::workspaceCreated, this, + [this] { + if (VirtualKeyboard::self()) { + m_inputContext->setFocusObject(VirtualKeyboard::self()); + } + } + ); + connect(qApp->inputMethod(), &QInputMethod::visibleChanged, this, + [this] { + if (qApp->inputMethod()->isVisible()) { + if (QWindow *w = VirtualKeyboard::self()->inputPanel()) { + QWindowSystemInterface::handleWindowActivated(w, Qt::ActiveWindowFocusReason); + } + } + } + ); } QAbstractEventDispatcher *Integration::createEventDispatcher() const @@ -261,5 +291,10 @@ } } +QPlatformInputContext *Integration::inputContext() const +{ + return m_inputContext.data(); +} + } } diff --git a/qml/CMakeLists.txt b/qml/CMakeLists.txt --- a/qml/CMakeLists.txt +++ b/qml/CMakeLists.txt @@ -1 +1,2 @@ install( DIRECTORY outline/plasma DESTINATION ${DATA_INSTALL_DIR}/${KWIN_NAME}/outline ) +install( DIRECTORY virtualkeyboard DESTINATION ${DATA_INSTALL_DIR}/${KWIN_NAME} ) diff --git a/qml/virtualkeyboard/main-enterprise.qml b/qml/virtualkeyboard/main-enterprise.qml new file mode 100644 --- /dev/null +++ b/qml/virtualkeyboard/main-enterprise.qml @@ -0,0 +1,32 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2016 Martin Gräßlin + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +import QtQuick 2.0 +import QtQuick.Enterprise.VirtualKeyboard 2.0 + +Item { + id: window + InputPanel { + id: inputPanel + objectName: "inputPanel" + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + } +} diff --git a/qml/virtualkeyboard/main.qml b/qml/virtualkeyboard/main.qml new file mode 100644 --- /dev/null +++ b/qml/virtualkeyboard/main.qml @@ -0,0 +1,32 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2016 Martin Gräßlin + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +import QtQuick 2.0 +import QtQuick.VirtualKeyboard 2.1 + +Item { + id: window + InputPanel { + id: inputPanel + objectName: "inputPanel" + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + } +} diff --git a/shell_client.cpp b/shell_client.cpp --- a/shell_client.cpp +++ b/shell_client.cpp @@ -764,6 +764,9 @@ bool ShellClient::isInputMethod() const { + if (m_internal && m_internalWindow) { + return m_internalWindow->property("__kwin_input_method").toBool(); + } return surface()->client() == waylandServer()->inputMethodConnection(); } diff --git a/tests/inputmethodstest.qml b/tests/inputmethodstest.qml new file mode 100644 --- /dev/null +++ b/tests/inputmethodstest.qml @@ -0,0 +1,84 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2016 Martin Gräßlin + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.2 + +GridLayout { + columns: 2 + Label { + text: "Normal:" + } + TextField { + } + Label { + text: "Digits:" + } + TextField { + inputMethodHints: Qt.ImhDigitsOnly + } + Label { + text: "Numbers:" + } + TextField { + inputMethodHints: Qt.ImhFormattedNumbersOnly + } + Label { + text: "Uppercase:" + } + TextField { + inputMethodHints: Qt.ImhUppercaseOnly + } + Label { + text: "Lowercase:" + } + TextField { + inputMethodHints: Qt.ImhLowercaseOnly + } + Label { + text: "Phone:" + } + TextField { + inputMethodHints: Qt.ImhDialableCharactersOnly + } + Label { + text: "Email:" + } + TextField { + inputMethodHints: Qt.ImhEmailCharactersOnly + } + Label { + text: "Url:" + } + TextField { + inputMethodHints: Qt.ImhUrlCharactersOnly + } + Label { + text: "Date:" + } + TextField { + inputMethodHints: Qt.ImhDate + } + Label { + text: "Time:" + } + TextField { + inputMethodHints: Qt.ImhTime + } +} diff --git a/virtualkeyboard.h b/virtualkeyboard.h new file mode 100644 --- /dev/null +++ b/virtualkeyboard.h @@ -0,0 +1,68 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2016 Martin Gräßlin + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#ifndef KWIN_INPUT_METHODS_H +#define KWIN_INPUT_METHODS_H + +#include + +#include +#include + +class QQuickView; +class QWindow; +class KStatusNotifierItem; + +namespace KWin +{ + +class KWIN_EXPORT VirtualKeyboard : public QObject +{ + Q_OBJECT +public: + virtual ~VirtualKeyboard(); + + void init(); + + bool event(QEvent *e) override; + bool eventFilter(QObject *o, QEvent *event) override; + + QWindow *inputPanel() const; + +private: + void show(); + void hide(); + void setEnabled(bool enable); + void updateSni(); + + bool m_enabled = false; + KStatusNotifierItem *m_sni = nullptr; + QScopedPointer m_inputWindow; + QMetaObject::Connection m_waylandShowConnection; + QMetaObject::Connection m_waylandHideConnection; + QMetaObject::Connection m_waylandHintsConnection; + QMetaObject::Connection m_waylandSurroundingTextConnection; + QMetaObject::Connection m_waylandResetConnection; + QMetaObject::Connection m_waylandEnabledConnection; + KWIN_SINGLETON(VirtualKeyboard) +}; + +} + +#endif diff --git a/virtualkeyboard.cpp b/virtualkeyboard.cpp new file mode 100644 --- /dev/null +++ b/virtualkeyboard.cpp @@ -0,0 +1,436 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2016 Martin Gräßlin + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#include "virtualkeyboard.h" +#include "utils.h" +#include "screens.h" +#include "wayland_server.h" +#include "workspace.h" + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace KWayland::Server; + +namespace KWin +{ + +KWIN_SINGLETON_FACTORY(VirtualKeyboard) + +VirtualKeyboard::VirtualKeyboard(QObject *parent) + : QObject(parent) +{ + // this is actually to 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 + connect(kwinApp(), &Application::workspaceCreated, this, &VirtualKeyboard::init); +} + +VirtualKeyboard::~VirtualKeyboard() = default; + +void VirtualKeyboard::init() +{ + // TODO: need a shared Qml engine + m_inputWindow.reset(new QQuickView(nullptr)); + m_inputWindow->setGeometry(screens()->geometry(screens()->current())); + m_inputWindow->setResizeMode(QQuickView::SizeRootObjectToView); + m_inputWindow->setSource(QUrl::fromLocalFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral(KWIN_NAME "/virtualkeyboard/main.qml")))); + if (m_inputWindow->status() != QQuickView::Status::Ready) { + // try enterprise + m_inputWindow->setSource(QUrl::fromLocalFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral(KWIN_NAME "/virtualkeyboard/main-enterprise.qml")))); + if (m_inputWindow->status() != QQuickView::Status::Ready) { + m_inputWindow.reset(); + return; + } + } + m_inputWindow->setProperty("__kwin_input_method", true); + + if (waylandServer()) { + m_enabled = !waylandServer()->seat()->hasKeyboard(); + connect(waylandServer()->seat(), &KWayland::Server::SeatInterface::hasKeyboardChanged, this, + [this] { + setEnabled(!waylandServer()->seat()->hasKeyboard()); + } + ); + } + + m_sni = new KStatusNotifierItem(QStringLiteral("kwin-virtual-keyboard"), this); + m_sni->setCategory(KStatusNotifierItem::Hardware); + m_sni->setStatus(KStatusNotifierItem::Passive); + m_sni->setTitle(i18n("Virtual Keyboard")); + updateSni(); + connect(m_sni, &KStatusNotifierItem::activateRequested, this, + [this] { + setEnabled(!m_enabled); + } + ); + + if (waylandServer()) { + // we can announce support for the text input interface + auto t = waylandServer()->display()->createTextInputManager(TextInputInterfaceVersion::UnstableV0, waylandServer()->display()); + t->create(); + auto t2 = waylandServer()->display()->createTextInputManager(TextInputInterfaceVersion::UnstableV2, waylandServer()->display()); + t2->create(); + connect(waylandServer()->seat(), &SeatInterface::focusedTextInputChanged, this, + [this] { + disconnect(m_waylandShowConnection); + disconnect(m_waylandHideConnection); + disconnect(m_waylandHintsConnection); + disconnect(m_waylandSurroundingTextConnection); + disconnect(m_waylandResetConnection); + disconnect(m_waylandEnabledConnection); + qApp->inputMethod()->reset(); + if (auto t = waylandServer()->seat()->focusedTextInput()) { + m_waylandShowConnection = connect(t, &TextInputInterface::requestShowInputPanel, this, &VirtualKeyboard::show); + m_waylandHideConnection = connect(t, &TextInputInterface::requestHideInputPanel, this, &VirtualKeyboard::hide); + m_waylandSurroundingTextConnection = connect(t, &TextInputInterface::surroundingTextChanged, this, + [] { + qApp->inputMethod()->update(Qt::ImSurroundingText | Qt::ImCursorPosition | Qt::ImAnchorPosition); + } + ); + m_waylandHintsConnection = connect(t, &TextInputInterface::contentTypeChanged, this, + [] { + 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); + } + ); + // TODO: calculate overlap + t->setInputPanelState(m_inputWindow->isVisible(), QRect(0, 0, 0, 0)); + } else { + m_waylandShowConnection = QMetaObject::Connection(); + m_waylandHideConnection = QMetaObject::Connection(); + m_waylandHintsConnection = QMetaObject::Connection(); + m_waylandSurroundingTextConnection = QMetaObject::Connection(); + m_waylandResetConnection = QMetaObject::Connection(); + m_waylandEnabledConnection = QMetaObject::Connection(); + } + qApp->inputMethod()->update(Qt::ImQueryAll); + } + ); + } + m_inputWindow->installEventFilter(this); + connect(Workspace::self(), &Workspace::destroyed, this, + [this] { + m_inputWindow.reset(); + } + ); + m_inputWindow->setColor(Qt::transparent); + m_inputWindow->setMask(m_inputWindow->rootObject()->childrenRect().toRect()); + connect(m_inputWindow->rootObject(), &QQuickItem::childrenRectChanged, m_inputWindow.data(), + [this] { + if (!m_inputWindow) { + return; + } + m_inputWindow->setMask(m_inputWindow->rootObject()->childrenRect().toRect()); + } + ); + connect(qApp->inputMethod(), &QInputMethod::visibleChanged, m_inputWindow.data(), + [this] { + m_inputWindow->setVisible(qApp->inputMethod()->isVisible()); + if (qApp->inputMethod()->isVisible()) { + m_inputWindow->setMask(m_inputWindow->rootObject()->childrenRect().toRect()); + } + if (waylandServer()) { + if (auto t = waylandServer()->seat()->focusedTextInput()) { + // TODO: calculate overlap + t->setInputPanelState(m_inputWindow->isVisible(), QRect(0, 0, 0, 0)); + } + } + } + ); +} + +void VirtualKeyboard::setEnabled(bool enabled) +{ + if (m_enabled == enabled) { + return; + } + m_enabled = enabled; + qApp->inputMethod()->update(Qt::ImQueryAll); + + updateSni(); + + // 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); +} + +void VirtualKeyboard::updateSni() +{ + if (!m_sni) { + return; + } + if (m_enabled) { + m_sni->setIconByName(QStringLiteral("input-keyboard-virtual-on")); + m_sni->setToolTipTitle(i18n("Virtual Keyboard is enabled.")); + } else { + m_sni->setIconByName(QStringLiteral("input-keyboard-virtual-off")); + m_sni->setToolTipTitle(i18n("Virtual Keyboard is disabled.")); + } +} + +void VirtualKeyboard::show() +{ + if (m_inputWindow.isNull() || !m_enabled) { + return; + } + m_inputWindow->setGeometry(screens()->geometry(screens()->current())); + qApp->inputMethod()->show(); +} + +void VirtualKeyboard::hide() +{ + if (m_inputWindow.isNull()) { + return; + } + qApp->inputMethod()->hide(); +} + +bool VirtualKeyboard::event(QEvent *e) +{ + if (e->type() == QEvent::InputMethod) { + QInputMethodEvent *event = static_cast(e); + if (m_enabled && waylandServer()) { + bool isPreedit = false; + for (auto attribute : event->attributes()) { + switch (attribute.type) { + case QInputMethodEvent::TextFormat: + case QInputMethodEvent::Cursor: + case QInputMethodEvent::Language: + case QInputMethodEvent::Ruby: + isPreedit = true; + break; + default: + break; + } + } + TextInputInterface *ti = waylandServer()->seat()->focusedTextInput(); + if (ti && ti->isEnabled()) { + if (!isPreedit && event->preeditString().isEmpty() && !event->commitString().isEmpty()) { + ti->commit(event->commitString().toUtf8()); + } else { + ti->preEdit(event->preeditString().toUtf8(), event->commitString().toUtf8()); + } + } + } + } + if (e->type() == QEvent::InputMethodQuery) { + auto event = static_cast(e); + TextInputInterface *ti = nullptr; + if (waylandServer() && m_enabled) { + ti = waylandServer()->seat()->focusedTextInput(); + } + if (event->queries().testFlag(Qt::ImEnabled)) { + event->setValue(Qt::ImEnabled, QVariant(ti != nullptr && ti->isEnabled())); + } + if (event->queries().testFlag(Qt::ImCursorRectangle)) { + // not used by virtual keyboard + } + if (event->queries().testFlag(Qt::ImFont)) { + // not used by virtual keyboard + } + if (event->queries().testFlag(Qt::ImCursorPosition)) { + // the virtual keyboard doesn't send us the cursor position in the preedit + // this would break text input, thus we ignore it + // see https://bugreports.qt.io/browse/QTBUG-53517 +#if 0 + event->setValue(Qt::ImCursorPosition, QString::fromUtf8(ti->surroundingText().left(ti->surroundingTextCursorPosition())).size()); +#else + event->setValue(Qt::ImCursorPosition, 0); +#endif + } + if (event->queries().testFlag(Qt::ImSurroundingText)) { + // the virtual keyboard doesn't send us the cursor position in the preedit + // this would break text input, thus we ignore it + // see https://bugreports.qt.io/browse/QTBUG-53517 +#if 0 + event->setValue(Qt::ImSurroundingText, QString::fromUtf8(ti->surroundingText())); +#else + event->setValue(Qt::ImSurroundingText, QString()); +#endif + } + if (event->queries().testFlag(Qt::ImCurrentSelection)) { + // TODO: should be text between cursor and anchor, but might be dangerous + } + if (event->queries().testFlag(Qt::ImMaximumTextLength)) { + // not used by virtual keyboard + } + if (event->queries().testFlag(Qt::ImAnchorPosition)) { + // not used by virtual keyboard + } + if (event->queries().testFlag(Qt::ImHints)) { + if (ti && ti->isEnabled()) { + Qt::InputMethodHints hints; + const auto contentHints = ti->contentHints(); + if (!contentHints.testFlag(TextInputInterface::ContentHint::AutoCompletion)) { + hints |= Qt::ImhNoPredictiveText; + } + if (contentHints.testFlag(TextInputInterface::ContentHint::AutoCorrection)) { + // no mapping in Qt + } + if (!contentHints.testFlag(TextInputInterface::ContentHint::AutoCapitalization)) { + hints |= Qt::ImhNoAutoUppercase; + } + if (contentHints.testFlag(TextInputInterface::ContentHint::Lowercase)) { + hints |= Qt::ImhPreferLowercase; + } + if (contentHints.testFlag(TextInputInterface::ContentHint::Uppercase)) { + hints |= Qt::ImhPreferUppercase; + } + if (contentHints.testFlag(TextInputInterface::ContentHint::Titlecase)) { + // no mapping in Qt + } + if (contentHints.testFlag(TextInputInterface::ContentHint::HiddenText)) { + hints |= Qt::ImhHiddenText; + } + if (contentHints.testFlag(TextInputInterface::ContentHint::SensitiveData)) { + hints |= Qt::ImhSensitiveData; + } + if (contentHints.testFlag(TextInputInterface::ContentHint::Latin)) { + hints |= Qt::ImhPreferLatin; + } + if (contentHints.testFlag(TextInputInterface::ContentHint::MultiLine)) { + hints |= Qt::ImhMultiLine; + } + switch (ti->contentPurpose()) { + case TextInputInterface::ContentPurpose::Digits: + hints |= Qt::ImhDigitsOnly; + break; + case TextInputInterface::ContentPurpose::Number: + hints |= Qt::ImhFormattedNumbersOnly; + break; + case TextInputInterface::ContentPurpose::Phone: + hints |= Qt::ImhDialableCharactersOnly; + break; + case TextInputInterface::ContentPurpose::Url: + hints |= Qt::ImhUrlCharactersOnly; + case TextInputInterface::ContentPurpose::Email: + hints |= Qt::ImhEmailCharactersOnly; + break; + case TextInputInterface::ContentPurpose::Date: + hints |= Qt::ImhDate; + break; + case TextInputInterface::ContentPurpose::Time: + hints |= Qt::ImhTime; + break; + case TextInputInterface::ContentPurpose::Datetime: + hints |= Qt::ImhDate; + hints |= Qt::ImhTime; + break; + case TextInputInterface::ContentPurpose::Name: + // no mapping in Qt + case TextInputInterface::ContentPurpose::Password: + // no mapping in Qt + case TextInputInterface::ContentPurpose::Terminal: + // no mapping in Qt + case TextInputInterface::ContentPurpose::Normal: + // that's the default + case TextInputInterface::ContentPurpose::Alpha: + // no mapping in Qt + break; + } + event->setValue(Qt::ImHints, QVariant(int(hints))); + } else { + event->setValue(Qt::ImHints, Qt::ImhNone); + } + } + if (event->queries().testFlag(Qt::ImPreferredLanguage)) { + // not used by virtual keyboard + } + if (event->queries().testFlag(Qt::ImPlatformData)) { + // not used by virtual keyboard + } + if (event->queries().testFlag(Qt::ImAbsolutePosition)) { + // not used by virtual keyboard + } + if (event->queries().testFlag(Qt::ImTextBeforeCursor)) { + // not used by virtual keyboard + } + if (event->queries().testFlag(Qt::ImTextAfterCursor)) { + // not used by virtual keyboard + } + event->accept(); + return true; + } + return QObject::event(e); +} + +bool VirtualKeyboard::eventFilter(QObject *o, QEvent *e) +{ + if (o != m_inputWindow.data() || !m_inputWindow->isVisible()) { + return false; + } + if (e->type() == QEvent::KeyPress || e->type() == QEvent::KeyRelease) { + QKeyEvent *event = static_cast(e); + if (event->nativeScanCode() == 0) { + // this is a key composed by the virtual keyboard - we need to send it to the client + // TODO: proper xkb support in KWindowSystem needed + int sym = 0; + KKeyServer::keyQtToSymX(event->key(), &sym); + if (sym != 0) { + if (waylandServer()) { + auto t = waylandServer()->seat()->focusedTextInput(); + if (t && t->isEnabled()) { + if (e->type() == QEvent::KeyPress) { + t->keysymPressed(sym); + } else if (e->type() == QEvent::KeyRelease) { + t->keysymReleased(sym); + } + } + } + } + return true; + } + } + return false; +} + +QWindow *VirtualKeyboard::inputPanel() const +{ + return m_inputWindow.data(); +} + +}