1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3#include "qibusplatforminputcontext.h"
4
5#include <QDebug>
6#include <QTextCharFormat>
7#include <QGuiApplication>
8#include <QWindow>
9#include <QEvent>
10#include <QFile>
11#include <QFileInfo>
12#include <QStandardPaths>
13#include <QDBusVariant>
14#include <QDBusPendingReply>
15#include <QDBusReply>
16#include <QDBusServiceWatcher>
17
18#include "qibusproxy.h"
19#include "qibusproxyportal.h"
20#include "qibusinputcontextproxy.h"
21#include "qibustypes.h"
22
23#include <qpa/qplatformcursor.h>
24#include <qpa/qplatformscreen.h>
25#include <qpa/qwindowsysteminterface_p.h>
26
27#include <private/qguiapplication_p.h>
28#include <private/qxkbcommon_p.h>
29
30#include <memory>
31
32#include <sys/types.h>
33#include <signal.h>
34
35
36#ifndef IBUS_RELEASE_MASK
37#define IBUS_RELEASE_MASK (1 << 30)
38#define IBUS_SHIFT_MASK (1 << 0)
39#define IBUS_CONTROL_MASK (1 << 2)
40#define IBUS_MOD1_MASK (1 << 3)
41#define IBUS_META_MASK (1 << 28)
42#endif
43
44QT_BEGIN_NAMESPACE
45
46using namespace Qt::StringLiterals;
47
48Q_LOGGING_CATEGORY(lcQpaInputMethods, "qt.qpa.input.methods");
49
50class QIBusPlatformInputContextPrivate
51{
52 Q_DISABLE_COPY_MOVE(QIBusPlatformInputContextPrivate)
53public:
54 // This enum might be synced with IBusPreeditFocusMode
55 // in ibustypes.h of IBUS project
56 enum PreeditFocusMode {
57 PREEDIT_CLEAR = 0,
58 PREEDIT_COMMIT = 1,
59 };
60
61 QIBusPlatformInputContextPrivate();
62 ~QIBusPlatformInputContextPrivate()
63 {
64 // dereference QDBusConnection to actually disconnect
65 serviceWatcher.setConnection(QDBusConnection(QString()));
66 context = nullptr;
67 portalBus = nullptr;
68 bus = nullptr;
69 QDBusConnection::disconnectFromBus(name: "QIBusProxy"_L1);
70 }
71
72 static QString getSocketPath();
73
74 void createConnection();
75 void initBus();
76 void createBusProxy();
77
78 std::unique_ptr<QIBusProxy> bus;
79 std::unique_ptr<QIBusProxyPortal> portalBus; // bus and portalBus are alternative.
80 std::unique_ptr<QIBusInputContextProxy> context;
81 QDBusServiceWatcher serviceWatcher;
82
83 bool usePortal; // return value of shouldConnectIbusPortal
84 bool valid;
85 bool busConnected;
86 QString predit;
87 QList<QInputMethodEvent::Attribute> attributes;
88 bool needsSurroundingText;
89 QLocale locale;
90 PreeditFocusMode preeditFocusMode = PREEDIT_COMMIT; // for backward compatibility
91};
92
93
94QIBusPlatformInputContext::QIBusPlatformInputContext ()
95 : d(new QIBusPlatformInputContextPrivate())
96{
97 if (!d->usePortal) {
98 QString socketPath = QIBusPlatformInputContextPrivate::getSocketPath();
99 QFile file(socketPath);
100 if (file.open(flags: QFile::ReadOnly)) {
101#if QT_CONFIG(filesystemwatcher)
102 qCDebug(qtQpaInputMethods) << "socketWatcher.addPath" << socketPath;
103 // If KDE session save is used or restart ibus-daemon,
104 // the applications could run before ibus-daemon runs.
105 // We watch the getSocketPath() to get the launching ibus-daemon.
106 m_socketWatcher.addPath(file: socketPath);
107 connect(sender: &m_socketWatcher, SIGNAL(fileChanged(QString)), receiver: this, SLOT(socketChanged(QString)));
108#endif
109 }
110 m_timer.setSingleShot(true);
111 connect(sender: &m_timer, SIGNAL(timeout()), receiver: this, SLOT(connectToBus()));
112 }
113
114 QObject::connect(sender: &d->serviceWatcher, SIGNAL(serviceRegistered(QString)), receiver: this, SLOT(busRegistered(QString)));
115 QObject::connect(sender: &d->serviceWatcher, SIGNAL(serviceUnregistered(QString)), receiver: this, SLOT(busUnregistered(QString)));
116
117 connectToContextSignals();
118
119 QInputMethod *p = qApp->inputMethod();
120 connect(sender: p, SIGNAL(cursorRectangleChanged()), receiver: this, SLOT(cursorRectChanged()));
121 m_eventFilterUseSynchronousMode = false;
122 if (qEnvironmentVariableIsSet(varName: "IBUS_ENABLE_SYNC_MODE")) {
123 bool ok;
124 int enableSync = qEnvironmentVariableIntValue(varName: "IBUS_ENABLE_SYNC_MODE", ok: &ok);
125 if (ok && enableSync == 1)
126 m_eventFilterUseSynchronousMode = true;
127 }
128}
129
130QIBusPlatformInputContext::~QIBusPlatformInputContext (void)
131{
132 delete d;
133}
134
135bool QIBusPlatformInputContext::isValid() const
136{
137 return d->valid && d->busConnected;
138}
139
140bool QIBusPlatformInputContext::hasCapability(Capability capability) const
141{
142 switch (capability) {
143 case QPlatformInputContext::HiddenTextCapability:
144 return false; // QTBUG-40691, do not show IME on desktop for password entry fields.
145 default:
146 break;
147 }
148 return true;
149}
150
151void QIBusPlatformInputContext::invokeAction(QInputMethod::Action a, int)
152{
153 if (!d->busConnected)
154 return;
155
156 if (a == QInputMethod::Click)
157 commit();
158}
159
160void QIBusPlatformInputContext::reset()
161{
162 if (!d->busConnected)
163 return;
164
165 d->context->Reset();
166 d->predit = QString();
167 d->attributes.clear();
168}
169
170void QIBusPlatformInputContext::commit()
171{
172 if (!d->busConnected)
173 return;
174
175 QObject *input = qApp->focusObject();
176 if (!input) {
177 d->predit = QString();
178 d->attributes.clear();
179 return;
180 }
181
182 if (d->preeditFocusMode == QIBusPlatformInputContextPrivate::PREEDIT_COMMIT) {
183 if (!d->predit.isEmpty()) {
184 QInputMethodEvent event;
185 event.setCommitString(commitString: d->predit);
186 QCoreApplication::sendEvent(receiver: input, event: &event);
187 }
188 } else {
189 if (!d->predit.isEmpty()) {
190 // Clear the existing preedit
191 QInputMethodEvent event;
192 QCoreApplication::sendEvent(receiver: input, event: &event);
193 }
194 }
195
196 d->context->Reset();
197 d->predit = QString();
198 d->attributes.clear();
199}
200
201
202void QIBusPlatformInputContext::update(Qt::InputMethodQueries q)
203{
204 QObject *input = qApp->focusObject();
205
206 if (d->needsSurroundingText && input
207 && (q.testFlag(flag: Qt::ImSurroundingText)
208 || q.testFlag(flag: Qt::ImCursorPosition)
209 || q.testFlag(flag: Qt::ImAnchorPosition))) {
210
211 QInputMethodQueryEvent query(Qt::ImSurroundingText | Qt::ImCursorPosition | Qt::ImAnchorPosition);
212
213 QCoreApplication::sendEvent(receiver: input, event: &query);
214
215 QString surroundingText = query.value(query: Qt::ImSurroundingText).toString();
216 uint cursorPosition = query.value(query: Qt::ImCursorPosition).toUInt();
217 uint anchorPosition = query.value(query: Qt::ImAnchorPosition).toUInt();
218
219 QIBusText text;
220 text.text = surroundingText;
221
222 QVariant variant;
223 variant.setValue(text);
224 QDBusVariant dbusText(variant);
225
226 d->context->SetSurroundingText(text: dbusText, cursor_pos: cursorPosition, anchor_pos: anchorPosition);
227 }
228}
229
230void QIBusPlatformInputContext::cursorRectChanged()
231{
232 if (!d->busConnected)
233 return;
234
235 QRect r = qApp->inputMethod()->cursorRectangle().toRect();
236 if (!r.isValid())
237 return;
238
239 QWindow *inputWindow = qApp->focusWindow();
240 if (!inputWindow)
241 return;
242 if (!inputWindow->screen())
243 return;
244
245 if (QGuiApplication::platformName().startsWith(s: "wayland"_L1)) {
246 auto margins = inputWindow->frameMargins();
247 r.translate(dx: margins.left(), dy: margins.top());
248 qreal scale = inputWindow->devicePixelRatio();
249 QRect newRect = QRect(r.x() * scale, r.y() * scale, r.width() * scale, r.height() * scale);
250 qCDebug(lcQpaInputMethods) << "microFocus" << newRect;
251 d->context->SetCursorLocationRelative(x: newRect.x(), y: newRect.y(),
252 w: newRect.width(), h: newRect.height());
253 return;
254 }
255
256 // x11/xcb
257 auto screenGeometry = inputWindow->screen()->geometry();
258 auto point = inputWindow->mapToGlobal(pos: r.topLeft());
259 qreal scale = inputWindow->devicePixelRatio();
260 auto native = (point - screenGeometry.topLeft()) * scale + screenGeometry.topLeft();
261 QRect newRect(native, r.size() * scale);
262 qCDebug(lcQpaInputMethods) << "microFocus" << newRect;
263 d->context->SetCursorLocation(x: newRect.x(), y: newRect.y(),
264 w: newRect.width(), h: newRect.height());
265}
266
267void QIBusPlatformInputContext::setFocusObject(QObject *object)
268{
269 if (!d->busConnected)
270 return;
271
272 // It would seem natural here to call FocusOut() on the input method if we
273 // transition from an IME accepted focus object to one that does not accept it.
274 // Mysteriously however that is not sufficient to fix bug QTBUG-63066.
275 if (object && !inputMethodAccepted())
276 return;
277
278 qCDebug(lcQpaInputMethods) << "setFocusObject" << object;
279 if (object)
280 d->context->FocusIn();
281 else
282 d->context->FocusOut();
283}
284
285void QIBusPlatformInputContext::commitText(const QDBusVariant &text)
286{
287 QObject *input = qApp->focusObject();
288 if (!input)
289 return;
290
291 const QDBusArgument arg = qvariant_cast<QDBusArgument>(v: text.variant());
292
293 QIBusText t;
294 qCDebug(lcQpaInputMethods) << arg.currentSignature();
295 arg >> t;
296 qCDebug(lcQpaInputMethods) << "commit text:" << t.text;
297
298 QInputMethodEvent event;
299 event.setCommitString(commitString: t.text);
300 QCoreApplication::sendEvent(receiver: input, event: &event);
301
302 d->predit = QString();
303 d->attributes.clear();
304}
305
306void QIBusPlatformInputContext::updatePreeditText(const QDBusVariant &text, uint cursorPos, bool visible)
307{
308 if (!qApp)
309 return;
310
311 QObject *input = qApp->focusObject();
312 if (!input)
313 return;
314
315 const QDBusArgument arg = qvariant_cast<QDBusArgument>(v: text.variant());
316
317 QIBusText t;
318 arg >> t;
319 qCDebug(lcQpaInputMethods) << "preedit text:" << t.text;
320
321 d->attributes = t.attributes.imAttributes();
322 if (!t.text.isEmpty())
323 d->attributes += QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, cursorPos, visible ? 1 : 0, QVariant());
324
325 QInputMethodEvent event(t.text, d->attributes);
326 QCoreApplication::sendEvent(receiver: input, event: &event);
327
328 d->predit = t.text;
329}
330
331void QIBusPlatformInputContext::updatePreeditTextWithMode(const QDBusVariant &text, uint cursorPos, bool visible, uint mode)
332{
333 updatePreeditText(text, cursorPos, visible);
334 if (mode > 0)
335 d->preeditFocusMode = QIBusPlatformInputContextPrivate::PreeditFocusMode::PREEDIT_COMMIT;
336 else
337 d->preeditFocusMode = QIBusPlatformInputContextPrivate::PreeditFocusMode::PREEDIT_CLEAR;
338}
339
340void QIBusPlatformInputContext::forwardKeyEvent(uint keyval, uint keycode, uint state)
341{
342 if (!qApp)
343 return;
344
345 QObject *input = qApp->focusObject();
346 if (!input)
347 return;
348
349 QEvent::Type type = QEvent::KeyPress;
350 if (state & IBUS_RELEASE_MASK)
351 type = QEvent::KeyRelease;
352
353 state &= ~IBUS_RELEASE_MASK;
354 keycode += 8;
355
356 Qt::KeyboardModifiers modifiers = Qt::NoModifier;
357 if (state & IBUS_SHIFT_MASK)
358 modifiers |= Qt::ShiftModifier;
359 if (state & IBUS_CONTROL_MASK)
360 modifiers |= Qt::ControlModifier;
361 if (state & IBUS_MOD1_MASK)
362 modifiers |= Qt::AltModifier;
363 if (state & IBUS_META_MASK)
364 modifiers |= Qt::MetaModifier;
365
366 int qtcode = QXkbCommon::keysymToQtKey(keysym: keyval, modifiers);
367 QString text = QXkbCommon::lookupStringNoKeysymTransformations(keysym: keyval);
368
369 qCDebug(lcQpaInputMethods) << "forwardKeyEvent" << keyval << keycode << state << modifiers << qtcode << text;
370
371 QKeyEvent event(type, qtcode, modifiers, keycode, keyval, state, text);
372 QCoreApplication::sendEvent(receiver: input, event: &event);
373}
374
375void QIBusPlatformInputContext::surroundingTextRequired()
376{
377 qCDebug(lcQpaInputMethods) << "surroundingTextRequired";
378 d->needsSurroundingText = true;
379 update(q: Qt::ImSurroundingText);
380}
381
382void QIBusPlatformInputContext::deleteSurroundingText(int offset, uint n_chars)
383{
384 QObject *input = qApp->focusObject();
385 if (!input)
386 return;
387
388 qCDebug(lcQpaInputMethods) << "deleteSurroundingText" << offset << n_chars;
389
390 QInputMethodEvent event;
391 event.setCommitString(commitString: "", replaceFrom: offset, replaceLength: n_chars);
392 QCoreApplication::sendEvent(receiver: input, event: &event);
393}
394
395void QIBusPlatformInputContext::hidePreeditText()
396{
397 QObject *input = QGuiApplication::focusObject();
398 if (!input)
399 return;
400
401 QList<QInputMethodEvent::Attribute> attributes;
402 QInputMethodEvent event(QString(), attributes);
403 QCoreApplication::sendEvent(receiver: input, event: &event);
404}
405
406void QIBusPlatformInputContext::showPreeditText()
407{
408 QObject *input = QGuiApplication::focusObject();
409 if (!input)
410 return;
411
412 QInputMethodEvent event(d->predit, d->attributes);
413 QCoreApplication::sendEvent(receiver: input, event: &event);
414}
415
416bool QIBusPlatformInputContext::filterEvent(const QEvent *event)
417{
418 if (!d->busConnected)
419 return false;
420
421 if (!inputMethodAccepted())
422 return false;
423
424 const QKeyEvent *keyEvent = static_cast<const QKeyEvent *>(event);
425 quint32 sym = keyEvent->nativeVirtualKey();
426 quint32 code = keyEvent->nativeScanCode();
427 quint32 state = keyEvent->nativeModifiers();
428 quint32 ibusState = state;
429
430 if (keyEvent->type() != QEvent::KeyPress)
431 ibusState |= IBUS_RELEASE_MASK;
432
433 QDBusPendingReply<bool> reply = d->context->ProcessKeyEvent(keyval: sym, keycode: code - 8, state: ibusState);
434
435 if (m_eventFilterUseSynchronousMode || reply.isFinished()) {
436 bool filtered = reply.value();
437 qCDebug(qtQpaInputMethods) << "filterEvent return" << code << sym << state << filtered;
438 return filtered;
439 }
440
441 Qt::KeyboardModifiers modifiers = keyEvent->modifiers();
442 const int qtcode = keyEvent->key();
443
444 // From QKeyEvent::modifiers()
445 switch (qtcode) {
446 case Qt::Key_Shift:
447 modifiers ^= Qt::ShiftModifier;
448 break;
449 case Qt::Key_Control:
450 modifiers ^= Qt::ControlModifier;
451 break;
452 case Qt::Key_Alt:
453 modifiers ^= Qt::AltModifier;
454 break;
455 case Qt::Key_Meta:
456 modifiers ^= Qt::MetaModifier;
457 break;
458 case Qt::Key_AltGr:
459 modifiers ^= Qt::GroupSwitchModifier;
460 break;
461 }
462
463 QVariantList args;
464 args << QVariant::fromValue(value: keyEvent->timestamp());
465 args << QVariant::fromValue(value: static_cast<uint>(keyEvent->type()));
466 args << QVariant::fromValue(value: qtcode);
467 args << QVariant::fromValue(value: code) << QVariant::fromValue(value: sym) << QVariant::fromValue(value: state);
468 args << QVariant::fromValue(value: keyEvent->text());
469 args << QVariant::fromValue(value: keyEvent->isAutoRepeat());
470
471 QIBusFilterEventWatcher *watcher = new QIBusFilterEventWatcher(reply, this, QGuiApplication::focusWindow(), modifiers, args);
472 QObject::connect(sender: watcher, signal: &QDBusPendingCallWatcher::finished, context: this, slot: &QIBusPlatformInputContext::filterEventFinished);
473
474 return true;
475}
476
477void QIBusPlatformInputContext::filterEventFinished(QDBusPendingCallWatcher *call)
478{
479 QIBusFilterEventWatcher *watcher = (QIBusFilterEventWatcher *) call;
480 QDBusPendingReply<bool> reply = *call;
481
482 if (reply.isError()) {
483 call->deleteLater();
484 return;
485 }
486
487 // Use watcher's window instead of the current focused window
488 // since there is a time lag until filterEventFinished() returns.
489 QWindow *window = watcher->window();
490
491 if (!window) {
492 call->deleteLater();
493 return;
494 }
495
496 Qt::KeyboardModifiers modifiers = watcher->modifiers();
497 QVariantList args = watcher->arguments();
498 const ulong time = static_cast<ulong>(args.at(i: 0).toUInt());
499 const QEvent::Type type = static_cast<QEvent::Type>(args.at(i: 1).toUInt());
500 const int qtcode = args.at(i: 2).toInt();
501 const quint32 code = args.at(i: 3).toUInt();
502 const quint32 sym = args.at(i: 4).toUInt();
503 const quint32 state = args.at(i: 5).toUInt();
504 const QString string = args.at(i: 6).toString();
505 const bool isAutoRepeat = args.at(i: 7).toBool();
506
507 // copied from QXcbKeyboard::handleKeyEvent()
508 bool filtered = reply.value();
509 qCDebug(qtQpaInputMethods) << "filterEventFinished return" << code << sym << state << filtered;
510 if (!filtered) {
511#ifndef QT_NO_CONTEXTMENU
512 if (type == QEvent::KeyPress && qtcode == Qt::Key_Menu
513 && window != nullptr) {
514 const QPoint globalPos = window->screen()->handle()->cursor()->pos();
515 const QPoint pos = window->mapFromGlobal(pos: globalPos);
516 QWindowSystemInterfacePrivate::ContextMenuEvent contextMenuEvent(window, false, pos,
517 globalPos, modifiers);
518 QGuiApplicationPrivate::processWindowSystemEvent(e: &contextMenuEvent);
519 }
520#endif
521 QWindowSystemInterfacePrivate::KeyEvent keyEvent(window, time, type, qtcode, modifiers,
522 code, sym, state, string, isAutoRepeat);
523 QGuiApplicationPrivate::processWindowSystemEvent(e: &keyEvent);
524 }
525 call->deleteLater();
526}
527
528QLocale QIBusPlatformInputContext::locale() const
529{
530 // d->locale is not updated when IBus portal is used
531 if (d->usePortal)
532 return QPlatformInputContext::locale();
533 return d->locale;
534}
535
536void QIBusPlatformInputContext::socketChanged(const QString &str)
537{
538 qCDebug(qtQpaInputMethods) << "socketChanged";
539 Q_UNUSED (str);
540
541 m_timer.stop();
542
543 // dereference QDBusConnection to actually disconnect
544 d->serviceWatcher.setConnection(QDBusConnection(QString()));
545 d->context = nullptr;
546 d->bus = nullptr;
547 d->busConnected = false;
548 QDBusConnection::disconnectFromBus(name: "QIBusProxy"_L1);
549
550 m_timer.start(msec: 100);
551}
552
553void QIBusPlatformInputContext::busRegistered(const QString &str)
554{
555 qCDebug(qtQpaInputMethods) << "busRegistered";
556 Q_UNUSED (str);
557 if (d->usePortal) {
558 connectToBus();
559 }
560}
561
562void QIBusPlatformInputContext::busUnregistered(const QString &str)
563{
564 qCDebug(qtQpaInputMethods) << "busUnregistered";
565 Q_UNUSED (str);
566 d->busConnected = false;
567}
568
569// When getSocketPath() is modified, the bus is not established yet
570// so use m_timer.
571void QIBusPlatformInputContext::connectToBus()
572{
573 qCDebug(qtQpaInputMethods) << "QIBusPlatformInputContext::connectToBus";
574 d->initBus();
575 connectToContextSignals();
576
577#if QT_CONFIG(filesystemwatcher)
578 if (!d->usePortal && m_socketWatcher.files().size() == 0)
579 m_socketWatcher.addPath(file: QIBusPlatformInputContextPrivate::getSocketPath());
580#endif
581}
582
583void QIBusPlatformInputContext::globalEngineChanged(const QString &engine_name)
584{
585 if (!d->bus || !d->bus->isValid())
586 return;
587
588 QIBusEngineDesc desc = d->bus->getGlobalEngine();
589 Q_ASSERT(engine_name == desc.engine_name);
590 QLocale locale(desc.language);
591 if (d->locale != locale) {
592 d->locale = locale;
593 emitLocaleChanged();
594 }
595}
596
597void QIBusPlatformInputContext::connectToContextSignals()
598{
599 if (d->bus && d->bus->isValid()) {
600 connect(sender: d->bus.get(), SIGNAL(GlobalEngineChanged(QString)), receiver: this, SLOT(globalEngineChanged(QString)));
601 }
602
603 if (d->context) {
604 connect(asender: d->context.get(), SIGNAL(CommitText(QDBusVariant)), SLOT(commitText(QDBusVariant)));
605 connect(sender: d->context.get(), SIGNAL(UpdatePreeditText(QDBusVariant,uint,bool)), receiver: this, SLOT(updatePreeditText(QDBusVariant,uint,bool)));
606 connect(sender: d->context.get(), SIGNAL(UpdatePreeditTextWithMode(QDBusVariant,uint,bool,uint)), receiver: this, SLOT(updatePreeditTextWithMode(QDBusVariant,uint,bool,uint)));
607 connect(sender: d->context.get(), SIGNAL(ForwardKeyEvent(uint,uint,uint)), receiver: this, SLOT(forwardKeyEvent(uint,uint,uint)));
608 connect(sender: d->context.get(), SIGNAL(DeleteSurroundingText(int,uint)), receiver: this, SLOT(deleteSurroundingText(int,uint)));
609 connect(sender: d->context.get(), SIGNAL(RequireSurroundingText()), receiver: this, SLOT(surroundingTextRequired()));
610 connect(sender: d->context.get(), SIGNAL(HidePreeditText()), receiver: this, SLOT(hidePreeditText()));
611 connect(sender: d->context.get(), SIGNAL(ShowPreeditText()), receiver: this, SLOT(showPreeditText()));
612 }
613}
614
615static inline bool checkNeedPortalSupport()
616{
617 return QFileInfo::exists(file: "/.flatpak-info"_L1) || qEnvironmentVariableIsSet(varName: "SNAP");
618}
619
620static bool shouldConnectIbusPortal()
621{
622 // honor the same env as ibus-gtk
623 return (checkNeedPortalSupport() || qEnvironmentVariableIsSet(varName: "IBUS_USE_PORTAL"));
624}
625
626QIBusPlatformInputContextPrivate::QIBusPlatformInputContextPrivate()
627 : usePortal(shouldConnectIbusPortal()),
628 valid(false),
629 busConnected(false),
630 needsSurroundingText(false)
631{
632 if (usePortal) {
633 valid = true;
634 qCDebug(lcQpaInputMethods) << "use IBus portal";
635 } else {
636 valid = !QStandardPaths::findExecutable(executableName: QString::fromLocal8Bit(ba: "ibus-daemon"), paths: QStringList()).isEmpty();
637 }
638 if (!valid)
639 return;
640 initBus();
641
642 if (bus && bus->isValid()) {
643 QIBusEngineDesc desc = bus->getGlobalEngine();
644 locale = QLocale(desc.language);
645 }
646}
647
648void QIBusPlatformInputContextPrivate::initBus()
649{
650 createConnection();
651 busConnected = false;
652 createBusProxy();
653}
654
655void QIBusPlatformInputContextPrivate::createBusProxy()
656{
657 QDBusConnection connection("QIBusProxy"_L1);
658 if (!connection.isConnected())
659 return;
660
661 const char* ibusService = usePortal ? "org.freedesktop.portal.IBus" : "org.freedesktop.IBus";
662 QDBusReply<QDBusObjectPath> ic;
663 if (usePortal) {
664 portalBus = std::make_unique<QIBusProxyPortal>(args: QLatin1StringView(ibusService),
665 args: "/org/freedesktop/IBus"_L1,
666 args&: connection);
667 if (!portalBus->isValid()) {
668 qWarning(msg: "QIBusPlatformInputContext: invalid portal bus.");
669 return;
670 }
671
672 ic = portalBus->CreateInputContext(name: "QIBusInputContext"_L1);
673 } else {
674 bus = std::make_unique<QIBusProxy>(args: QLatin1StringView(ibusService),
675 args: "/org/freedesktop/IBus"_L1,
676 args&: connection);
677 if (!bus->isValid()) {
678 qWarning(msg: "QIBusPlatformInputContext: invalid bus.");
679 return;
680 }
681
682 ic = bus->CreateInputContext(name: "QIBusInputContext"_L1);
683 }
684
685 serviceWatcher.removeWatchedService(service: ibusService);
686 serviceWatcher.setConnection(connection);
687 serviceWatcher.addWatchedService(newService: ibusService);
688
689 if (!ic.isValid()) {
690 qWarning(msg: "QIBusPlatformInputContext: CreateInputContext failed.");
691 return;
692 }
693
694 context = std::make_unique<QIBusInputContextProxy>(args: QLatin1StringView(ibusService), args: ic.value().path(), args&: connection);
695
696 if (!context->isValid()) {
697 qWarning(msg: "QIBusPlatformInputContext: invalid input context.");
698 return;
699 }
700
701 enum Capabilities {
702 IBUS_CAP_PREEDIT_TEXT = 1 << 0,
703 IBUS_CAP_AUXILIARY_TEXT = 1 << 1,
704 IBUS_CAP_LOOKUP_TABLE = 1 << 2,
705 IBUS_CAP_FOCUS = 1 << 3,
706 IBUS_CAP_PROPERTY = 1 << 4,
707 IBUS_CAP_SURROUNDING_TEXT = 1 << 5
708 };
709 context->SetCapabilities(IBUS_CAP_PREEDIT_TEXT|IBUS_CAP_FOCUS|IBUS_CAP_SURROUNDING_TEXT);
710
711 context->setClientCommitPreedit(QIBusPropTypeClientCommitPreedit(true));
712
713 qCDebug(lcQpaInputMethods) << ">>>> bus connected!";
714 busConnected = true;
715}
716
717QString QIBusPlatformInputContextPrivate::getSocketPath()
718{
719 QByteArray display;
720 QByteArray displayNumber = "0";
721 bool isWayland = false;
722
723 if (qEnvironmentVariableIsSet(varName: "IBUS_ADDRESS_FILE")) {
724 QByteArray path = qgetenv(varName: "IBUS_ADDRESS_FILE");
725 return QString::fromLocal8Bit(ba: path);
726 } else if (qEnvironmentVariableIsSet(varName: "WAYLAND_DISPLAY")) {
727 display = qgetenv(varName: "WAYLAND_DISPLAY");
728 isWayland = true;
729 } else {
730 display = qgetenv(varName: "DISPLAY");
731 }
732 QByteArray host = "unix";
733
734 if (isWayland) {
735 displayNumber = display;
736 } else {
737 int pos = display.indexOf(ch: ':');
738 if (pos > 0)
739 host = display.left(n: pos);
740 ++pos;
741 int pos2 = display.indexOf(ch: '.', from: pos);
742 if (pos2 > 0)
743 displayNumber = display.mid(index: pos, len: pos2 - pos);
744 else
745 displayNumber = display.mid(index: pos);
746 }
747
748 qCDebug(lcQpaInputMethods) << "host=" << host << "displayNumber" << displayNumber;
749
750 return QStandardPaths::writableLocation(type: QStandardPaths::ConfigLocation) +
751 "/ibus/bus/"_L1 +
752 QLatin1StringView(QDBusConnection::localMachineId()) +
753 u'-' + QString::fromLocal8Bit(ba: host) + u'-' + QString::fromLocal8Bit(ba: displayNumber);
754}
755
756void QIBusPlatformInputContextPrivate::createConnection()
757{
758 if (usePortal) {
759 QDBusConnection::connectToBus(type: QDBusConnection::SessionBus, name: "QIBusProxy"_L1);
760 return;
761 }
762
763 QFile file(getSocketPath());
764 if (!file.open(flags: QFile::ReadOnly))
765 return;
766
767 QByteArray address;
768 int pid = -1;
769
770 while (!file.atEnd()) {
771 QByteArray line = file.readLine().trimmed();
772 if (line.startsWith(c: '#'))
773 continue;
774
775 if (line.startsWith(bv: "IBUS_ADDRESS="))
776 address = line.mid(index: sizeof("IBUS_ADDRESS=") - 1);
777 if (line.startsWith(bv: "IBUS_DAEMON_PID="))
778 pid = line.mid(index: sizeof("IBUS_DAEMON_PID=") - 1).toInt();
779 }
780
781 qCDebug(lcQpaInputMethods) << "IBUS_ADDRESS=" << address << "PID=" << pid;
782 if (address.isEmpty() || pid < 0 || kill(pid: pid, sig: 0) != 0)
783 return;
784
785 QDBusConnection::connectToBus(address: QString::fromLatin1(ba: address), name: "QIBusProxy"_L1);
786}
787
788QT_END_NAMESPACE
789
790#include "moc_qibusplatforminputcontext.cpp"
791

source code of qtbase/src/plugins/platforminputcontexts/ibus/qibusplatforminputcontext.cpp