From 050ce24247f39776163f1e4c2b0b325155875f65 Mon Sep 17 00:00:00 2001 From: Aleix Pol Date: Tue, 27 Apr 2021 17:49:55 +0200 Subject: [PATCH] inputmethod: Move the input method process into the InputMethod class Use the control this gives us for stopping the input method process when we disable. --- autotests/integration/kwin_wayland_test.cpp | 29 +---- src/inputmethod.cpp | 123 ++++++++++++++++++-- src/inputmethod.h | 11 ++ src/main_wayland.cpp | 82 +------------ src/main_wayland.h | 6 - 5 files changed, 128 insertions(+), 123 deletions(-) diff --git a/autotests/integration/kwin_wayland_test.cpp b/autotests/integration/kwin_wayland_test.cpp index 61422edd39..e2b041f9cc 100644 --- a/autotests/integration/kwin_wayland_test.cpp +++ b/autotests/integration/kwin_wayland_test.cpp @@ -95,33 +95,8 @@ void WaylandTestApplication::performStartup() if (!m_inputMethodServerToStart.isEmpty()) { InputMethod::create(); if (m_inputMethodServerToStart != QStringLiteral("internal")) { - int socket = dup(waylandServer()->createInputMethodConnection()); - if (socket >= 0) { - QProcessEnvironment environment = 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"); - QProcess *p = new Process(this); - p->setProcessChannelMode(QProcess::ForwardedErrorChannel); - connect(p, qOverload(&QProcess::finished), this, - [p] { - if (waylandServer()) { - waylandServer()->destroyInputMethodConnection(); - } - p->deleteLater(); - } - ); - p->setProcessEnvironment(environment); - p->setProgram(m_inputMethodServerToStart); - // p->setArguments(arguments); - p->start(); - connect(waylandServer(), &WaylandServer::terminatingInternalClientConnection, p, [p] { - p->kill(); - p->waitForFinished(); - }); - } + InputMethod::self()->setInputMethodCommand(m_inputMethodServerToStart); + InputMethod::self()->setEnabled(true); } } diff --git a/src/inputmethod.cpp b/src/inputmethod.cpp index 8efd4795b8..314ee0e766 100644 --- a/src/inputmethod.cpp +++ b/src/inputmethod.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include @@ -34,6 +35,7 @@ #include #include +#include using namespace KWaylandServer; @@ -48,13 +50,23 @@ InputMethod::InputMethod(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 - connect(kwinApp(), &Application::workspaceCreated, this, &InputMethod::init); + if (workspace()) { + init(); + } else { + connect(kwinApp(), &Application::workspaceCreated, this, &InputMethod::init); + } } InputMethod::~InputMethod() = default; void InputMethod::init() { + // 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; + }); connect(ScreenLockerWatcher::self(), &ScreenLockerWatcher::aboutToLock, this, &InputMethod::hide); if (waylandServer()) { @@ -230,6 +242,10 @@ void InputMethod::contentTypeChanged() void InputMethod::textInputInterfaceV2StateUpdated(quint32 serial, KWaylandServer::TextInputV2Interface::UpdateReason reason) { + if (!m_enabled) { + return; + } + auto t2 = waylandServer()->seat()->textInputV2(); auto inputContext = waylandServer()->inputMethod()->context(); if (!inputContext) { @@ -259,6 +275,10 @@ void InputMethod::textInputInterfaceV2StateUpdated(quint32 serial, KWaylandServe void InputMethod::textInputInterfaceV2EnabledChanged() { + if (!m_enabled) { + return; + } + auto t = waylandServer()->seat()->textInputV2(); if (t->isEnabled()) { waylandServer()->inputMethod()->sendActivate(); @@ -270,6 +290,10 @@ void InputMethod::textInputInterfaceV2EnabledChanged() void InputMethod::textInputInterfaceV3EnabledChanged() { + if (!m_enabled) { + return; + } + auto t3 = waylandServer()->seat()->textInputV3(); if (t3->isEnabled()) { waylandServer()->inputMethod()->sendActivate(); @@ -313,17 +337,11 @@ void InputMethod::setEnabled(bool enabled) ); msg.setArguments({enabled}); QDBusConnection::sessionBus().asyncCall(msg); - - auto textInputV2 = waylandServer()->seat()->textInputV2(); - auto textInputV3 = waylandServer()->seat()->textInputV3(); - if (m_enabled) { - connect(textInputV2, &TextInputV2Interface::enabledChanged, this, &InputMethod::textInputInterfaceV2EnabledChanged, Qt::UniqueConnection); - connect(textInputV3, &TextInputV3Interface::enabledChanged, this, &InputMethod::textInputInterfaceV3EnabledChanged, Qt::UniqueConnection); - } else { + if (!m_enabled) { hide(); - - disconnect(textInputV2, &TextInputV2Interface::enabledChanged, this, &InputMethod::textInputInterfaceV2EnabledChanged); - disconnect(textInputV3, &TextInputV3Interface::enabledChanged, this, &InputMethod::textInputInterfaceV3EnabledChanged); + stopInputMethod(); + } else { + startInputMethod(); } } @@ -518,4 +536,87 @@ void InputMethod::updateInputPanelState() t->setInputPanelState(m_inputClient && m_inputClient->isShown(false), QRect(0, 0, 0, 0)); } +void InputMethod::setInputMethodCommand(const QString &command) +{ + if (m_inputMethodCommand == command) { + return; + } + + m_inputMethodCommand = command; + + if (m_enabled) { + startInputMethod(); + } +} + +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::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(); + } + } + }); +} } diff --git a/src/inputmethod.h b/src/inputmethod.h index b5cbfaecb8..13cebf355b 100644 --- a/src/inputmethod.h +++ b/src/inputmethod.h @@ -15,9 +15,11 @@ #include #include +#include #include class KStatusNotifierItem; +class QProcess; namespace KWin { @@ -46,6 +48,8 @@ public: void hide(); void show(); + void setInputMethodCommand(const QString &path); + Q_SIGNALS: void activeChanged(bool active); void enabledChanged(bool enabled); @@ -76,6 +80,8 @@ private: void setCursorPosition(qint32 index, qint32 anchor); void setLanguage(uint32_t serial, const QString &language); void setTextDirection(uint32_t serial, Qt::LayoutDirection direction); + void startInputMethod(); + void stopInputMethod(); struct { QString text = QString(); @@ -89,6 +95,11 @@ private: QPointer m_inputClient; QPointer m_trackedClient; + QProcess *m_inputMethodProcess = nullptr; + QTimer m_inputMethodCrashTimer; + uint m_inputMethodCrashes = 0; + QString m_inputMethodCommand; + KWIN_SINGLETON(InputMethod) }; diff --git a/src/main_wayland.cpp b/src/main_wayland.cpp index 810606a91e..6916e519d7 100644 --- a/src/main_wayland.cpp +++ b/src/main_wayland.cpp @@ -114,12 +114,6 @@ void gainRealTime(RealTimeFlags flags = RealTimeFlags::DontReset) ApplicationWayland::ApplicationWayland(int &argc, char **argv) : ApplicationWaylandAbstract(OperationModeWaylandOnly, argc, argv) { - // 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; - }); } ApplicationWayland::~ApplicationWayland() @@ -216,89 +210,19 @@ void ApplicationWayland::continueStartupWithScene() m_xwayland->start(); } - -void ApplicationWayland::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 ApplicationWayland::startInputMethod(const QString &executable) -{ - stopInputMethod(); - if (executable.isEmpty() || isTerminating()) { - return; - } - - connect(waylandServer(), &WaylandServer::terminatingInternalClientConnection, this, &ApplicationWayland::stopInputMethod, Qt::UniqueConnection); - - QStringList arguments = KShell::splitArgs(executable); - if (arguments.isEmpty()) { - qWarning("Failed to launch the input method server: %s is an invalid command", qPrintable(m_inputMethodServerToStart)); - return; - } - - const QString program = arguments.takeFirst(); - int socket = dup(waylandServer()->createInputMethodConnection()); - if (socket < 0) { - qWarning("Failed to create the input method connection"); - return; - } - - QProcessEnvironment environment = 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(); - connect(m_inputMethodProcess, QOverload::of(&QProcess::finished), this, [this, executable] (int exitCode, QProcess::ExitStatus exitStatus) { - if (exitStatus == QProcess::CrashExit) { - m_inputMethodCrashes++; - m_inputMethodCrashTimer.start(); - qWarning() << "Input Method crashed" << executable << exitCode << exitStatus; - if (m_inputMethodCrashes < 5) { - startInputMethod(executable); - } else { - qWarning() << "Input Method keeps crashing, please fix" << executable; - stopInputMethod(); - } - } - }); -} - void ApplicationWayland::refreshSettings(const KConfigGroup &group, const QByteArrayList &names) { if (group.name() != "Wayland" || !names.contains("InputMethod")) { return; } - startInputMethod(group.readEntry("InputMethod", QString())); + InputMethod::self()->setInputMethodCommand(group.readEntry("InputMethod", QString())); } void ApplicationWayland::startSession() { if (!m_inputMethodServerToStart.isEmpty()) { - startInputMethod(m_inputMethodServerToStart); + InputMethod::self()->setInputMethodCommand(m_inputMethodServerToStart); } else { KSharedConfig::Ptr kwinSettings = kwinApp()->config(); m_settingsWatcher = KConfigWatcher::create(kwinSettings); @@ -306,7 +230,7 @@ void ApplicationWayland::startSession() KConfigGroup group = kwinSettings->group("Wayland"); KDesktopFile file(group.readEntry("InputMethod", QString())); - startInputMethod(file.desktopGroup().readEntry("Exec", QString())); + InputMethod::self()->setInputMethodCommand(file.desktopGroup().readEntry("Exec", QString())); } // start session diff --git a/src/main_wayland.h b/src/main_wayland.h index ea5fceb391..06ba7a42bf 100644 --- a/src/main_wayland.h +++ b/src/main_wayland.h @@ -55,9 +55,7 @@ private: void continueStartupWithScene(); void finalizeStartup(); void startSession() override; - void startInputMethod(const QString &executable); void refreshSettings(const KConfigGroup &group, const QByteArrayList &names); - void stopInputMethod(); bool m_startXWayland = false; QStringList m_applicationsToStart; @@ -65,10 +63,6 @@ private: QProcessEnvironment m_environment; QString m_sessionArgument; - QProcess *m_inputMethodProcess = nullptr; - QTimer m_inputMethodCrashTimer; - uint m_inputMethodCrashes = 0; - Xwl::Xwayland *m_xwayland = nullptr; KConfigWatcher::Ptr m_settingsWatcher; };