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 // 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 if (debug)
251 qDebug() << "microFocus" << newRect;
252 d->context->SetCursorLocationRelative(x: newRect.x(), y: newRect.y(),
253 w: newRect.width(), h: newRect.height());
254 return;
255 }
256
257 // x11/xcb
258 auto screenGeometry = inputWindow->screen()->geometry();
259 auto point = inputWindow->mapToGlobal(pos: r.topLeft());
260 qreal scale = inputWindow->devicePixelRatio();
261 auto native = (point - screenGeometry.topLeft()) * scale + screenGeometry.topLeft();
262 QRect newRect(native, r.size() * scale);
263 if (debug)
264 qDebug() << "microFocus" << newRect;
265 d->context->SetCursorLocation(x: newRect.x(), y: newRect.y(),
266 w: newRect.width(), h: newRect.height());
267}
268
269void QIBusPlatformInputContext::setFocusObject(QObject *object)
270{
271 if (!d->busConnected)
272 return;
273
274 // It would seem natural here to call FocusOut() on the input method if we
275 // transition from an IME accepted focus object to one that does not accept it.
276 // Mysteriously however that is not sufficient to fix bug QTBUG-63066.
277 if (object && !inputMethodAccepted())
278 return;
279
280 if (debug)
281 qDebug() << "setFocusObject" << object;
282 if (object)
283 d->context->FocusIn();
284 else
285 d->context->FocusOut();
286}
287
288void QIBusPlatformInputContext::commitText(const QDBusVariant &text)
289{
290 QObject *input = qApp->focusObject();
291 if (!input)
292 return;
293
294 const QDBusArgument arg = qvariant_cast<QDBusArgument>(v: text.variant());
295
296 QIBusText t;
297 if (debug)
298 qDebug() << arg.currentSignature();
299 arg >> t;
300 if (debug)
301 qDebug() << "commit text:" << t.text;
302
303 QInputMethodEvent event;
304 event.setCommitString(commitString: t.text);
305 QCoreApplication::sendEvent(receiver: input, event: &event);
306
307 d->predit = QString();
308 d->attributes.clear();
309}
310
311void QIBusPlatformInputContext::updatePreeditText(const QDBusVariant &text, uint cursorPos, bool visible)
312{
313 if (!qApp)
314 return;
315
316 QObject *input = qApp->focusObject();
317 if (!input)
318 return;
319
320 const QDBusArgument arg = qvariant_cast<QDBusArgument>(v: text.variant());
321
322 QIBusText t;
323 arg >> t;
324 if (debug)
325 qDebug() << "preedit text:" << t.text;
326
327 d->attributes = t.attributes.imAttributes();
328 if (!t.text.isEmpty())
329 d->attributes += QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, cursorPos, visible ? 1 : 0, QVariant());
330
331 QInputMethodEvent event(t.text, d->attributes);
332 QCoreApplication::sendEvent(receiver: input, event: &event);
333
334 d->predit = t.text;
335}
336
337void QIBusPlatformInputContext::updatePreeditTextWithMode(const QDBusVariant &text, uint cursorPos, bool visible, uint mode)
338{
339 updatePreeditText(text, cursorPos, visible);
340 if (mode > 0)
341 d->preeditFocusMode = QIBusPlatformInputContextPrivate::PreeditFocusMode::PREEDIT_COMMIT;
342 else
343 d->preeditFocusMode = QIBusPlatformInputContextPrivate::PreeditFocusMode::PREEDIT_CLEAR;
344}
345
346void QIBusPlatformInputContext::forwardKeyEvent(uint keyval, uint keycode, uint state)
347{
348 if (!qApp)
349 return;
350
351 QObject *input = qApp->focusObject();
352 if (!input)
353 return;
354
355 QEvent::Type type = QEvent::KeyPress;
356 if (state & IBUS_RELEASE_MASK)
357 type = QEvent::KeyRelease;
358
359 state &= ~IBUS_RELEASE_MASK;
360 keycode += 8;
361
362 Qt::KeyboardModifiers modifiers = Qt::NoModifier;
363 if (state & IBUS_SHIFT_MASK)
364 modifiers |= Qt::ShiftModifier;
365 if (state & IBUS_CONTROL_MASK)
366 modifiers |= Qt::ControlModifier;
367 if (state & IBUS_MOD1_MASK)
368 modifiers |= Qt::AltModifier;
369 if (state & IBUS_META_MASK)
370 modifiers |= Qt::MetaModifier;
371
372 int qtcode = QXkbCommon::keysymToQtKey(keysym: keyval, modifiers);
373 QString text = QXkbCommon::lookupStringNoKeysymTransformations(keysym: keyval);
374
375 if (debug)
376 qDebug() << "forwardKeyEvent" << keyval << keycode << state << modifiers << qtcode << text;
377
378 QKeyEvent event(type, qtcode, modifiers, keycode, keyval, state, text);
379 QCoreApplication::sendEvent(receiver: input, event: &event);
380}
381
382void QIBusPlatformInputContext::surroundingTextRequired()
383{
384 if (debug)
385 qDebug(msg: "surroundingTextRequired");
386 d->needsSurroundingText = true;
387 update(q: Qt::ImSurroundingText);
388}
389
390void QIBusPlatformInputContext::deleteSurroundingText(int offset, uint n_chars)
391{
392 QObject *input = qApp->focusObject();
393 if (!input)
394 return;
395
396 if (debug)
397 qDebug() << "deleteSurroundingText" << offset << n_chars;
398
399 QInputMethodEvent event;
400 event.setCommitString(commitString: "", replaceFrom: offset, replaceLength: n_chars);
401 QCoreApplication::sendEvent(receiver: input, event: &event);
402}
403
404void QIBusPlatformInputContext::hidePreeditText()
405{
406 QObject *input = QGuiApplication::focusObject();
407 if (!input)
408 return;
409
410 QList<QInputMethodEvent::Attribute> attributes;
411 QInputMethodEvent event(QString(), attributes);
412 QCoreApplication::sendEvent(receiver: input, event: &event);
413}
414
415void QIBusPlatformInputContext::showPreeditText()
416{
417 QObject *input = QGuiApplication::focusObject();
418 if (!input)
419 return;
420
421 QInputMethodEvent event(d->predit, d->attributes);
422 QCoreApplication::sendEvent(receiver: input, event: &event);
423}
424
425bool QIBusPlatformInputContext::filterEvent(const QEvent *event)
426{
427 if (!d->busConnected)
428 return false;
429
430 if (!inputMethodAccepted())
431 return false;
432
433 const QKeyEvent *keyEvent = static_cast<const QKeyEvent *>(event);
434 quint32 sym = keyEvent->nativeVirtualKey();
435 quint32 code = keyEvent->nativeScanCode();
436 quint32 state = keyEvent->nativeModifiers();
437 quint32 ibusState = state;
438
439 if (keyEvent->type() != QEvent::KeyPress)
440 ibusState |= IBUS_RELEASE_MASK;
441
442 QDBusPendingReply<bool> reply = d->context->ProcessKeyEvent(keyval: sym, keycode: code - 8, state: ibusState);
443
444 if (m_eventFilterUseSynchronousMode || reply.isFinished()) {
445 bool filtered = reply.value();
446 qCDebug(qtQpaInputMethods) << "filterEvent return" << code << sym << state << filtered;
447 return filtered;
448 }
449
450 Qt::KeyboardModifiers modifiers = keyEvent->modifiers();
451 const int qtcode = keyEvent->key();
452
453 // From QKeyEvent::modifiers()
454 switch (qtcode) {
455 case Qt::Key_Shift:
456 modifiers ^= Qt::ShiftModifier;
457 break;
458 case Qt::Key_Control:
459 modifiers ^= Qt::ControlModifier;
460 break;
461 case Qt::Key_Alt:
462 modifiers ^= Qt::AltModifier;
463 break;
464 case Qt::Key_Meta:
465 modifiers ^= Qt::MetaModifier;
466 break;
467 case Qt::Key_AltGr:
468 modifiers ^= Qt::GroupSwitchModifier;
469 break;
470 }
471
472 QVariantList args;
473 args << QVariant::fromValue(value: keyEvent->timestamp());
474 args << QVariant::fromValue(value: static_cast<uint>(keyEvent->type()));
475 args << QVariant::fromValue(value: qtcode);
476 args << QVariant::fromValue(value: code) << QVariant::fromValue(value: sym) << QVariant::fromValue(value: state);
477 args << QVariant::fromValue(value: keyEvent->text());
478 args << QVariant::fromValue(value: keyEvent->isAutoRepeat());
479
480 QIBusFilterEventWatcher *watcher = new QIBusFilterEventWatcher(reply, this, QGuiApplication::focusWindow(), modifiers, args);
481 QObject::connect(sender: watcher, signal: &QDBusPendingCallWatcher::finished, context: this, slot: &QIBusPlatformInputContext::filterEventFinished);
482
483 return true;
484}
485
486void QIBusPlatformInputContext::filterEventFinished(QDBusPendingCallWatcher *call)
487{
488 QIBusFilterEventWatcher *watcher = (QIBusFilterEventWatcher *) call;
489 QDBusPendingReply<bool> reply = *call;
490
491 if (reply.isError()) {
492 call->deleteLater();
493 return;
494 }
495
496 // Use watcher's window instead of the current focused window
497 // since there is a time lag until filterEventFinished() returns.
498 QWindow *window = watcher->window();
499
500 if (!window) {
501 call->deleteLater();
502 return;
503 }
504
505 Qt::KeyboardModifiers modifiers = watcher->modifiers();
506 QVariantList args = watcher->arguments();
507 const ulong time = static_cast<ulong>(args.at(i: 0).toUInt());
508 const QEvent::Type type = static_cast<QEvent::Type>(args.at(i: 1).toUInt());
509 const int qtcode = args.at(i: 2).toInt();
510 const quint32 code = args.at(i: 3).toUInt();
511 const quint32 sym = args.at(i: 4).toUInt();
512 const quint32 state = args.at(i: 5).toUInt();
513 const QString string = args.at(i: 6).toString();
514 const bool isAutoRepeat = args.at(i: 7).toBool();
515
516 // copied from QXcbKeyboard::handleKeyEvent()
517 bool filtered = reply.value();
518 qCDebug(qtQpaInputMethods) << "filterEventFinished return" << code << sym << state << filtered;
519 if (!filtered) {
520#ifndef QT_NO_CONTEXTMENU
521 if (type == QEvent::KeyPress && qtcode == Qt::Key_Menu
522 && window != nullptr) {
523 const QPoint globalPos = window->screen()->handle()->cursor()->pos();
524 const QPoint pos = window->mapFromGlobal(pos: globalPos);
525 QWindowSystemInterfacePrivate::ContextMenuEvent contextMenuEvent(window, false, pos,
526 globalPos, modifiers);
527 QGuiApplicationPrivate::processWindowSystemEvent(e: &contextMenuEvent);
528 }
529#endif
530 QWindowSystemInterfacePrivate::KeyEvent keyEvent(window, time, type, qtcode, modifiers,
531 code, sym, state, string, isAutoRepeat);
532 QGuiApplicationPrivate::processWindowSystemEvent(e: &keyEvent);
533 }
534 call->deleteLater();
535}
536
537QLocale QIBusPlatformInputContext::locale() const
538{
539 // d->locale is not updated when IBus portal is used
540 if (d->usePortal)
541 return QPlatformInputContext::locale();
542 return d->locale;
543}
544
545void QIBusPlatformInputContext::socketChanged(const QString &str)
546{
547 qCDebug(qtQpaInputMethods) << "socketChanged";
548 Q_UNUSED (str);
549
550 m_timer.stop();
551
552 // dereference QDBusConnection to actually disconnect
553 d->serviceWatcher.setConnection(QDBusConnection(QString()));
554 d->context = nullptr;
555 d->bus = nullptr;
556 d->busConnected = false;
557 QDBusConnection::disconnectFromBus(name: "QIBusProxy"_L1);
558
559 m_timer.start(msec: 100);
560}
561
562void QIBusPlatformInputContext::busRegistered(const QString &str)
563{
564 qCDebug(qtQpaInputMethods) << "busRegistered";
565 Q_UNUSED (str);
566 if (d->usePortal) {
567 connectToBus();
568 }
569}
570
571void QIBusPlatformInputContext::busUnregistered(const QString &str)
572{
573 qCDebug(qtQpaInputMethods) << "busUnregistered";
574 Q_UNUSED (str);
575 d->busConnected = false;
576}
577
578// When getSocketPath() is modified, the bus is not established yet
579// so use m_timer.
580void QIBusPlatformInputContext::connectToBus()
581{
582 qCDebug(qtQpaInputMethods) << "QIBusPlatformInputContext::connectToBus";
583 d->initBus();
584 connectToContextSignals();
585
586#if QT_CONFIG(filesystemwatcher)
587 if (!d->usePortal && m_socketWatcher.files().size() == 0)
588 m_socketWatcher.addPath(file: QIBusPlatformInputContextPrivate::getSocketPath());
589#endif
590}
591
592void QIBusPlatformInputContext::globalEngineChanged(const QString &engine_name)
593{
594 if (!d->bus || !d->bus->isValid())
595 return;
596
597 QIBusEngineDesc desc = d->bus->getGlobalEngine();
598 Q_ASSERT(engine_name == desc.engine_name);
599 QLocale locale(desc.language);
600 if (d->locale != locale) {
601 d->locale = locale;
602 emitLocaleChanged();
603 }
604}
605
606void QIBusPlatformInputContext::connectToContextSignals()
607{
608 if (d->bus && d->bus->isValid()) {
609 connect(sender: d->bus.get(), SIGNAL(GlobalEngineChanged(QString)), receiver: this, SLOT(globalEngineChanged(QString)));
610 }
611
612 if (d->context) {
613 connect(asender: d->context.get(), SIGNAL(CommitText(QDBusVariant)), SLOT(commitText(QDBusVariant)));
614 connect(sender: d->context.get(), SIGNAL(UpdatePreeditText(QDBusVariant,uint,bool)), receiver: this, SLOT(updatePreeditText(QDBusVariant,uint,bool)));
615 connect(sender: d->context.get(), SIGNAL(UpdatePreeditTextWithMode(QDBusVariant,uint,bool,uint)), receiver: this, SLOT(updatePreeditTextWithMode(QDBusVariant,uint,bool,uint)));
616 connect(sender: d->context.get(), SIGNAL(ForwardKeyEvent(uint,uint,uint)), receiver: this, SLOT(forwardKeyEvent(uint,uint,uint)));
617 connect(sender: d->context.get(), SIGNAL(DeleteSurroundingText(int,uint)), receiver: this, SLOT(deleteSurroundingText(int,uint)));
618 connect(sender: d->context.get(), SIGNAL(RequireSurroundingText()), receiver: this, SLOT(surroundingTextRequired()));
619 connect(sender: d->context.get(), SIGNAL(HidePreeditText()), receiver: this, SLOT(hidePreeditText()));
620 connect(sender: d->context.get(), SIGNAL(ShowPreeditText()), receiver: this, SLOT(showPreeditText()));
621 }
622}
623
624static inline bool checkNeedPortalSupport()
625{
626 return QFileInfo::exists(file: "/.flatpak-info"_L1) || qEnvironmentVariableIsSet(varName: "SNAP");
627}
628
629static bool shouldConnectIbusPortal()
630{
631 // honor the same env as ibus-gtk
632 return (checkNeedPortalSupport() || qEnvironmentVariableIsSet(varName: "IBUS_USE_PORTAL"));
633}
634
635QIBusPlatformInputContextPrivate::QIBusPlatformInputContextPrivate()
636 : usePortal(shouldConnectIbusPortal()),
637 valid(false),
638 busConnected(false),
639 needsSurroundingText(false)
640{
641 if (usePortal) {
642 valid = true;
643 if (debug)
644 qDebug() << "use IBus portal";
645 } else {
646 valid = !QStandardPaths::findExecutable(executableName: QString::fromLocal8Bit(ba: "ibus-daemon"), paths: QStringList()).isEmpty();
647 }
648 if (!valid)
649 return;
650 initBus();
651
652 if (bus && bus->isValid()) {
653 QIBusEngineDesc desc = bus->getGlobalEngine();
654 locale = QLocale(desc.language);
655 }
656}
657
658void QIBusPlatformInputContextPrivate::initBus()
659{
660 createConnection();
661 busConnected = false;
662 createBusProxy();
663}
664
665void QIBusPlatformInputContextPrivate::createBusProxy()
666{
667 QDBusConnection connection("QIBusProxy"_L1);
668 if (!connection.isConnected())
669 return;
670
671 const char* ibusService = usePortal ? "org.freedesktop.portal.IBus" : "org.freedesktop.IBus";
672 QDBusReply<QDBusObjectPath> ic;
673 if (usePortal) {
674 portalBus = std::make_unique<QIBusProxyPortal>(args: QLatin1StringView(ibusService),
675 args: "/org/freedesktop/IBus"_L1,
676 args&: connection);
677 if (!portalBus->isValid()) {
678 qWarning(msg: "QIBusPlatformInputContext: invalid portal bus.");
679 return;
680 }
681
682 ic = portalBus->CreateInputContext(name: "QIBusInputContext"_L1);
683 } else {
684 bus = std::make_unique<QIBusProxy>(args: QLatin1StringView(ibusService),
685 args: "/org/freedesktop/IBus"_L1,
686 args&: connection);
687 if (!bus->isValid()) {
688 qWarning(msg: "QIBusPlatformInputContext: invalid bus.");
689 return;
690 }
691
692 ic = bus->CreateInputContext(name: "QIBusInputContext"_L1);
693 }
694
695 serviceWatcher.removeWatchedService(service: ibusService);
696 serviceWatcher.setConnection(connection);
697 serviceWatcher.addWatchedService(newService: ibusService);
698
699 if (!ic.isValid()) {
700 qWarning(msg: "QIBusPlatformInputContext: CreateInputContext failed.");
701 return;
702 }
703
704 context = std::make_unique<QIBusInputContextProxy>(args: QLatin1StringView(ibusService), args: ic.value().path(), args&: connection);
705
706 if (!context->isValid()) {
707 qWarning(msg: "QIBusPlatformInputContext: invalid input context.");
708 return;
709 }
710
711 enum Capabilities {
712 IBUS_CAP_PREEDIT_TEXT = 1 << 0,
713 IBUS_CAP_AUXILIARY_TEXT = 1 << 1,
714 IBUS_CAP_LOOKUP_TABLE = 1 << 2,
715 IBUS_CAP_FOCUS = 1 << 3,
716 IBUS_CAP_PROPERTY = 1 << 4,
717 IBUS_CAP_SURROUNDING_TEXT = 1 << 5
718 };
719 context->SetCapabilities(IBUS_CAP_PREEDIT_TEXT|IBUS_CAP_FOCUS|IBUS_CAP_SURROUNDING_TEXT);
720
721 context->setClientCommitPreedit(QIBusPropTypeClientCommitPreedit(true));
722
723 if (debug)
724 qDebug(msg: ">>>> bus connected!");
725 busConnected = true;
726}
727
728QString QIBusPlatformInputContextPrivate::getSocketPath()
729{
730 QByteArray display;
731 QByteArray displayNumber = "0";
732 bool isWayland = false;
733
734 if (qEnvironmentVariableIsSet(varName: "IBUS_ADDRESS_FILE")) {
735 QByteArray path = qgetenv(varName: "IBUS_ADDRESS_FILE");
736 return QString::fromLocal8Bit(ba: path);
737 } else if (qEnvironmentVariableIsSet(varName: "WAYLAND_DISPLAY")) {
738 display = qgetenv(varName: "WAYLAND_DISPLAY");
739 isWayland = true;
740 } else {
741 display = qgetenv(varName: "DISPLAY");
742 }
743 QByteArray host = "unix";
744
745 if (isWayland) {
746 displayNumber = display;
747 } else {
748 int pos = display.indexOf(ch: ':');
749 if (pos > 0)
750 host = display.left(n: pos);
751 ++pos;
752 int pos2 = display.indexOf(ch: '.', from: pos);
753 if (pos2 > 0)
754 displayNumber = display.mid(index: pos, len: pos2 - pos);
755 else
756 displayNumber = display.mid(index: pos);
757 }
758
759 if (debug)
760 qDebug() << "host=" << host << "displayNumber" << displayNumber;
761
762 return QStandardPaths::writableLocation(type: QStandardPaths::ConfigLocation) +
763 "/ibus/bus/"_L1 +
764 QLatin1StringView(QDBusConnection::localMachineId()) +
765 u'-' + QString::fromLocal8Bit(ba: host) + u'-' + QString::fromLocal8Bit(ba: displayNumber);
766}
767
768void QIBusPlatformInputContextPrivate::createConnection()
769{
770 if (usePortal) {
771 QDBusConnection::connectToBus(type: QDBusConnection::SessionBus, name: "QIBusProxy"_L1);
772 return;
773 }
774
775 QFile file(getSocketPath());
776 if (!file.open(flags: QFile::ReadOnly))
777 return;
778
779 QByteArray address;
780 int pid = -1;
781
782 while (!file.atEnd()) {
783 QByteArray line = file.readLine().trimmed();
784 if (line.startsWith(c: '#'))
785 continue;
786
787 if (line.startsWith(bv: "IBUS_ADDRESS="))
788 address = line.mid(index: sizeof("IBUS_ADDRESS=") - 1);
789 if (line.startsWith(bv: "IBUS_DAEMON_PID="))
790 pid = line.mid(index: sizeof("IBUS_DAEMON_PID=") - 1).toInt();
791 }
792
793 if (debug)
794 qDebug() << "IBUS_ADDRESS=" << address << "PID=" << pid;
795 if (address.isEmpty() || pid < 0 || kill(pid: pid, sig: 0) != 0)
796 return;
797
798 QDBusConnection::connectToBus(address: QString::fromLatin1(ba: address), name: "QIBusProxy"_L1);
799}
800
801QT_END_NAMESPACE
802
803#include "moc_qibusplatforminputcontext.cpp"
804

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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