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

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