KWin
Loading...
Searching...
No Matches
inputmethod.cpp
Go to the documentation of this file.
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
11#include <config-kwin.h>
12
13#include "input.h"
14#include "inputpanelv1window.h"
15#include "keyboard_input.h"
16#include "utils/common.h"
18#include "wayland_server.h"
19#include "window.h"
20#include "workspace.h"
21#if KWIN_BUILD_SCREENLOCKER
22#include "screenlockerwatcher.h"
23#endif
24#include "tablet_input.h"
25#include "touch_input.h"
26#include "wayland/display.h"
28#include "wayland/keyboard.h"
29#include "wayland/seat.h"
30#include "wayland/surface.h"
33#include "xkb.h"
34
35#include <KLocalizedString>
36#include <KShell>
37#include <KKeyServer>
38
39#include <QDBusConnection>
40#include <QDBusMessage>
41#include <QDBusPendingCall>
42#include <QKeyEvent>
43#include <QMenu>
44
45#include <linux/input-event-codes.h>
46#include <unistd.h>
47#include <xkbcommon/xkbcommon-keysyms.h>
48
49namespace KWin
50{
51
52static std::vector<quint32> textToKey(const QString &text)
53{
54 if (text.isEmpty()) {
55 return {};
56 }
57
58 auto sequence = QKeySequence::fromString(text);
59 if (sequence.isEmpty()) {
60 return {};
61 }
62
63 const QList<int> syms(KKeyServer::keyQtToSymXs(sequence[0]));
64 if (syms.empty()) {
65 return {};
66 }
67
68 std::optional<int> keyCode;
69 for (int sym : syms) {
70 auto code = input()->keyboard()->xkb()->keycodeFromKeysym(sym);
71 if (code) {
72 keyCode = code;
73 break;
74 }
75 }
76 if (!keyCode) {
77 return {};
78 }
79
80 if (text.isUpper()) {
81 return {KEY_LEFTSHIFT, quint32(keyCode.value())};
82 }
83
84 return {quint32(keyCode.value())};
85}
86
88{
89 m_enabled = kwinApp()->config()->group(QStringLiteral("Wayland")).readEntry("VirtualKeyboardEnabled", true);
90 // this is actually too late. Other processes are started before init,
91 // so might miss the availability of text input
92 // but without Workspace we don't have the window listed at all
93 if (workspace()) {
94 init();
95 } else {
96 connect(kwinApp(), &Application::workspaceCreated, this, &InputMethod::init);
97 }
98}
99
101{
102 stopInputMethod();
103}
104
106{
107 // Stop restarting the input method if it starts crashing very frequently
108 m_inputMethodCrashTimer.setInterval(20000);
109 m_inputMethodCrashTimer.setSingleShot(true);
110 connect(&m_inputMethodCrashTimer, &QTimer::timeout, this, [this] {
111 m_inputMethodCrashes = 0;
112 });
113#if KWIN_BUILD_SCREENLOCKER
114 connect(kwinApp()->screenLockerWatcher(), &ScreenLockerWatcher::aboutToLock, this, &InputMethod::hide);
115#endif
116
117 new VirtualKeyboardDBus(this);
118 qCDebug(KWIN_VIRTUALKEYBOARD) << "Registering the DBus interface";
119
120 if (waylandServer()) {
121 new TextInputManagerV1Interface(waylandServer()->display(), this);
122 new TextInputManagerV2Interface(waylandServer()->display(), this);
123 new TextInputManagerV3Interface(waylandServer()->display(), this);
124
125 connect(waylandServer()->seat(), &SeatInterface::focusedTextInputSurfaceChanged, this, &InputMethod::handleFocusedSurfaceChanged);
126
127 TextInputV1Interface *textInputV1 = waylandServer()->seat()->textInputV1();
130 connect(textInputV1, &TextInputV1Interface::surroundingTextChanged, this, &InputMethod::surroundingTextChanged);
131 connect(textInputV1, &TextInputV1Interface::contentTypeChanged, this, &InputMethod::contentTypeChanged);
132 connect(textInputV1, &TextInputV1Interface::stateUpdated, this, &InputMethod::textInputInterfaceV1StateUpdated);
133 connect(textInputV1, &TextInputV1Interface::reset, this, &InputMethod::textInputInterfaceV1Reset);
134 connect(textInputV1, &TextInputV1Interface::invokeAction, this, &InputMethod::invokeAction);
135 connect(textInputV1, &TextInputV1Interface::enabledChanged, this, &InputMethod::textInputInterfaceV1EnabledChanged);
136
137 TextInputV2Interface *textInputV2 = waylandServer()->seat()->textInputV2();
140 connect(textInputV2, &TextInputV2Interface::surroundingTextChanged, this, &InputMethod::surroundingTextChanged);
141 connect(textInputV2, &TextInputV2Interface::contentTypeChanged, this, &InputMethod::contentTypeChanged);
142 connect(textInputV2, &TextInputV2Interface::stateUpdated, this, &InputMethod::textInputInterfaceV2StateUpdated);
143 connect(textInputV2, &TextInputV2Interface::enabledChanged, this, &InputMethod::textInputInterfaceV2EnabledChanged);
144
145 TextInputV3Interface *textInputV3 = waylandServer()->seat()->textInputV3();
146 connect(textInputV3, &TextInputV3Interface::surroundingTextChanged, this, &InputMethod::surroundingTextChanged);
147 connect(textInputV3, &TextInputV3Interface::contentTypeChanged, this, &InputMethod::contentTypeChanged);
148 connect(textInputV3, &TextInputV3Interface::stateCommitted, this, &InputMethod::stateCommitted);
149 connect(textInputV3, &TextInputV3Interface::enabledChanged, this, &InputMethod::textInputInterfaceV3EnabledChanged);
150 connect(textInputV3, &TextInputV3Interface::enableRequested, this, &InputMethod::textInputInterfaceV3EnableRequested);
151
152 connect(input()->keyboard()->xkb(), &Xkb::modifierStateChanged, this, [this]() {
153 m_hasPendingModifiers = true;
154 });
155 }
156}
157
159{
160 m_shouldShowPanel = true;
161 if (m_panel) {
162 m_panel->show();
163 updateInputPanelState();
164 } else {
165 if (!isActive()) {
166 refreshActive();
167 }
168
169 // refreshActive affects the result of isActive
170 if (isActive()) {
171 adoptInputMethodContext();
172 }
173 }
174}
175
177{
178 m_shouldShowPanel = false;
179 if (m_panel) {
180 m_panel->hide();
181 updateInputPanelState();
182 }
183}
184
186{
187 static bool alwaysShowIm = qEnvironmentVariableIntValue("KWIN_IM_SHOW_ALWAYS") != 0;
188 return alwaysShowIm || input()->touch() == input()->lastInputHandler()
189 || input()->tablet() == input()->lastInputHandler();
190}
191
192void InputMethod::refreshActive()
193{
194 auto seat = waylandServer()->seat();
195 auto t1 = seat->textInputV1();
196 auto t2 = seat->textInputV2();
197 auto t3 = seat->textInputV3();
198
199 bool active = false;
200 if (auto focusedSurface = seat->focusedTextInputSurface()) {
201 auto client = focusedSurface->client();
202 if ((t1->clientSupportsTextInput(client) && t1->isEnabled()) || (t2->clientSupportsTextInput(client) && t2->isEnabled()) || (t3->clientSupportsTextInput(client) && t3->isEnabled())) {
203 active = true;
204 }
205 }
206
207 setActive(active);
208}
209
210void InputMethod::setActive(bool active)
211{
212 const bool wasActive = waylandServer()->inputMethod()->context();
213 if (wasActive && !active) {
215 }
216
217 if (active) {
218 if (!m_enabled) {
219 return;
220 }
221
222 if (!wasActive) {
224 }
225 adoptInputMethodContext();
226 } else {
227 updateInputPanelState();
228 }
229
230 if (wasActive != isActive()) {
231 Q_EMIT activeChanged(active);
232 }
233}
234
236{
237 return m_panel;
238}
239
241{
242 Q_ASSERT(panel->isInputMethod());
243 if (m_panel) {
244 qCWarning(KWIN_VIRTUALKEYBOARD) << "Replacing input panel" << m_panel << "with" << panel;
245 disconnect(m_panel, nullptr, this, nullptr);
246 }
247
248 m_panel = panel;
249 connect(panel, &Window::closed, this, [this]() {
250 if (m_trackedWindow) {
251 m_trackedWindow->setVirtualKeyboardGeometry({});
252 }
253 });
254 connect(m_panel, &Window::frameGeometryChanged, this, &InputMethod::updateInputPanelState);
255 connect(m_panel, &Window::windowHidden, this, &InputMethod::updateInputPanelState);
256 connect(m_panel, &Window::closed, this, &InputMethod::updateInputPanelState);
257 connect(m_panel, &Window::windowShown, this, &InputMethod::visibleChanged);
258 connect(m_panel, &Window::windowHidden, this, &InputMethod::visibleChanged);
259 connect(m_panel, &Window::closed, this, &InputMethod::visibleChanged);
260 Q_EMIT visibleChanged();
261 updateInputPanelState();
262 Q_EMIT panelChanged();
263
264 if (m_shouldShowPanel) {
265 show();
266 }
267}
268
269void InputMethod::setTrackedWindow(Window *trackedWindow)
270{
271 // Reset the old window virtual keybaord geom if necessary
272 // Old and new windows could be the same if focus moves between subsurfaces
273 if (m_trackedWindow == trackedWindow) {
274 return;
275 }
276 if (m_trackedWindow) {
277 m_trackedWindow->setVirtualKeyboardGeometry(QRect());
278 disconnect(m_trackedWindow, &Window::frameGeometryChanged, this, &InputMethod::updateInputPanelState);
279 }
280 m_trackedWindow = trackedWindow;
281 m_shouldShowPanel = false;
282 if (m_trackedWindow) {
283 connect(m_trackedWindow, &Window::frameGeometryChanged, this, &InputMethod::updateInputPanelState, Qt::QueuedConnection);
284 }
285 updateInputPanelState();
286}
287
288void InputMethod::handleFocusedSurfaceChanged()
289{
290 auto seat = waylandServer()->seat();
291 SurfaceInterface *focusedSurface = seat->focusedTextInputSurface();
292
293 setTrackedWindow(waylandServer()->findWindow(focusedSurface));
294
295 const auto client = focusedSurface ? focusedSurface->client() : nullptr;
296 bool ret = seat->textInputV2()->clientSupportsTextInput(client)
297 || seat->textInputV3()->clientSupportsTextInput(client);
298 if (ret != m_activeClientSupportsTextInput) {
299 m_activeClientSupportsTextInput = ret;
301 }
302}
303
304void InputMethod::surroundingTextChanged()
305{
306 auto t2 = waylandServer()->seat()->textInputV2();
307 auto t3 = waylandServer()->seat()->textInputV3();
308 auto inputContext = waylandServer()->inputMethod()->context();
309 if (!inputContext) {
310 return;
311 }
312 if (t2 && t2->isEnabled()) {
313 inputContext->sendSurroundingText(t2->surroundingText(), t2->surroundingTextCursorPosition(), t2->surroundingTextSelectionAnchor());
314 return;
315 }
316 if (t3 && t3->isEnabled()) {
317 inputContext->sendSurroundingText(t3->surroundingText(), t3->surroundingTextCursorPosition(), t3->surroundingTextSelectionAnchor());
318 return;
319 }
320}
321
322void InputMethod::contentTypeChanged()
323{
324 auto t1 = waylandServer()->seat()->textInputV1();
325 auto t2 = waylandServer()->seat()->textInputV2();
326 auto t3 = waylandServer()->seat()->textInputV3();
327 auto inputContext = waylandServer()->inputMethod()->context();
328 if (!inputContext) {
329 return;
330 }
331 if (t1 && t1->isEnabled()) {
332 inputContext->sendContentType(t1->contentHints(), t1->contentPurpose());
333 }
334 if (t2 && t2->isEnabled()) {
335 inputContext->sendContentType(t2->contentHints(), t2->contentPurpose());
336 }
337 if (t3 && t3->isEnabled()) {
338 inputContext->sendContentType(t3->contentHints(), t3->contentPurpose());
339 }
340}
341
342void InputMethod::textInputInterfaceV1Reset()
343{
344 if (!m_enabled) {
345 return;
346 }
347 auto t1 = waylandServer()->seat()->textInputV1();
348 auto inputContext = waylandServer()->inputMethod()->context();
349 if (!inputContext) {
350 return;
351 }
352 if (!t1 || !t1->isEnabled()) {
353 return;
354 }
355 inputContext->sendReset();
356}
357
358void InputMethod::invokeAction(quint32 button, quint32 index)
359{
360 if (!m_enabled) {
361 return;
362 }
363 auto t1 = waylandServer()->seat()->textInputV1();
364 auto inputContext = waylandServer()->inputMethod()->context();
365 if (!inputContext) {
366 return;
367 }
368 if (!t1 || !t1->isEnabled()) {
369 return;
370 }
371 inputContext->sendInvokeAction(button, index);
372}
373
374void InputMethod::textInputInterfaceV1StateUpdated(quint32 serial)
375{
376 if (!m_enabled) {
377 return;
378 }
379 auto t1 = waylandServer()->seat()->textInputV1();
380 auto inputContext = waylandServer()->inputMethod()->context();
381 if (!inputContext) {
382 return;
383 }
384 if (!t1 || !t1->isEnabled()) {
385 return;
386 }
387 inputContext->sendCommitState(serial);
388}
389
390void InputMethod::textInputInterfaceV2StateUpdated(quint32 serial, TextInputV2Interface::UpdateReason reason)
391{
392 if (!m_enabled) {
393 return;
394 }
395
396 auto t2 = waylandServer()->seat()->textInputV2();
397 auto inputContext = waylandServer()->inputMethod()->context();
398 if (!inputContext) {
399 return;
400 }
401 if (!t2 || !t2->isEnabled()) {
402 return;
403 }
404 if (m_panel && shouldShowOnActive()) {
405 m_panel->allow();
406 }
407 switch (reason) {
409 break;
412 adoptInputMethodContext();
413 break;
415 inputContext->sendReset();
416 break;
417 }
418}
419
420void InputMethod::textInputInterfaceV1EnabledChanged()
421{
422 if (!m_enabled) {
423 return;
424 }
425
426 refreshActive();
427}
428
429void InputMethod::textInputInterfaceV2EnabledChanged()
430{
431 if (!m_enabled) {
432 return;
433 }
434
435 refreshActive();
436}
437
438void InputMethod::textInputInterfaceV3EnabledChanged()
439{
440 if (!m_enabled) {
441 return;
442 }
443
444 auto t3 = waylandServer()->seat()->textInputV3();
445 refreshActive();
446 if (t3->isEnabled()) {
447 show();
448 } else {
449 // reset value of preedit when textinput is disabled
450 resetPendingPreedit();
451 }
452 auto context = waylandServer()->inputMethod()->context();
453 if (context) {
454 context->sendReset();
455 adoptInputMethodContext();
456 }
457}
458
459void InputMethod::stateCommitted(uint32_t serial)
460{
461 if (!isEnabled()) {
462 return;
463 }
464 TextInputV3Interface *textInputV3 = waylandServer()->seat()->textInputV3();
465 if (!textInputV3) {
466 return;
467 }
468
469 if (auto inputContext = waylandServer()->inputMethod()->context()) {
470 inputContext->sendCommitState(serial);
471 }
472}
473
474void InputMethod::setEnabled(bool enabled)
475{
476 if (m_enabled == enabled) {
477 return;
478 }
479 m_enabled = enabled;
480 Q_EMIT enabledChanged(m_enabled);
481
482 // send OSD message
483 QDBusMessage msg = QDBusMessage::createMethodCall(
484 QStringLiteral("org.kde.plasmashell"),
485 QStringLiteral("/org/kde/osdService"),
486 QStringLiteral("org.kde.osdService"),
487 QStringLiteral("virtualKeyboardEnabledChanged"));
488 msg.setArguments({enabled});
489 QDBusConnection::sessionBus().asyncCall(msg);
490 if (!m_enabled) {
491 hide();
492 stopInputMethod();
493 } else {
494 startInputMethod();
495 }
496 // save value into config
497 kwinApp()->config()->group(QStringLiteral("Wayland")).writeEntry("VirtualKeyboardEnabled", m_enabled);
498 kwinApp()->config()->sync();
499}
500
501static quint32 keysymToKeycode(quint32 sym)
502{
503 switch (sym) {
504 case XKB_KEY_BackSpace:
505 return KEY_BACKSPACE;
506 case XKB_KEY_Return:
507 return KEY_ENTER;
508 case XKB_KEY_Left:
509 return KEY_LEFT;
510 case XKB_KEY_Right:
511 return KEY_RIGHT;
512 case XKB_KEY_Up:
513 return KEY_UP;
514 case XKB_KEY_Down:
515 return KEY_DOWN;
516 default:
517 return KEY_UNKNOWN;
518 }
519}
520
521void InputMethod::keysymReceived(quint32 serial, quint32 time, quint32 sym, bool pressed, quint32 modifiers)
522{
523 if (auto t1 = waylandServer()->seat()->textInputV1(); t1 && t1->isEnabled()) {
524 if (pressed) {
525 t1->keysymPressed(time, sym, modifiers);
526 } else {
527 t1->keysymReleased(time, sym, modifiers);
528 }
529 return;
530 }
531
532 auto t2 = waylandServer()->seat()->textInputV2();
533 if (t2 && t2->isEnabled()) {
534 if (pressed) {
535 t2->keysymPressed(sym, modifiers);
536 } else {
537 t2->keysymReleased(sym, modifiers);
538 }
539 return;
540 }
541
542 KeyboardKeyState state;
543 if (pressed) {
545 } else {
547 }
548 waylandServer()->seat()->notifyKeyboardKey(keysymToKeycode(sym), state);
549}
550
551void InputMethod::commitString(qint32 serial, const QString &text)
552{
553 if (auto t1 = waylandServer()->seat()->textInputV1(); t1 && t1->isEnabled()) {
554 t1->commitString(text.toUtf8());
555 t1->setPreEditCursor(0);
556 t1->preEdit({}, {});
557 return;
558 }
559 if (auto t2 = waylandServer()->seat()->textInputV2(); t2 && t2->isEnabled()) {
560 t2->commitString(text.toUtf8());
561 t2->setPreEditCursor(0);
562 t2->preEdit({}, {});
563 return;
564 } else if (auto t3 = waylandServer()->seat()->textInputV3(); t3 && t3->isEnabled()) {
565 t3->commitString(text.toUtf8());
566 t3->done();
567 return;
568 } else {
569 // The application has no way of communicating with the input method.
570 // So instead, try to convert what we get from the input method into
571 // keycodes and send those as fake input to the client.
572 auto keys = textToKey(text);
573 if (keys.empty()) {
574 return;
575 }
576
577 // First, send all the extracted keys as pressed keys to the client.
578 for (const auto &key : keys) {
580 }
581
582 // Then, send key release for those keys in reverse.
583 for (auto itr = keys.rbegin(); itr != keys.rend(); ++itr) {
584 // Since we are faking key events, we do not have distinct press/release
585 // events. So instead, just queue the button release so it gets sent
586 // a few moments after the press.
587 auto key = *itr;
588 QMetaObject::invokeMethod(
589 this, [key]() {
591 },
592 Qt::QueuedConnection);
593 }
594 }
595}
596
597void InputMethod::deleteSurroundingText(int32_t index, uint32_t length)
598{
599 // zwp_input_method_v1 Delete surrounding text interface is designed for text-input-v1.
600 // The parameter has different meaning in text-input-v{2,3}.
601 // Current cursor is at index 0.
602 // The actualy deleted text range is [index, index + length].
603 // In v{2,3}'s before/after style, text to be deleted with v{2,3} interface is [-before, after].
604 // And before/after are all unsigned, which make it impossible to do certain things.
605 // Those request will be ignored.
606
607 // Verify we can handle such request.
608 if (index > 0 || index + static_cast<ssize_t>(length) < 0) {
609 return;
610 }
611 const quint32 before = -index;
612 const quint32 after = index + length;
613
614 auto t1 = waylandServer()->seat()->textInputV1();
615 if (t1 && t1->isEnabled()) {
616 t1->deleteSurroundingText(before, after);
617 }
618 auto t2 = waylandServer()->seat()->textInputV2();
619 if (t2 && t2->isEnabled()) {
620 t2->deleteSurroundingText(before, after);
621 }
622 auto t3 = waylandServer()->seat()->textInputV3();
623 if (t3 && t3->isEnabled()) {
624 t3->deleteSurroundingText(before, after);
625 t3->done();
626 }
627}
628
629void InputMethod::setCursorPosition(qint32 index, qint32 anchor)
630{
631 auto t1 = waylandServer()->seat()->textInputV1();
632 if (t1 && t1->isEnabled()) {
633 t1->setCursorPosition(index, anchor);
634 }
635 auto t2 = waylandServer()->seat()->textInputV2();
636 if (t2 && t2->isEnabled()) {
637 t2->setCursorPosition(index, anchor);
638 }
639}
640
641void InputMethod::setLanguage(uint32_t serial, const QString &language)
642{
643 auto t1 = waylandServer()->seat()->textInputV1();
644 if (t1 && t1->isEnabled()) {
645 t1->setLanguage(language.toUtf8());
646 }
647 auto t2 = waylandServer()->seat()->textInputV2();
648 if (t2 && t2->isEnabled()) {
649 t2->setLanguage(language.toUtf8());
650 }
651}
652
653void InputMethod::setTextDirection(uint32_t serial, Qt::LayoutDirection direction)
654{
655 auto t1 = waylandServer()->seat()->textInputV1();
656 if (t1 && t1->isEnabled()) {
657 t1->setTextDirection(direction);
658 }
659 auto t2 = waylandServer()->seat()->textInputV2();
660 if (t2 && t2->isEnabled()) {
661 t2->setTextDirection(direction);
662 }
663}
664
665void InputMethod::setPreeditCursor(qint32 index)
666{
667 auto t1 = waylandServer()->seat()->textInputV1();
668 if (t1 && t1->isEnabled()) {
669 t1->setPreEditCursor(index);
670 }
671 auto t2 = waylandServer()->seat()->textInputV2();
672 if (t2 && t2->isEnabled()) {
673 t2->setPreEditCursor(index);
674 }
675 auto t3 = waylandServer()->seat()->textInputV3();
676 if (t3 && t3->isEnabled()) {
677 preedit.cursor = index;
678 }
679}
680
681void InputMethod::setPreeditStyling(quint32 index, quint32 length, quint32 style)
682{
683 auto t1 = waylandServer()->seat()->textInputV1();
684 if (t1 && t1->isEnabled()) {
685 t1->preEditStyling(index, length, style);
686 }
687 auto t2 = waylandServer()->seat()->textInputV2();
688 if (t2 && t2->isEnabled()) {
689 t2->preEditStyling(index, length, style);
690 }
691 auto t3 = waylandServer()->seat()->textInputV3();
692 if (t3 && t3->isEnabled()) {
693 // preedit style: highlight(4) or selection(6)
694 if (style == 4 || style == 6) {
695 preedit.highlightRanges.emplace_back(index, index + length);
696 }
697 }
698}
699
700void InputMethod::setPreeditString(uint32_t serial, const QString &text, const QString &commit)
701{
702 auto t1 = waylandServer()->seat()->textInputV1();
703 if (t1 && t1->isEnabled()) {
704 t1->preEdit(text.toUtf8(), commit.toUtf8());
705 }
706 auto t2 = waylandServer()->seat()->textInputV2();
707 if (t2 && t2->isEnabled()) {
708 t2->preEdit(text.toUtf8(), commit.toUtf8());
709 }
710 auto t3 = waylandServer()->seat()->textInputV3();
711 if (t3 && t3->isEnabled()) {
712 preedit.text = text;
713 if (!preedit.text.isEmpty()) {
714 quint32 cursor = 0, cursorEnd = 0;
715 if (preedit.cursor > 0) {
716 cursor = cursorEnd = preedit.cursor;
717 }
718 // Check if we can convert highlight style to a range of selection.
719 if (!preedit.highlightRanges.empty()) {
720 std::sort(preedit.highlightRanges.begin(), preedit.highlightRanges.end());
721 // Check if starting point matches.
722 if (preedit.highlightRanges.front().first == cursor) {
723 quint32 end = preedit.highlightRanges.front().second;
724 bool nonContinousHighlight = false;
725 for (size_t i = 1; i < preedit.highlightRanges.size(); i++) {
726 if (end >= preedit.highlightRanges[i].first) {
727 end = std::max(end, preedit.highlightRanges[i].second);
728 } else {
729 nonContinousHighlight = true;
730 break;
731 }
732 }
733 if (!nonContinousHighlight) {
734 cursorEnd = end;
735 }
736 }
737 }
738
739 t3->sendPreEditString(preedit.text, cursor, cursorEnd);
740 }
741 t3->done();
742 }
743 resetPendingPreedit();
744}
745
746void InputMethod::key(quint32 /*serial*/, quint32 /*time*/, quint32 keyCode, bool pressed)
747{
750}
751
752void InputMethod::modifiers(quint32 serial, quint32 mods_depressed, quint32 mods_latched, quint32 mods_locked, quint32 group)
753{
754 auto xkb = input()->keyboard()->xkb();
755 xkb->updateModifiers(mods_depressed, mods_latched, mods_locked, group);
756}
757
759{
760 const bool sendModifiers = m_hasPendingModifiers || force == Force;
761 m_hasPendingModifiers = false;
762 if (!sendModifiers) {
763 return;
764 }
765 auto xkb = input()->keyboard()->xkb();
766 if (m_keyboardGrab) {
767 m_keyboardGrab->sendModifiers(waylandServer()->display()->nextSerial(),
768 xkb->modifierState().depressed,
769 xkb->modifierState().latched,
770 xkb->modifierState().locked,
771 xkb->currentLayout());
772 }
773}
774
775void InputMethod::adoptInputMethodContext()
776{
777 auto inputContext = waylandServer()->inputMethod()->context();
778
782
783 if (t1 && t1->isEnabled()) {
784 inputContext->sendSurroundingText(t1->surroundingText(), t1->surroundingTextCursorPosition(), t1->surroundingTextSelectionAnchor());
785 inputContext->sendPreferredLanguage(t1->preferredLanguage());
786 inputContext->sendContentType(t1->contentHints(), t2->contentPurpose());
787 connect(inputContext, &InputMethodContextV1Interface::language, this, &InputMethod::setLanguage);
788 connect(inputContext, &InputMethodContextV1Interface::textDirection, this, &InputMethod::setTextDirection);
789 } else if (t2 && t2->isEnabled()) {
790 inputContext->sendSurroundingText(t2->surroundingText(), t2->surroundingTextCursorPosition(), t2->surroundingTextSelectionAnchor());
791 inputContext->sendPreferredLanguage(t2->preferredLanguage());
792 inputContext->sendContentType(t2->contentHints(), t2->contentPurpose());
793 connect(inputContext, &InputMethodContextV1Interface::language, this, &InputMethod::setLanguage);
794 connect(inputContext, &InputMethodContextV1Interface::textDirection, this, &InputMethod::setTextDirection);
795 } else if (t3 && t3->isEnabled()) {
796 inputContext->sendSurroundingText(t3->surroundingText(), t3->surroundingTextCursorPosition(), t3->surroundingTextSelectionAnchor());
797 inputContext->sendContentType(t3->contentHints(), t3->contentPurpose());
798 } else {
799 // When we have neither text-input-v2 nor text-input-v3 we can only send
800 // fake key events, not more complex text. So ask the input method to
801 // only send basic characters without any pre-editing.
802 inputContext->sendContentType(TextInputContentHint::Latin, TextInputContentPurpose::Normal);
803 }
804
805 inputContext->sendCommitState(m_serial++);
806
807 connect(inputContext, &InputMethodContextV1Interface::keysym, this, &InputMethod::keysymReceived, Qt::UniqueConnection);
808 connect(inputContext, &InputMethodContextV1Interface::key, this, &InputMethod::key, Qt::UniqueConnection);
809 connect(inputContext, &InputMethodContextV1Interface::modifiers, this, &InputMethod::modifiers, Qt::UniqueConnection);
810 connect(inputContext, &InputMethodContextV1Interface::commitString, this, &InputMethod::commitString, Qt::UniqueConnection);
811 connect(inputContext, &InputMethodContextV1Interface::deleteSurroundingText, this, &InputMethod::deleteSurroundingText, Qt::UniqueConnection);
812 connect(inputContext, &InputMethodContextV1Interface::cursorPosition, this, &InputMethod::setCursorPosition, Qt::UniqueConnection);
813 connect(inputContext, &InputMethodContextV1Interface::preeditStyling, this, &InputMethod::setPreeditStyling, Qt::UniqueConnection);
814 connect(inputContext, &InputMethodContextV1Interface::preeditString, this, &InputMethod::setPreeditString, Qt::UniqueConnection);
815 connect(inputContext, &InputMethodContextV1Interface::preeditCursor, this, &InputMethod::setPreeditCursor, Qt::UniqueConnection);
816 connect(inputContext, &InputMethodContextV1Interface::keyboardGrabRequested, this, &InputMethod::installKeyboardGrab, Qt::UniqueConnection);
817 connect(inputContext, &InputMethodContextV1Interface::modifiersMap, this, &InputMethod::updateModifiersMap, Qt::UniqueConnection);
818}
819
820void InputMethod::updateInputPanelState()
821{
822 if (!waylandServer()) {
823 return;
824 }
825
826 auto t = waylandServer()->seat()->textInputV2();
827
828 if (!t) {
829 return;
830 }
831
832 if (m_panel && shouldShowOnActive()) {
833 m_panel->allow();
834 }
835
836 QRectF overlap = QRectF(0, 0, 0, 0);
837 if (m_trackedWindow) {
838 const bool bottomKeyboard = m_panel && m_panel->mode() != InputPanelV1Window::Mode::Overlay && m_panel->isShown();
839 m_trackedWindow->setVirtualKeyboardGeometry(bottomKeyboard ? m_panel->frameGeometry() : QRectF());
840
841 if (m_panel && m_panel->mode() != InputPanelV1Window::Mode::Overlay) {
842 overlap = m_trackedWindow->frameGeometry() & m_panel->frameGeometry();
843 overlap.moveTo(m_trackedWindow->mapToLocal(overlap.topLeft()));
844 }
845 }
846 t->setInputPanelState(m_panel && m_panel->isShown(), overlap.toRect());
847}
848
849void InputMethod::setInputMethodCommand(const QString &command)
850{
851 if (m_inputMethodCommand == command) {
852 return;
853 }
854
855 m_inputMethodCommand = command;
856
857 if (m_enabled) {
858 startInputMethod();
859 }
860 Q_EMIT availableChanged();
861}
862
863void InputMethod::stopInputMethod()
864{
865 if (!m_inputMethodProcess) {
866 return;
867 }
868 disconnect(m_inputMethodProcess, nullptr, this, nullptr);
869
870 m_inputMethodProcess->terminate();
871 if (!m_inputMethodProcess->waitForFinished()) {
872 m_inputMethodProcess->kill();
873 m_inputMethodProcess->waitForFinished();
874 }
875 m_inputMethodProcess->deleteLater();
876 m_inputMethodProcess = nullptr;
877
879}
880
881void InputMethod::startInputMethod()
882{
883 stopInputMethod();
884 if (m_inputMethodCommand.isEmpty() || kwinApp()->isTerminating()) {
885 return;
886 }
887
888 QStringList arguments = KShell::splitArgs(m_inputMethodCommand);
889 if (arguments.isEmpty()) {
890 qWarning("Failed to launch the input method server: %s is an invalid command", qPrintable(m_inputMethodCommand));
891 return;
892 }
893
894 const QString program = arguments.takeFirst();
896 if (socket < 0) {
897 qWarning("Failed to create the input method connection");
898 return;
899 }
900 socket = dup(socket);
901
902 QProcessEnvironment environment = kwinApp()->processStartupEnvironment();
903 environment.insert(QStringLiteral("WAYLAND_SOCKET"), QByteArray::number(socket));
904 environment.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("wayland"));
905 // When we use Maliit as virtual keyboard, we want KWin to handle the animation
906 // since that works a lot better. So we need to tell Maliit to not do client side
907 // animation.
908 environment.insert(QStringLiteral("MALIIT_ENABLE_ANIMATIONS"), "0");
909
910 m_inputMethodProcess = new QProcess(this);
911 m_inputMethodProcess->setProcessChannelMode(QProcess::ForwardedErrorChannel);
912 m_inputMethodProcess->setProcessEnvironment(environment);
913 m_inputMethodProcess->setProgram(program);
914 m_inputMethodProcess->setArguments(arguments);
915 m_inputMethodProcess->start();
916 close(socket);
917 connect(m_inputMethodProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus) {
918 if (exitStatus == QProcess::CrashExit) {
919 m_inputMethodCrashes++;
920 m_inputMethodCrashTimer.start();
921 qWarning() << "Input Method crashed" << m_inputMethodProcess->program() << m_inputMethodProcess->arguments() << exitCode << exitStatus;
922 if (m_inputMethodCrashes < 5) {
923 startInputMethod();
924 } else {
925 qWarning() << "Input Method keeps crashing, please fix" << m_inputMethodProcess->program() << m_inputMethodProcess->arguments();
926 stopInputMethod();
927 }
928 }
929 });
930}
932{
933 return waylandServer()->inputMethod()->context();
934}
935
937{
938 return isActive() ? m_keyboardGrab : nullptr;
939}
940
941void InputMethod::installKeyboardGrab(InputMethodGrabV1 *keyboardGrab)
942{
943 auto xkb = input()->keyboard()->xkb();
944 m_keyboardGrab = keyboardGrab;
945 keyboardGrab->sendKeymap(xkb->keymapContents());
947}
948
949void InputMethod::updateModifiersMap(const QByteArray &modifiers)
950{
951 TextInputV2Interface *t2 = waylandServer()->seat()->textInputV2();
952
953 if (t2 && t2->isEnabled()) {
954 t2->setModifiersMap(modifiers);
955 }
956}
957
959{
960 return m_panel && m_panel->isShown() && m_panel->readyForPainting();
961}
962
964{
965 return !m_inputMethodCommand.isEmpty();
966}
967
968void InputMethod::resetPendingPreedit()
969{
970 preedit.text = QString();
971 preedit.cursor = 0;
972 preedit.highlightRanges.clear();
973}
974
976{
977 return m_activeClientSupportsTextInput;
978}
979
981{
982 setActive(true);
983 show();
984}
985
986void InputMethod::textInputInterfaceV3EnableRequested()
987{
988 refreshActive();
989 show();
990}
991}
992
993#include "moc_inputmethod.cpp"
void language(quint32 serial, const QString &language)
void deleteSurroundingText(qint32 index, quint32 length)
void sendSurroundingText(const QString &text, quint32 cursor, quint32 anchor)
void modifiersMap(const QByteArray &map)
void sendInvokeAction(quint32 button, quint32 index)
void preeditString(quint32 serial, const QString &text, const QString &commit)
void key(quint32 serial, quint32 time, quint32 key, bool pressed)
void modifiers(quint32 serial, quint32 mods_depressed, quint32 mods_latched, quint32 mods_locked, quint32 group)
void cursorPosition(qint32 index, qint32 anchor)
void sendContentType(KWin::TextInputContentHints hint, KWin::TextInputContentPurpose purpose)
void keysym(quint32 serial, quint32 time, quint32 sym, bool pressed, quint32 modifiers)
void keyboardGrabRequested(InputMethodGrabV1 *keyboardGrab)
void textDirection(quint32 serial, Qt::LayoutDirection direction)
void preeditStyling(quint32 index, quint32 length, quint32 style)
void commitString(quint32 serial, const QString &text)
void sendKeymap(const QByteArray &content)
~InputMethod() override
void activeClientSupportsTextInputChanged()
void setInputMethodCommand(const QString &path)
void enabledChanged(bool enabled)
bool shouldShowOnActive() const
bool isVisible() const
void setActive(bool active)
bool isActive() const
bool isEnabled() const
Definition inputmethod.h:51
void forwardModifiers(ForwardModifiersForce force)
void setPanel(InputPanelV1Window *panel)
InputPanelV1Window * panel() const
void activeChanged(bool active)
bool isAvailable() const
InputMethodGrabV1 * keyboardGrab()
bool activeClientSupportsTextInput() const
void setEnabled(bool enable)
InputMethodContextV1Interface * context() const
bool isInputMethod() const override
KeyboardInputRedirection * keyboard() const
Definition input.h:216
TabletInputRedirection * tablet() const
Definition input.h:224
TouchInputRedirection * touch() const
Definition input.h:228
QObject * lastInputHandler() const
Definition input.cpp:2734
TextInputV1Interface * textInputV1() const
Definition seat.cpp:1264
void notifyKeyboardKey(quint32 keyCode, KeyboardKeyState state)
Definition seat.cpp:968
void focusedTextInputSurfaceChanged()
TextInputV2Interface * textInputV2() const
Definition seat.cpp:1269
TextInputV3Interface * textInputV3() const
Definition seat.cpp:1274
Represent the Global for the interface.
Represent the Global for the interface.
Represent the Global for the interface.
Represents a generic Resource for a text input object.
qint32 surroundingTextSelectionAnchor() const
QString preferredLanguage() const
void stateUpdated(quint32 serial)
void preEditStyling(uint32_t index, uint32_t length, uint32_t style)
void setLanguage(const QString &languageTag)
qint32 surroundingTextCursorPosition() const
void setPreEditCursor(qint32 index)
void preEdit(const QString &text, const QString &commitText)
void setTextDirection(Qt::LayoutDirection direction)
void setCursorPosition(qint32 index, qint32 anchor)
void invokeAction(quint32 button, quint32 index)
void deleteSurroundingText(quint32 beforeLength, quint32 afterLength)
TextInputContentHints contentHints() const
QString surroundingText() const
Represents a generic Resource for a text input object.
void preEdit(const QString &text, const QString &commitText)
void setPreEditCursor(qint32 index)
void deleteSurroundingText(quint32 beforeLength, quint32 afterLength)
TextInputContentHints contentHints() const
qint32 surroundingTextCursorPosition() const
void keysymPressed(quint32 keysym, quint32 modifiers=0)
QString surroundingText() const
void setTextDirection(Qt::LayoutDirection direction)
void preEditStyling(uint32_t index, uint32_t length, uint32_t style)
void setCursorPosition(qint32 index, qint32 anchor)
QString preferredLanguage() const
void stateUpdated(uint32_t serial, UpdateReason reason)
TextInputContentPurpose contentPurpose() const
void setModifiersMap(const QByteArray &modifiersMap)
qint32 surroundingTextSelectionAnchor() const
void setLanguage(const QString &languageTag)
Represents a generic Resource for a text input object.A TextInputV3Interface gets created by the Text...
void stateCommitted(quint32 serial)
void deleteSurroundingText(quint32 beforeLength, quint32 afterLength)
TextInputContentPurpose contentPurpose() const
qint32 surroundingTextSelectionAnchor() const
QString surroundingText() const
qint32 surroundingTextCursorPosition() const
TextInputContentHints contentHints() const
SeatInterface * seat() const
InputMethodV1Interface * inputMethod() const
void windowShown(KWin::Window *window)
void frameGeometryChanged(const QRectF &oldGeometry)
void windowHidden(KWin::Window *window)
void modifierStateChanged()
std::optional< int > keycodeFromKeysym(xkb_keysym_t keysym)
Definition xkb.cpp:750
void updateModifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group)
Definition xkb.cpp:380
KWayland::Client::Seat * seat
MockInputMethod * inputMethod()
WaylandServer * waylandServer()
Workspace * workspace()
Definition workspace.h:830
InputRedirection * input()
Definition input.h:549
KeyboardKeyState
Definition seat.h:82