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.
This commit is contained in:
Aleix Pol 2021-04-27 17:49:55 +02:00 committed by Aleix Pol Gonzalez
parent ac3c09ba75
commit 050ce24247
5 changed files with 128 additions and 123 deletions

View file

@ -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<int, QProcess::ExitStatus>(&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);
}
}

View file

@ -25,6 +25,7 @@
#include <KWaylandServer/surface_interface.h>
#include <KWaylandServer/inputmethod_v1_interface.h>
#include <KShell>
#include <KStatusNotifierItem>
#include <KLocalizedString>
@ -34,6 +35,7 @@
#include <linux/input-event-codes.h>
#include <xkbcommon/xkbcommon-keysyms.h>
#include <unistd.h>
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<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();
}
}
});
}
}

View file

@ -15,9 +15,11 @@
#include <kwin_export.h>
#include <QPointer>
#include <QTimer>
#include <KWaylandServer/textinput_v2_interface.h>
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<AbstractClient> m_inputClient;
QPointer<AbstractClient> m_trackedClient;
QProcess *m_inputMethodProcess = nullptr;
QTimer m_inputMethodCrashTimer;
uint m_inputMethodCrashes = 0;
QString m_inputMethodCommand;
KWIN_SINGLETON(InputMethod)
};

View file

@ -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<int, QProcess::ExitStatus>::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

View file

@ -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;
};