| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the plugins of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| #include "qibusplatforminputcontext.h" |
| |
| #include <QtDebug> |
| #include <QTextCharFormat> |
| #include <QGuiApplication> |
| #include <QDBusVariant> |
| #include <qwindow.h> |
| #include <qevent.h> |
| |
| #include <qpa/qplatformcursor.h> |
| #include <qpa/qplatformscreen.h> |
| #include <qpa/qwindowsysteminterface_p.h> |
| |
| #include <QtGui/private/qguiapplication_p.h> |
| |
| #include <QtXkbCommonSupport/private/qxkbcommon_p.h> |
| |
| #include "qibusproxy.h" |
| #include "qibusproxyportal.h" |
| #include "qibusinputcontextproxy.h" |
| #include "qibustypes.h" |
| |
| #include <sys/types.h> |
| #include <signal.h> |
| |
| #include <QtDBus> |
| |
| #ifndef IBUS_RELEASE_MASK |
| #define IBUS_RELEASE_MASK (1 << 30) |
| #define IBUS_SHIFT_MASK (1 << 0) |
| #define IBUS_CONTROL_MASK (1 << 2) |
| #define IBUS_MOD1_MASK (1 << 3) |
| #define IBUS_META_MASK (1 << 28) |
| #endif |
| |
| QT_BEGIN_NAMESPACE |
| |
| enum { debug = 0 }; |
| |
| class QIBusPlatformInputContextPrivate |
| { |
| public: |
| QIBusPlatformInputContextPrivate(); |
| ~QIBusPlatformInputContextPrivate() |
| { |
| delete context; |
| delete bus; |
| delete portalBus; |
| delete connection; |
| } |
| |
| static QString getSocketPath(); |
| |
| QDBusConnection *createConnection(); |
| void initBus(); |
| void createBusProxy(); |
| |
| QDBusConnection *connection; |
| QIBusProxy *bus; |
| QIBusProxyPortal *portalBus; // bus and portalBus are alternative. |
| QIBusInputContextProxy *context; |
| QDBusServiceWatcher serviceWatcher; |
| |
| bool usePortal; // return value of shouldConnectIbusPortal |
| bool valid; |
| bool busConnected; |
| QString predit; |
| QList<QInputMethodEvent::Attribute> attributes; |
| bool needsSurroundingText; |
| QLocale locale; |
| }; |
| |
| |
| QIBusPlatformInputContext::QIBusPlatformInputContext () |
| : d(new QIBusPlatformInputContextPrivate()) |
| { |
| if (!d->usePortal) { |
| QString socketPath = QIBusPlatformInputContextPrivate::getSocketPath(); |
| QFile file(socketPath); |
| if (file.open(QFile::ReadOnly)) { |
| #if QT_CONFIG(filesystemwatcher) |
| qCDebug(qtQpaInputMethods) << "socketWatcher.addPath" << socketPath; |
| // If KDE session save is used or restart ibus-daemon, |
| // the applications could run before ibus-daemon runs. |
| // We watch the getSocketPath() to get the launching ibus-daemon. |
| m_socketWatcher.addPath(socketPath); |
| connect(&m_socketWatcher, SIGNAL(fileChanged(QString)), this, SLOT(socketChanged(QString))); |
| #endif |
| } |
| m_timer.setSingleShot(true); |
| connect(&m_timer, SIGNAL(timeout()), this, SLOT(connectToBus())); |
| } |
| |
| QObject::connect(&d->serviceWatcher, SIGNAL(serviceRegistered(QString)), this, SLOT(busRegistered(QString))); |
| QObject::connect(&d->serviceWatcher, SIGNAL(serviceUnregistered(QString)), this, SLOT(busUnregistered(QString))); |
| |
| connectToContextSignals(); |
| |
| QInputMethod *p = qApp->inputMethod(); |
| connect(p, SIGNAL(cursorRectangleChanged()), this, SLOT(cursorRectChanged())); |
| m_eventFilterUseSynchronousMode = false; |
| if (qEnvironmentVariableIsSet("IBUS_ENABLE_SYNC_MODE")) { |
| bool ok; |
| int enableSync = qEnvironmentVariableIntValue("IBUS_ENABLE_SYNC_MODE", &ok); |
| if (ok && enableSync == 1) |
| m_eventFilterUseSynchronousMode = true; |
| } |
| } |
| |
| QIBusPlatformInputContext::~QIBusPlatformInputContext (void) |
| { |
| delete d; |
| } |
| |
| bool QIBusPlatformInputContext::isValid() const |
| { |
| return d->valid && d->busConnected; |
| } |
| |
| bool QIBusPlatformInputContext::hasCapability(Capability capability) const |
| { |
| switch (capability) { |
| case QPlatformInputContext::HiddenTextCapability: |
| return false; // QTBUG-40691, do not show IME on desktop for password entry fields. |
| default: |
| break; |
| } |
| return true; |
| } |
| |
| void QIBusPlatformInputContext::invokeAction(QInputMethod::Action a, int) |
| { |
| if (!d->busConnected) |
| return; |
| |
| if (a == QInputMethod::Click) |
| commit(); |
| } |
| |
| void QIBusPlatformInputContext::reset() |
| { |
| QPlatformInputContext::reset(); |
| |
| if (!d->busConnected) |
| return; |
| |
| d->context->Reset(); |
| d->predit = QString(); |
| d->attributes.clear(); |
| } |
| |
| void QIBusPlatformInputContext::commit() |
| { |
| QPlatformInputContext::commit(); |
| |
| if (!d->busConnected) |
| return; |
| |
| QObject *input = qApp->focusObject(); |
| if (!input) { |
| d->predit = QString(); |
| d->attributes.clear(); |
| return; |
| } |
| |
| if (!d->predit.isEmpty()) { |
| QInputMethodEvent event; |
| event.setCommitString(d->predit); |
| QCoreApplication::sendEvent(input, &event); |
| } |
| |
| d->context->Reset(); |
| d->predit = QString(); |
| d->attributes.clear(); |
| } |
| |
| |
| void QIBusPlatformInputContext::update(Qt::InputMethodQueries q) |
| { |
| QObject *input = qApp->focusObject(); |
| |
| if (d->needsSurroundingText && input |
| && (q.testFlag(Qt::ImSurroundingText) |
| || q.testFlag(Qt::ImCursorPosition) |
| || q.testFlag(Qt::ImAnchorPosition))) { |
| |
| QInputMethodQueryEvent query(Qt::ImSurroundingText | Qt::ImCursorPosition | Qt::ImAnchorPosition); |
| |
| QCoreApplication::sendEvent(input, &query); |
| |
| QString surroundingText = query.value(Qt::ImSurroundingText).toString(); |
| uint cursorPosition = query.value(Qt::ImCursorPosition).toUInt(); |
| uint anchorPosition = query.value(Qt::ImAnchorPosition).toUInt(); |
| |
| QIBusText text; |
| text.text = surroundingText; |
| |
| QVariant variant; |
| variant.setValue(text); |
| QDBusVariant dbusText(variant); |
| |
| d->context->SetSurroundingText(dbusText, cursorPosition, anchorPosition); |
| } |
| QPlatformInputContext::update(q); |
| } |
| |
| void QIBusPlatformInputContext::cursorRectChanged() |
| { |
| if (!d->busConnected) |
| return; |
| |
| QRect r = qApp->inputMethod()->cursorRectangle().toRect(); |
| if(!r.isValid()) |
| return; |
| |
| QWindow *inputWindow = qApp->focusWindow(); |
| if (!inputWindow) |
| return; |
| r.moveTopLeft(inputWindow->mapToGlobal(r.topLeft())); |
| if (debug) |
| qDebug() << "microFocus" << r; |
| d->context->SetCursorLocation(r.x(), r.y(), r.width(), r.height()); |
| } |
| |
| void QIBusPlatformInputContext::setFocusObject(QObject *object) |
| { |
| if (!d->busConnected) |
| return; |
| |
| // It would seem natural here to call FocusOut() on the input method if we |
| // transition from an IME accepted focus object to one that does not accept it. |
| // Mysteriously however that is not sufficient to fix bug QTBUG-63066. |
| if (!inputMethodAccepted()) |
| return; |
| |
| if (debug) |
| qDebug() << "setFocusObject" << object; |
| if (object) |
| d->context->FocusIn(); |
| else |
| d->context->FocusOut(); |
| } |
| |
| void QIBusPlatformInputContext::commitText(const QDBusVariant &text) |
| { |
| QObject *input = qApp->focusObject(); |
| if (!input) |
| return; |
| |
| const QDBusArgument arg = text.variant().value<QDBusArgument>(); |
| |
| QIBusText t; |
| if (debug) |
| qDebug() << arg.currentSignature(); |
| arg >> t; |
| if (debug) |
| qDebug() << "commit text:" << t.text; |
| |
| QInputMethodEvent event; |
| event.setCommitString(t.text); |
| QCoreApplication::sendEvent(input, &event); |
| |
| d->predit = QString(); |
| d->attributes.clear(); |
| } |
| |
| void QIBusPlatformInputContext::updatePreeditText(const QDBusVariant &text, uint cursorPos, bool visible) |
| { |
| if (!qApp) |
| return; |
| |
| QObject *input = qApp->focusObject(); |
| if (!input) |
| return; |
| |
| const QDBusArgument arg = text.variant().value<QDBusArgument>(); |
| |
| QIBusText t; |
| arg >> t; |
| if (debug) |
| qDebug() << "preedit text:" << t.text; |
| |
| d->attributes = t.attributes.imAttributes(); |
| if (!t.text.isEmpty()) |
| d->attributes += QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, cursorPos, visible ? 1 : 0, QVariant()); |
| |
| QInputMethodEvent event(t.text, d->attributes); |
| QCoreApplication::sendEvent(input, &event); |
| |
| d->predit = t.text; |
| } |
| |
| void QIBusPlatformInputContext::forwardKeyEvent(uint keyval, uint keycode, uint state) |
| { |
| if (!qApp) |
| return; |
| |
| QObject *input = qApp->focusObject(); |
| if (!input) |
| return; |
| |
| QEvent::Type type = QEvent::KeyPress; |
| if (state & IBUS_RELEASE_MASK) |
| type = QEvent::KeyRelease; |
| |
| state &= ~IBUS_RELEASE_MASK; |
| keycode += 8; |
| |
| Qt::KeyboardModifiers modifiers = Qt::NoModifier; |
| if (state & IBUS_SHIFT_MASK) |
| modifiers |= Qt::ShiftModifier; |
| if (state & IBUS_CONTROL_MASK) |
| modifiers |= Qt::ControlModifier; |
| if (state & IBUS_MOD1_MASK) |
| modifiers |= Qt::AltModifier; |
| if (state & IBUS_META_MASK) |
| modifiers |= Qt::MetaModifier; |
| |
| int qtcode = QXkbCommon::keysymToQtKey(keyval, modifiers); |
| QString text = QXkbCommon::lookupStringNoKeysymTransformations(keyval); |
| |
| if (debug) |
| qDebug() << "forwardKeyEvent" << keyval << keycode << state << modifiers << qtcode << text; |
| |
| QKeyEvent event(type, qtcode, modifiers, keycode, keyval, state, text); |
| QCoreApplication::sendEvent(input, &event); |
| } |
| |
| void QIBusPlatformInputContext::surroundingTextRequired() |
| { |
| if (debug) |
| qDebug("surroundingTextRequired"); |
| d->needsSurroundingText = true; |
| update(Qt::ImSurroundingText); |
| } |
| |
| void QIBusPlatformInputContext::deleteSurroundingText(int offset, uint n_chars) |
| { |
| QObject *input = qApp->focusObject(); |
| if (!input) |
| return; |
| |
| if (debug) |
| qDebug() << "deleteSurroundingText" << offset << n_chars; |
| |
| QInputMethodEvent event; |
| event.setCommitString("", offset, n_chars); |
| QCoreApplication::sendEvent(input, &event); |
| } |
| |
| void QIBusPlatformInputContext::hidePreeditText() |
| { |
| QObject *input = QGuiApplication::focusObject(); |
| if (!input) |
| return; |
| |
| QList<QInputMethodEvent::Attribute> attributes; |
| QInputMethodEvent event(QString(), attributes); |
| QCoreApplication::sendEvent(input, &event); |
| } |
| |
| void QIBusPlatformInputContext::showPreeditText() |
| { |
| QObject *input = QGuiApplication::focusObject(); |
| if (!input) |
| return; |
| |
| QInputMethodEvent event(d->predit, d->attributes); |
| QCoreApplication::sendEvent(input, &event); |
| } |
| |
| bool QIBusPlatformInputContext::filterEvent(const QEvent *event) |
| { |
| if (!d->busConnected) |
| return false; |
| |
| if (!inputMethodAccepted()) |
| return false; |
| |
| const QKeyEvent *keyEvent = static_cast<const QKeyEvent *>(event); |
| quint32 sym = keyEvent->nativeVirtualKey(); |
| quint32 code = keyEvent->nativeScanCode(); |
| quint32 state = keyEvent->nativeModifiers(); |
| quint32 ibusState = state; |
| |
| if (keyEvent->type() != QEvent::KeyPress) |
| ibusState |= IBUS_RELEASE_MASK; |
| |
| QDBusPendingReply<bool> reply = d->context->ProcessKeyEvent(sym, code - 8, ibusState); |
| |
| if (m_eventFilterUseSynchronousMode || reply.isFinished()) { |
| bool filtered = reply.value(); |
| qCDebug(qtQpaInputMethods) << "filterEvent return" << code << sym << state << filtered; |
| return filtered; |
| } |
| |
| Qt::KeyboardModifiers modifiers = keyEvent->modifiers(); |
| const int qtcode = keyEvent->key(); |
| |
| // From QKeyEvent::modifiers() |
| switch (qtcode) { |
| case Qt::Key_Shift: |
| modifiers ^= Qt::ShiftModifier; |
| break; |
| case Qt::Key_Control: |
| modifiers ^= Qt::ControlModifier; |
| break; |
| case Qt::Key_Alt: |
| modifiers ^= Qt::AltModifier; |
| break; |
| case Qt::Key_Meta: |
| modifiers ^= Qt::MetaModifier; |
| break; |
| case Qt::Key_AltGr: |
| modifiers ^= Qt::GroupSwitchModifier; |
| break; |
| } |
| |
| QVariantList args; |
| args << QVariant::fromValue(keyEvent->timestamp()); |
| args << QVariant::fromValue(static_cast<uint>(keyEvent->type())); |
| args << QVariant::fromValue(qtcode); |
| args << QVariant::fromValue(code) << QVariant::fromValue(sym) << QVariant::fromValue(state); |
| args << QVariant::fromValue(keyEvent->text()); |
| args << QVariant::fromValue(keyEvent->isAutoRepeat()); |
| |
| QIBusFilterEventWatcher *watcher = new QIBusFilterEventWatcher(reply, this, QGuiApplication::focusWindow(), modifiers, args); |
| QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, &QIBusPlatformInputContext::filterEventFinished); |
| |
| return true; |
| } |
| |
| void QIBusPlatformInputContext::filterEventFinished(QDBusPendingCallWatcher *call) |
| { |
| QIBusFilterEventWatcher *watcher = (QIBusFilterEventWatcher *) call; |
| QDBusPendingReply<bool> reply = *call; |
| |
| if (reply.isError()) { |
| call->deleteLater(); |
| return; |
| } |
| |
| // Use watcher's window instead of the current focused window |
| // since there is a time lag until filterEventFinished() returns. |
| QWindow *window = watcher->window(); |
| |
| if (!window) { |
| call->deleteLater(); |
| return; |
| } |
| |
| Qt::KeyboardModifiers modifiers = watcher->modifiers(); |
| QVariantList args = watcher->arguments(); |
| const ulong time = static_cast<ulong>(args.at(0).toUInt()); |
| const QEvent::Type type = static_cast<QEvent::Type>(args.at(1).toUInt()); |
| const int qtcode = args.at(2).toInt(); |
| const quint32 code = args.at(3).toUInt(); |
| const quint32 sym = args.at(4).toUInt(); |
| const quint32 state = args.at(5).toUInt(); |
| const QString string = args.at(6).toString(); |
| const bool isAutoRepeat = args.at(7).toBool(); |
| |
| // copied from QXcbKeyboard::handleKeyEvent() |
| bool filtered = reply.value(); |
| qCDebug(qtQpaInputMethods) << "filterEventFinished return" << code << sym << state << filtered; |
| if (!filtered) { |
| #ifndef QT_NO_CONTEXTMENU |
| if (type == QEvent::KeyPress && qtcode == Qt::Key_Menu |
| && window != NULL) { |
| const QPoint globalPos = window->screen()->handle()->cursor()->pos(); |
| const QPoint pos = window->mapFromGlobal(globalPos); |
| QWindowSystemInterfacePrivate::ContextMenuEvent contextMenuEvent(window, false, pos, |
| globalPos, modifiers); |
| QGuiApplicationPrivate::processWindowSystemEvent(&contextMenuEvent); |
| } |
| #endif |
| QWindowSystemInterfacePrivate::KeyEvent keyEvent(window, time, type, qtcode, modifiers, |
| code, sym, state, string, isAutoRepeat); |
| QGuiApplicationPrivate::processWindowSystemEvent(&keyEvent); |
| } |
| call->deleteLater(); |
| } |
| |
| QLocale QIBusPlatformInputContext::locale() const |
| { |
| // d->locale is not updated when IBus portal is used |
| if (d->usePortal) |
| return QPlatformInputContext::locale(); |
| return d->locale; |
| } |
| |
| void QIBusPlatformInputContext::socketChanged(const QString &str) |
| { |
| qCDebug(qtQpaInputMethods) << "socketChanged"; |
| Q_UNUSED (str); |
| |
| m_timer.stop(); |
| |
| if (d->context) |
| disconnect(d->context); |
| if (d->bus && d->bus->isValid()) |
| disconnect(d->bus); |
| if (d->connection) |
| d->connection->disconnectFromBus(QLatin1String("QIBusProxy")); |
| |
| m_timer.start(100); |
| } |
| |
| void QIBusPlatformInputContext::busRegistered(const QString &str) |
| { |
| qCDebug(qtQpaInputMethods) << "busRegistered"; |
| Q_UNUSED (str); |
| if (d->usePortal) { |
| connectToBus(); |
| } |
| } |
| |
| void QIBusPlatformInputContext::busUnregistered(const QString &str) |
| { |
| qCDebug(qtQpaInputMethods) << "busUnregistered"; |
| Q_UNUSED (str); |
| d->busConnected = false; |
| } |
| |
| // When getSocketPath() is modified, the bus is not established yet |
| // so use m_timer. |
| void QIBusPlatformInputContext::connectToBus() |
| { |
| qCDebug(qtQpaInputMethods) << "QIBusPlatformInputContext::connectToBus"; |
| d->initBus(); |
| connectToContextSignals(); |
| |
| #if QT_CONFIG(filesystemwatcher) |
| if (!d->usePortal && m_socketWatcher.files().size() == 0) |
| m_socketWatcher.addPath(QIBusPlatformInputContextPrivate::getSocketPath()); |
| #endif |
| } |
| |
| void QIBusPlatformInputContext::globalEngineChanged(const QString &engine_name) |
| { |
| if (!d->bus || !d->bus->isValid()) |
| return; |
| |
| QIBusEngineDesc desc = d->bus->getGlobalEngine(); |
| Q_ASSERT(engine_name == desc.engine_name); |
| QLocale locale(desc.language); |
| if (d->locale != locale) { |
| d->locale = locale; |
| emitLocaleChanged(); |
| } |
| } |
| |
| void QIBusPlatformInputContext::connectToContextSignals() |
| { |
| if (d->bus && d->bus->isValid()) { |
| connect(d->bus, SIGNAL(GlobalEngineChanged(QString)), this, SLOT(globalEngineChanged(QString))); |
| } |
| |
| if (d->context) { |
| connect(d->context, SIGNAL(CommitText(QDBusVariant)), SLOT(commitText(QDBusVariant))); |
| connect(d->context, SIGNAL(UpdatePreeditText(QDBusVariant,uint,bool)), this, SLOT(updatePreeditText(QDBusVariant,uint,bool))); |
| connect(d->context, SIGNAL(ForwardKeyEvent(uint,uint,uint)), this, SLOT(forwardKeyEvent(uint,uint,uint))); |
| connect(d->context, SIGNAL(DeleteSurroundingText(int,uint)), this, SLOT(deleteSurroundingText(int,uint))); |
| connect(d->context, SIGNAL(RequireSurroundingText()), this, SLOT(surroundingTextRequired())); |
| connect(d->context, SIGNAL(HidePreeditText()), this, SLOT(hidePreeditText())); |
| connect(d->context, SIGNAL(ShowPreeditText()), this, SLOT(showPreeditText())); |
| } |
| } |
| |
| static inline bool checkRunningUnderFlatpak() |
| { |
| return !QStandardPaths::locate(QStandardPaths::RuntimeLocation, QLatin1String("flatpak-info")).isEmpty(); |
| } |
| |
| static bool shouldConnectIbusPortal() |
| { |
| // honor the same env as ibus-gtk |
| return (checkRunningUnderFlatpak() || !qgetenv("IBUS_USE_PORTAL").isNull()); |
| } |
| |
| QIBusPlatformInputContextPrivate::QIBusPlatformInputContextPrivate() |
| : connection(0), |
| bus(0), |
| portalBus(0), |
| context(0), |
| usePortal(shouldConnectIbusPortal()), |
| valid(false), |
| busConnected(false), |
| needsSurroundingText(false) |
| { |
| if (usePortal) { |
| valid = true; |
| if (debug) |
| qDebug() << "use IBus portal"; |
| } else { |
| valid = !QStandardPaths::findExecutable(QString::fromLocal8Bit("ibus-daemon"), QStringList()).isEmpty(); |
| } |
| if (!valid) |
| return; |
| initBus(); |
| |
| if (bus && bus->isValid()) { |
| QIBusEngineDesc desc = bus->getGlobalEngine(); |
| locale = QLocale(desc.language); |
| } |
| } |
| |
| void QIBusPlatformInputContextPrivate::initBus() |
| { |
| connection = createConnection(); |
| busConnected = false; |
| createBusProxy(); |
| } |
| |
| void QIBusPlatformInputContextPrivate::createBusProxy() |
| { |
| if (!connection || !connection->isConnected()) |
| return; |
| |
| const char* ibusService = usePortal ? "org.freedesktop.portal.IBus" : "org.freedesktop.IBus"; |
| QDBusReply<QDBusObjectPath> ic; |
| if (usePortal) { |
| portalBus = new QIBusProxyPortal(QLatin1String(ibusService), |
| QLatin1String("/org/freedesktop/IBus"), |
| *connection); |
| if (!portalBus->isValid()) { |
| qWarning("QIBusPlatformInputContext: invalid portal bus."); |
| return; |
| } |
| |
| ic = portalBus->CreateInputContext(QLatin1String("QIBusInputContext")); |
| } else { |
| bus = new QIBusProxy(QLatin1String(ibusService), |
| QLatin1String("/org/freedesktop/IBus"), |
| *connection); |
| if (!bus->isValid()) { |
| qWarning("QIBusPlatformInputContext: invalid bus."); |
| return; |
| } |
| |
| ic = bus->CreateInputContext(QLatin1String("QIBusInputContext")); |
| } |
| |
| serviceWatcher.removeWatchedService(ibusService); |
| serviceWatcher.setConnection(*connection); |
| serviceWatcher.addWatchedService(ibusService); |
| |
| if (!ic.isValid()) { |
| qWarning("QIBusPlatformInputContext: CreateInputContext failed."); |
| return; |
| } |
| |
| context = new QIBusInputContextProxy(QLatin1String(ibusService), ic.value().path(), *connection); |
| |
| if (!context->isValid()) { |
| qWarning("QIBusPlatformInputContext: invalid input context."); |
| return; |
| } |
| |
| enum Capabilities { |
| IBUS_CAP_PREEDIT_TEXT = 1 << 0, |
| IBUS_CAP_AUXILIARY_TEXT = 1 << 1, |
| IBUS_CAP_LOOKUP_TABLE = 1 << 2, |
| IBUS_CAP_FOCUS = 1 << 3, |
| IBUS_CAP_PROPERTY = 1 << 4, |
| IBUS_CAP_SURROUNDING_TEXT = 1 << 5 |
| }; |
| context->SetCapabilities(IBUS_CAP_PREEDIT_TEXT|IBUS_CAP_FOCUS|IBUS_CAP_SURROUNDING_TEXT); |
| |
| if (debug) |
| qDebug(">>>> bus connected!"); |
| busConnected = true; |
| } |
| |
| QString QIBusPlatformInputContextPrivate::getSocketPath() |
| { |
| QByteArray display(qgetenv("DISPLAY")); |
| QByteArray host = "unix"; |
| QByteArray displayNumber = "0"; |
| |
| int pos = display.indexOf(':'); |
| if (pos > 0) |
| host = display.left(pos); |
| ++pos; |
| int pos2 = display.indexOf('.', pos); |
| if (pos2 > 0) |
| displayNumber = display.mid(pos, pos2 - pos); |
| else |
| displayNumber = display.mid(pos); |
| if (debug) |
| qDebug() << "host=" << host << "displayNumber" << displayNumber; |
| |
| return QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + |
| QLatin1String("/ibus/bus/") + |
| QLatin1String(QDBusConnection::localMachineId()) + |
| QLatin1Char('-') + QString::fromLocal8Bit(host) + QLatin1Char('-') + QString::fromLocal8Bit(displayNumber); |
| } |
| |
| QDBusConnection *QIBusPlatformInputContextPrivate::createConnection() |
| { |
| if (usePortal) |
| return new QDBusConnection(QDBusConnection::connectToBus(QDBusConnection::SessionBus, QLatin1String("QIBusProxy"))); |
| QFile file(getSocketPath()); |
| |
| if (!file.open(QFile::ReadOnly)) |
| return 0; |
| |
| QByteArray address; |
| int pid = -1; |
| |
| while (!file.atEnd()) { |
| QByteArray line = file.readLine().trimmed(); |
| if (line.startsWith('#')) |
| continue; |
| |
| if (line.startsWith("IBUS_ADDRESS=")) |
| address = line.mid(sizeof("IBUS_ADDRESS=") - 1); |
| if (line.startsWith("IBUS_DAEMON_PID=")) |
| pid = line.mid(sizeof("IBUS_DAEMON_PID=") - 1).toInt(); |
| } |
| |
| if (debug) |
| qDebug() << "IBUS_ADDRESS=" << address << "PID=" << pid; |
| if (address.isEmpty() || pid < 0 || kill(pid, 0) != 0) |
| return 0; |
| |
| return new QDBusConnection(QDBusConnection::connectToBus(QString::fromLatin1(address), QLatin1String("QIBusProxy"))); |
| } |
| |
| QT_END_NAMESPACE |