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 <QtXkbCommonSupport/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 
74 QT_BEGIN_NAMESPACE
75 
76 enum { debug = 0 };
77 
78 class QIBusPlatformInputContextPrivate
79 {
80 public:
81     QIBusPlatformInputContextPrivate();
~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 
QIBusPlatformInputContext()112 QIBusPlatformInputContext::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 
~QIBusPlatformInputContext(void)148 QIBusPlatformInputContext::~QIBusPlatformInputContext (void)
149 {
150     delete d;
151 }
152 
isValid() const153 bool QIBusPlatformInputContext::isValid() const
154 {
155     return d->valid && d->busConnected;
156 }
157 
hasCapability(Capability capability) const158 bool 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 
invokeAction(QInputMethod::Action a,int)169 void QIBusPlatformInputContext::invokeAction(QInputMethod::Action a, int)
170 {
171     if (!d->busConnected)
172         return;
173 
174     if (a == QInputMethod::Click)
175         commit();
176 }
177 
reset()178 void 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 
commit()190 void 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 
update(Qt::InputMethodQueries q)216 void 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 
cursorRectChanged()245 void 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 
setFocusObject(QObject * object)263 void 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 
commitText(const QDBusVariant & text)282 void 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 
updatePreeditText(const QDBusVariant & text,uint cursorPos,bool visible)305 void 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 
forwardKeyEvent(uint keyval,uint keycode,uint state)331 void 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 
surroundingTextRequired()367 void QIBusPlatformInputContext::surroundingTextRequired()
368 {
369     if (debug)
370         qDebug("surroundingTextRequired");
371     d->needsSurroundingText = true;
372     update(Qt::ImSurroundingText);
373 }
374 
deleteSurroundingText(int offset,uint n_chars)375 void 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 
hidePreeditText()389 void 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 
showPreeditText()400 void 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 
filterEvent(const QEvent * event)410 bool 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 
filterEventFinished(QDBusPendingCallWatcher * call)471 void 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 
locale() const522 QLocale 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 
socketChanged(const QString & str)530 void 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 
busRegistered(const QString & str)547 void QIBusPlatformInputContext::busRegistered(const QString &str)
548 {
549     qCDebug(qtQpaInputMethods) << "busRegistered";
550     Q_UNUSED (str);
551     if (d->usePortal) {
552         connectToBus();
553     }
554 }
555 
busUnregistered(const QString & str)556 void 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.
connectToBus()565 void 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 
globalEngineChanged(const QString & engine_name)577 void 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 
connectToContextSignals()591 void 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 
checkRunningUnderFlatpak()608 static inline bool checkRunningUnderFlatpak()
609 {
610     return !QStandardPaths::locate(QStandardPaths::RuntimeLocation, QLatin1String("flatpak-info")).isEmpty();
611 }
612 
shouldConnectIbusPortal()613 static bool shouldConnectIbusPortal()
614 {
615     // honor the same env as ibus-gtk
616     return (checkRunningUnderFlatpak() || !qgetenv("IBUS_USE_PORTAL").isNull());
617 }
618 
QIBusPlatformInputContextPrivate()619 QIBusPlatformInputContextPrivate::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 
initBus()646 void QIBusPlatformInputContextPrivate::initBus()
647 {
648     connection = createConnection();
649     busConnected = false;
650     createBusProxy();
651 }
652 
createBusProxy()653 void 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 
getSocketPath()713 QString 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 
createConnection()753 QDBusConnection *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 
784 QT_END_NAMESPACE
785