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