1 /*
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5 SPDX-FileCopyrightText: 2016 Martin Gräßlin <mgraesslin@kde.org>
6
7 SPDX-License-Identifier: GPL-2.0-or-later
8 */
9 #include "inputmethod.h"
10 #include "abstract_client.h"
11 #include "virtualkeyboard_dbus.h"
12 #include "input.h"
13 #include "inputpanelv1client.h"
14 #include "keyboard_input.h"
15 #include "utils.h"
16 #include "screens.h"
17 #include "wayland_server.h"
18 #include "workspace.h"
19 #include "screenlockerwatcher.h"
20 #include "deleted.h"
21
22 #include <KWaylandServer/display.h>
23 #include <KWaylandServer/keyboard_interface.h>
24 #include <KWaylandServer/seat_interface.h>
25 #include <KWaylandServer/textinput_v3_interface.h>
26 #include <KWaylandServer/surface_interface.h>
27 #include <KWaylandServer/inputmethod_v1_interface.h>
28
29 #include <KShell>
30 #include <KStatusNotifierItem>
31 #include <KLocalizedString>
32
33 #include <QDBusConnection>
34 #include <QDBusPendingCall>
35 #include <QDBusMessage>
36 #include <QMenu>
37 #include <QKeyEvent>
38
39 #include <linux/input-event-codes.h>
40 #include <xkbcommon/xkbcommon-keysyms.h>
41 #include <unistd.h>
42
43 using namespace KWaylandServer;
44
45 namespace KWin
46 {
47
KWIN_SINGLETON_FACTORY(InputMethod)48 KWIN_SINGLETON_FACTORY(InputMethod)
49
50 InputMethod::InputMethod(QObject *parent)
51 : QObject(parent)
52 {
53 m_enabled = kwinApp()->config()->group("Wayland").readEntry("VirtualKeyboardEnabled", true);
54 // this is actually too late. Other processes are started before init,
55 // so might miss the availability of text input
56 // but without Workspace we don't have the window listed at all
57 if (workspace()) {
58 init();
59 } else {
60 connect(kwinApp(), &Application::workspaceCreated, this, &InputMethod::init);
61 }
62 }
63
64 InputMethod::~InputMethod() = default;
65
init()66 void InputMethod::init()
67 {
68 // Stop restarting the input method if it starts crashing very frequently
69 m_inputMethodCrashTimer.setInterval(20000);
70 m_inputMethodCrashTimer.setSingleShot(true);
71 connect(&m_inputMethodCrashTimer, &QTimer::timeout, this, [this] {
72 m_inputMethodCrashes = 0;
73 });
74 connect(ScreenLockerWatcher::self(), &ScreenLockerWatcher::aboutToLock, this, &InputMethod::hide);
75
76 new VirtualKeyboardDBus(this);
77 qCDebug(KWIN_VIRTUALKEYBOARD) << "Registering the DBus interface";
78
79 if (waylandServer()) {
80 new TextInputManagerV2Interface(waylandServer()->display());
81 new TextInputManagerV3Interface(waylandServer()->display());
82
83 connect(workspace(), &Workspace::clientAdded, this, &InputMethod::clientAdded);
84 connect(waylandServer()->seat(), &SeatInterface::focusedTextInputSurfaceChanged, this, &InputMethod::handleFocusedSurfaceChanged);
85
86 TextInputV2Interface *textInputV2 = waylandServer()->seat()->textInputV2();
87 connect(textInputV2, &TextInputV2Interface::requestShowInputPanel, this, &InputMethod::show);
88 connect(textInputV2, &TextInputV2Interface::requestHideInputPanel, this, &InputMethod::hide);
89 connect(textInputV2, &TextInputV2Interface::surroundingTextChanged, this, &InputMethod::surroundingTextChanged);
90 connect(textInputV2, &TextInputV2Interface::contentTypeChanged, this, &InputMethod::contentTypeChanged);
91 connect(textInputV2, &TextInputV2Interface::stateUpdated, this, &InputMethod::textInputInterfaceV2StateUpdated);
92
93 TextInputV3Interface *textInputV3 = waylandServer()->seat()->textInputV3();
94 connect(textInputV3, &TextInputV3Interface::surroundingTextChanged, this, &InputMethod::surroundingTextChanged);
95 connect(textInputV3, &TextInputV3Interface::contentTypeChanged, this, &InputMethod::contentTypeChanged);
96 connect(textInputV3, &TextInputV3Interface::stateCommitted, this, &InputMethod::stateCommitted);
97
98 if (m_enabled) {
99 connect(textInputV2, &TextInputV2Interface::enabledChanged, this, &InputMethod::textInputInterfaceV2EnabledChanged);
100 connect(textInputV3, &TextInputV3Interface::enabledChanged, this, &InputMethod::textInputInterfaceV3EnabledChanged);
101 }
102 }
103 }
104
show()105 void InputMethod::show()
106 {
107 setActive(true);
108 }
109
hide()110 void InputMethod::hide()
111 {
112 setActive(false);
113 }
114
setActive(bool active)115 void InputMethod::setActive(bool active)
116 {
117 const bool wasActive = waylandServer()->inputMethod()->context();
118 if (wasActive && !active) {
119 waylandServer()->inputMethod()->sendDeactivate();
120 }
121
122 if (active) {
123 if (!m_enabled) {
124 return;
125 }
126
127 if (!wasActive) {
128 waylandServer()->inputMethod()->sendActivate();
129 } else {
130 waylandServer()->inputMethod()->context()->sendReset();
131 }
132 adoptInputMethodContext();
133 } else {
134 updateInputPanelState();
135 }
136
137 if (wasActive != isActive()) {
138 Q_EMIT activeChanged(active);
139 }
140 }
141
clientAdded(AbstractClient * _client)142 void InputMethod::clientAdded(AbstractClient *_client)
143 {
144 if (!_client->isInputMethod()) {
145 return;
146 }
147
148 if (m_inputClient) {
149 qCWarning(KWIN_VIRTUALKEYBOARD) << "Replacing input client" << m_inputClient << "with" << _client;
150 disconnect(m_inputClient, nullptr, this, nullptr);
151 }
152
153 const auto client = dynamic_cast<InputPanelV1Client *>(_client);
154 m_inputClient = client;
155 connect(client->surface(), &SurfaceInterface::inputChanged, this, &InputMethod::updateInputPanelState);
156 connect(client, &QObject::destroyed, this, [this] {
157 if (m_trackedClient) {
158 m_trackedClient->setVirtualKeyboardGeometry({});
159 }
160 });
161 connect(m_inputClient, &AbstractClient::frameGeometryChanged, this, &InputMethod::updateInputPanelState);
162 connect(m_inputClient, &AbstractClient::windowHidden, this, &InputMethod::updateInputPanelState);
163 connect(m_inputClient, &AbstractClient::windowClosed, this, &InputMethod::updateInputPanelState);
164 connect(m_inputClient, &AbstractClient::windowShown, this, &InputMethod::visibleChanged);
165 connect(m_inputClient, &AbstractClient::windowHidden, this, &InputMethod::visibleChanged);
166 connect(m_inputClient, &AbstractClient::windowClosed, this, &InputMethod::visibleChanged);
167 Q_EMIT visibleChanged();
168 updateInputPanelState();
169 }
170
setTrackedClient(AbstractClient * trackedClient)171 void InputMethod::setTrackedClient(AbstractClient* trackedClient)
172 {
173 // Reset the old client virtual keybaord geom if necessary
174 // Old and new clients could be the same if focus moves between subsurfaces
175 if (m_trackedClient == trackedClient) {
176 return;
177 }
178 if (m_trackedClient) {
179 m_trackedClient->setVirtualKeyboardGeometry(QRect());
180 disconnect(m_trackedClient, &AbstractClient::frameGeometryChanged, this, &InputMethod::updateInputPanelState);
181 }
182 m_trackedClient = trackedClient;
183 if (m_trackedClient) {
184 connect(m_trackedClient, &AbstractClient::frameGeometryChanged, this, &InputMethod::updateInputPanelState, Qt::QueuedConnection);
185 }
186 updateInputPanelState();
187 }
188
handleFocusedSurfaceChanged()189 void InputMethod::handleFocusedSurfaceChanged()
190 {
191 SurfaceInterface *focusedSurface = waylandServer()->seat()->focusedTextInputSurface();
192 setTrackedClient(waylandServer()->findClient(focusedSurface));
193 if (!focusedSurface) {
194 setActive(false);
195 }
196 }
197
surroundingTextChanged()198 void InputMethod::surroundingTextChanged()
199 {
200 auto t2 = waylandServer()->seat()->textInputV2();
201 auto t3 = waylandServer()->seat()->textInputV3();
202 auto inputContext = waylandServer()->inputMethod()->context();
203 if (!inputContext) {
204 return;
205 }
206 if (t2 && t2->isEnabled()) {
207 inputContext->sendSurroundingText(t2->surroundingText(), t2->surroundingTextCursorPosition(), t2->surroundingTextSelectionAnchor());
208 return;
209 }
210 if (t3 && t3->isEnabled()) {
211 inputContext->sendSurroundingText(t3->surroundingText(), t3->surroundingTextCursorPosition(), t3->surroundingTextSelectionAnchor());
212 return;
213 }
214 }
215
contentTypeChanged()216 void InputMethod::contentTypeChanged()
217 {
218 auto t2 = waylandServer()->seat()->textInputV2();
219 auto t3 = waylandServer()->seat()->textInputV3();
220 auto inputContext = waylandServer()->inputMethod()->context();
221 if (!inputContext) {
222 return;
223 }
224 if (t2 && t2->isEnabled()) {
225 inputContext->sendContentType(t2->contentHints(), t2->contentPurpose());
226 }
227 if (t3 && t3->isEnabled()) {
228 inputContext->sendContentType(t3->contentHints(), t3->contentPurpose());
229 }
230 }
231
textInputInterfaceV2StateUpdated(quint32 serial,KWaylandServer::TextInputV2Interface::UpdateReason reason)232 void InputMethod::textInputInterfaceV2StateUpdated(quint32 serial, KWaylandServer::TextInputV2Interface::UpdateReason reason)
233 {
234 if (!m_enabled) {
235 return;
236 }
237 Q_UNUSED(serial);
238
239 auto t2 = waylandServer()->seat()->textInputV2();
240 auto inputContext = waylandServer()->inputMethod()->context();
241 if (!inputContext) {
242 return;
243 }
244 if (!t2 || !t2->isEnabled()) {
245 return;
246 }
247 switch (reason) {
248 case KWaylandServer::TextInputV2Interface::UpdateReason::StateChange:
249 break;
250 case KWaylandServer::TextInputV2Interface::UpdateReason::StateEnter:
251 case KWaylandServer::TextInputV2Interface::UpdateReason::StateFull:
252 adoptInputMethodContext();
253 break;
254 case KWaylandServer::TextInputV2Interface::UpdateReason::StateReset:
255 inputContext->sendReset();
256 break;
257 }
258 }
259
textInputInterfaceV2EnabledChanged()260 void InputMethod::textInputInterfaceV2EnabledChanged()
261 {
262 if (!m_enabled) {
263 return;
264 }
265
266 auto t = waylandServer()->seat()->textInputV2();
267 setActive(t->isEnabled());
268 }
269
textInputInterfaceV3EnabledChanged()270 void InputMethod::textInputInterfaceV3EnabledChanged()
271 {
272 if (!m_enabled) {
273 return;
274 }
275
276 auto t3 = waylandServer()->seat()->textInputV3();
277 setActive(t3->isEnabled());
278 if (!t3->isEnabled()) {
279 // reset value of preedit when textinput is disabled
280 preedit.text = QString();
281 preedit.begin = 0;
282 preedit.end = 0;
283 }
284 auto context = waylandServer()->inputMethod()->context();
285 if (context) {
286 context->sendReset();
287 adoptInputMethodContext();
288 }
289 }
290
stateCommitted(uint32_t serial)291 void InputMethod::stateCommitted(uint32_t serial)
292 {
293 if (!isEnabled()) {
294 return;
295 }
296 TextInputV3Interface *textInputV3 = waylandServer()->seat()->textInputV3();
297 if (!textInputV3) {
298 return;
299 }
300
301 if (auto inputContext = waylandServer()->inputMethod()->context()) {
302 inputContext->sendCommitState(serial);
303 }
304 setActive(textInputV3->isEnabled());
305 }
306
setEnabled(bool enabled)307 void InputMethod::setEnabled(bool enabled)
308 {
309 if (m_enabled == enabled) {
310 return;
311 }
312 m_enabled = enabled;
313 Q_EMIT enabledChanged(m_enabled);
314
315 // send OSD message
316 QDBusMessage msg = QDBusMessage::createMethodCall(
317 QStringLiteral("org.kde.plasmashell"),
318 QStringLiteral("/org/kde/osdService"),
319 QStringLiteral("org.kde.osdService"),
320 QStringLiteral("virtualKeyboardEnabledChanged")
321 );
322 msg.setArguments({enabled});
323 QDBusConnection::sessionBus().asyncCall(msg);
324 if (!m_enabled) {
325 hide();
326 stopInputMethod();
327 } else {
328 startInputMethod();
329 }
330 // save value into config
331 kwinApp()->config()->group("Wayland").writeEntry("VirtualKeyboardEnabled", m_enabled);
332 kwinApp()->config()->sync();
333 }
334
keysymToKeycode(quint32 sym)335 static quint32 keysymToKeycode(quint32 sym)
336 {
337 switch(sym) {
338 case XKB_KEY_BackSpace:
339 return KEY_BACKSPACE;
340 case XKB_KEY_Return:
341 return KEY_ENTER;
342 case XKB_KEY_Left:
343 return KEY_LEFT;
344 case XKB_KEY_Right:
345 return KEY_RIGHT;
346 case XKB_KEY_Up:
347 return KEY_UP;
348 case XKB_KEY_Down:
349 return KEY_DOWN;
350 default:
351 return KEY_UNKNOWN;
352 }
353 }
354
keysymReceived(quint32 serial,quint32 time,quint32 sym,bool pressed,Qt::KeyboardModifiers modifiers)355 void InputMethod::keysymReceived(quint32 serial, quint32 time, quint32 sym, bool pressed, Qt::KeyboardModifiers modifiers)
356 {
357 Q_UNUSED(serial)
358 Q_UNUSED(time)
359 auto t2 = waylandServer()->seat()->textInputV2();
360 if (t2 && t2->isEnabled()) {
361 if (pressed) {
362 t2->keysymPressed(sym, modifiers);
363 } else {
364 t2->keysymReleased(sym, modifiers);
365 }
366 return;
367 }
368 auto t3 = waylandServer()->seat()->textInputV3();
369 if (t3 && t3->isEnabled()) {
370 KWaylandServer::KeyboardKeyState state;
371 if (pressed) {
372 state = KWaylandServer::KeyboardKeyState::Pressed;
373 } else {
374 state = KWaylandServer::KeyboardKeyState::Released;
375 }
376 waylandServer()->seat()->notifyKeyboardKey(keysymToKeycode(sym), state);
377 return;
378 }
379 }
380
commitString(qint32 serial,const QString & text)381 void InputMethod::commitString(qint32 serial, const QString &text)
382 {
383 Q_UNUSED(serial)
384 if (auto t2 = waylandServer()->seat()->textInputV2(); t2 && t2->isEnabled()) {
385 t2->commitString(text.toUtf8());
386 t2->preEdit({}, {});
387 return;
388 } else if (auto t3 = waylandServer()->seat()->textInputV3(); t3 && t3->isEnabled()) {
389 t3->commitString(text.toUtf8());
390 t3->done();
391 return;
392 } else {
393 qCWarning(KWIN_VIRTUALKEYBOARD) << "We have nobody to commit to!!!";
394 }
395 }
396
deleteSurroundingText(int32_t index,uint32_t length)397 void InputMethod::deleteSurroundingText(int32_t index, uint32_t length)
398 {
399 auto t2 = waylandServer()->seat()->textInputV2();
400 if (t2 && t2->isEnabled()) {
401 t2->deleteSurroundingText(index, length);
402 }
403 auto t3 = waylandServer()->seat()->textInputV3();
404 if (t3 && t3->isEnabled()) {
405 t3->deleteSurroundingText(index, length);
406 }
407 }
408
setCursorPosition(qint32 index,qint32 anchor)409 void InputMethod::setCursorPosition(qint32 index, qint32 anchor)
410 {
411 auto t2 = waylandServer()->seat()->textInputV2();
412 if (t2 && t2->isEnabled()) {
413 t2->setCursorPosition(index, anchor);
414 }
415 }
416
setLanguage(uint32_t serial,const QString & language)417 void InputMethod::setLanguage(uint32_t serial, const QString &language)
418 {
419 Q_UNUSED(serial)
420 auto t2 = waylandServer()->seat()->textInputV2();
421 if (t2 && t2->isEnabled()) {
422 t2->setLanguage(language.toUtf8());
423 }
424 }
425
setTextDirection(uint32_t serial,Qt::LayoutDirection direction)426 void InputMethod::setTextDirection(uint32_t serial, Qt::LayoutDirection direction)
427 {
428 Q_UNUSED(serial)
429 auto t2 = waylandServer()->seat()->textInputV2();
430 if (t2 && t2->isEnabled()) {
431 t2->setTextDirection(direction);
432 }
433 }
434
setPreeditCursor(qint32 index)435 void InputMethod::setPreeditCursor(qint32 index)
436 {
437 auto t2 = waylandServer()->seat()->textInputV2();
438 if (t2 && t2->isEnabled()) {
439 t2->setPreEditCursor(index);
440 }
441 auto t3 = waylandServer()->seat()->textInputV3();
442 if (t3 && t3->isEnabled()) {
443 preedit.begin = index;
444 preedit.end = index;
445 t3->sendPreEditString(preedit.text, preedit.begin, preedit.end);
446 }
447 }
448
449
setPreeditString(uint32_t serial,const QString & text,const QString & commit)450 void InputMethod::setPreeditString(uint32_t serial, const QString &text, const QString &commit)
451 {
452 Q_UNUSED(serial)
453 auto t2 = waylandServer()->seat()->textInputV2();
454 if (t2 && t2->isEnabled()) {
455 t2->preEdit(text.toUtf8(), commit.toUtf8());
456 }
457 auto t3 = waylandServer()->seat()->textInputV3();
458 if (t3 && t3->isEnabled()) {
459 preedit.text = text;
460 t3->sendPreEditString(preedit.text, preedit.begin, preedit.end);
461 }
462 }
463
key(quint32,quint32,quint32 keyCode,bool pressed)464 void InputMethod::key(quint32 /*serial*/, quint32 /*time*/, quint32 keyCode, bool pressed)
465 {
466 waylandServer()->seat()->notifyKeyboardKey(keyCode, pressed ? KWaylandServer::KeyboardKeyState::Pressed : KWaylandServer::KeyboardKeyState::Released);
467 }
468
modifiers(quint32 serial,quint32 mods_depressed,quint32 mods_latched,quint32 mods_locked,quint32 group)469 void InputMethod::modifiers(quint32 serial, quint32 mods_depressed, quint32 mods_latched, quint32 mods_locked, quint32 group)
470 {
471 Q_UNUSED(serial)
472 auto xkb = input()->keyboard()->xkb();
473 xkb->updateModifiers(mods_depressed, mods_latched, mods_locked, group);
474 }
475
adoptInputMethodContext()476 void InputMethod::adoptInputMethodContext()
477 {
478 auto inputContext = waylandServer()->inputMethod()->context();
479
480 TextInputV2Interface *t2 = waylandServer()->seat()->textInputV2();
481 TextInputV3Interface *t3 = waylandServer()->seat()->textInputV3();
482
483 if (t2 && t2->isEnabled()) {
484 inputContext->sendSurroundingText(t2->surroundingText(), t2->surroundingTextCursorPosition(), t2->surroundingTextSelectionAnchor());
485 inputContext->sendPreferredLanguage(t2->preferredLanguage());
486 inputContext->sendContentType(t2->contentHints(), t2->contentPurpose());
487 connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::language, this, &InputMethod::setLanguage);
488 connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::textDirection, this, &InputMethod::setTextDirection);
489 }
490
491 if (t3 && t3->isEnabled()) {
492 inputContext->sendSurroundingText(t3->surroundingText(), t3->surroundingTextCursorPosition(), t3->surroundingTextSelectionAnchor());
493 inputContext->sendContentType(t3->contentHints(), t3->contentPurpose());
494 }
495 inputContext->sendCommitState(m_serial++);
496
497 connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::keysym, this, &InputMethod::keysymReceived, Qt::UniqueConnection);
498 connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::key, this, &InputMethod::key, Qt::UniqueConnection);
499 connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::modifiers, this, &InputMethod::modifiers, Qt::UniqueConnection);
500 connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::commitString, this, &InputMethod::commitString, Qt::UniqueConnection);
501 connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::deleteSurroundingText, this, &InputMethod::deleteSurroundingText, Qt::UniqueConnection);
502 connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::cursorPosition, this, &InputMethod::setCursorPosition, Qt::UniqueConnection);
503 connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::preeditString, this, &InputMethod::setPreeditString, Qt::UniqueConnection);
504 connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::preeditCursor, this, &InputMethod::setPreeditCursor, Qt::UniqueConnection);
505 connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::keyboardGrabRequested, this, &InputMethod::installKeyboardGrab, Qt::UniqueConnection);
506 }
507
updateInputPanelState()508 void InputMethod::updateInputPanelState()
509 {
510 if (!waylandServer()) {
511 return;
512 }
513
514 auto t = waylandServer()->seat()->textInputV2();
515
516 if (!t) {
517 return;
518 }
519
520 QRect overlap = QRect(0, 0, 0, 0);
521 if (m_trackedClient) {
522 const bool bottomKeyboard = m_inputClient && m_inputClient->mode() != InputPanelV1Client::Overlay && m_inputClient->isShown(false);
523 m_trackedClient->setVirtualKeyboardGeometry(bottomKeyboard ? m_inputClient->inputGeometry() : QRect());
524
525 if (m_inputClient) {
526 overlap = m_trackedClient->frameGeometry() & m_inputClient->inputGeometry();
527 overlap.moveTo(m_trackedClient->mapToLocal(overlap.topLeft()));
528 }
529 }
530 t->setInputPanelState(m_inputClient && m_inputClient->isShown(false), overlap);
531 }
532
setInputMethodCommand(const QString & command)533 void InputMethod::setInputMethodCommand(const QString &command)
534 {
535 if (m_inputMethodCommand == command) {
536 return;
537 }
538
539 m_inputMethodCommand = command;
540
541 if (m_enabled) {
542 startInputMethod();
543 }
544 Q_EMIT availableChanged();
545 }
546
stopInputMethod()547 void InputMethod::stopInputMethod()
548 {
549 if (!m_inputMethodProcess) {
550 return;
551 }
552 disconnect(m_inputMethodProcess, nullptr, this, nullptr);
553
554 m_inputMethodProcess->terminate();
555 if (!m_inputMethodProcess->waitForFinished()) {
556 m_inputMethodProcess->kill();
557 m_inputMethodProcess->waitForFinished();
558 }
559 if (waylandServer()) {
560 waylandServer()->destroyInputMethodConnection();
561 }
562 m_inputMethodProcess->deleteLater();
563 m_inputMethodProcess = nullptr;
564 }
565
startInputMethod()566 void InputMethod::startInputMethod()
567 {
568 stopInputMethod();
569 if (m_inputMethodCommand.isEmpty() || kwinApp()->isTerminating()) {
570 return;
571 }
572
573 connect(waylandServer(), &WaylandServer::terminatingInternalClientConnection, this, &InputMethod::stopInputMethod, Qt::UniqueConnection);
574
575 QStringList arguments = KShell::splitArgs(m_inputMethodCommand);
576 if (arguments.isEmpty()) {
577 qWarning("Failed to launch the input method server: %s is an invalid command", qPrintable(m_inputMethodCommand));
578 return;
579 }
580
581 const QString program = arguments.takeFirst();
582 int socket = waylandServer()->createInputMethodConnection();
583 if (socket < 0) {
584 qWarning("Failed to create the input method connection");
585 return;
586 }
587 socket = dup(socket);
588
589 QProcessEnvironment environment = kwinApp()->processStartupEnvironment();
590 environment.insert(QStringLiteral("WAYLAND_SOCKET"), QByteArray::number(socket));
591 environment.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("wayland"));
592 environment.remove("DISPLAY");
593 environment.remove("WAYLAND_DISPLAY");
594 environment.remove("XAUTHORITY");
595
596 m_inputMethodProcess = new Process(this);
597 m_inputMethodProcess->setProcessChannelMode(QProcess::ForwardedErrorChannel);
598 m_inputMethodProcess->setProcessEnvironment(environment);
599 m_inputMethodProcess->setProgram(program);
600 m_inputMethodProcess->setArguments(arguments);
601 m_inputMethodProcess->start();
602 close(socket);
603 connect(m_inputMethodProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus) {
604 if (exitStatus == QProcess::CrashExit) {
605 m_inputMethodCrashes++;
606 m_inputMethodCrashTimer.start();
607 qWarning() << "Input Method crashed" << m_inputMethodProcess->program() << m_inputMethodProcess->arguments() << exitCode << exitStatus;
608 if (m_inputMethodCrashes < 5) {
609 startInputMethod();
610 } else {
611 qWarning() << "Input Method keeps crashing, please fix" << m_inputMethodProcess->program() << m_inputMethodProcess->arguments();
612 stopInputMethod();
613 }
614 }
615 });
616 }
isActive() const617 bool InputMethod::isActive() const
618 {
619 return waylandServer()->inputMethod()->context();
620 }
621
622 class InputKeyboardFilter : public InputEventFilter {
623 public:
InputKeyboardFilter(KWaylandServer::InputMethodGrabV1 * grab)624 InputKeyboardFilter(KWaylandServer::InputMethodGrabV1 *grab)
625 : m_keyboardGrab(grab)
626 {
627 }
628
keyEvent(QKeyEvent * event)629 bool keyEvent(QKeyEvent *event) override {
630 if (event->isAutoRepeat()) {
631 return true;
632 }
633 auto newState = event->type() == QEvent::KeyPress ? KWaylandServer::KeyboardKeyState::Pressed : KWaylandServer::KeyboardKeyState::Released;
634 m_keyboardGrab->sendKey(waylandServer()->display()->nextSerial(), event->timestamp(), event->nativeScanCode(), newState);
635 return true;
636 }
637 InputMethodGrabV1 *const m_keyboardGrab;
638 };
639
installKeyboardGrab(KWaylandServer::InputMethodGrabV1 * keyboardGrab)640 void InputMethod::installKeyboardGrab(KWaylandServer::InputMethodGrabV1 *keyboardGrab)
641 {
642 auto xkb = input()->keyboard()->xkb();
643 auto filter = new InputKeyboardFilter(keyboardGrab);
644 keyboardGrab->sendKeymap(xkb->keymapContents());
645 input()->prependInputEventFilter(filter);
646 connect(keyboardGrab, &QObject::destroyed, input(), [filter] {
647 input()->uninstallInputEventFilter(filter);
648 });
649 }
650
isVisible() const651 bool InputMethod::isVisible() const
652 {
653 return m_inputClient && m_inputClient->isShown(false);
654 }
655
isAvailable() const656 bool InputMethod::isAvailable() const
657 {
658 return !m_inputMethodCommand.isEmpty();
659 }
660
661 }
662