| 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(); | 
| 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 |  | 
| 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(flags: 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(file: socketPath); | 
| 125 |             connect(sender: &m_socketWatcher, SIGNAL(fileChanged(QString)), receiver: this, SLOT(socketChanged(QString))); | 
| 126 | #endif | 
| 127 |         } | 
| 128 |         m_timer.setSingleShot(true); | 
| 129 |         connect(sender: &m_timer, SIGNAL(timeout()), receiver: this, SLOT(connectToBus())); | 
| 130 |     } | 
| 131 |  | 
| 132 |     QObject::connect(sender: &d->serviceWatcher, SIGNAL(serviceRegistered(QString)), receiver: this, SLOT(busRegistered(QString))); | 
| 133 |     QObject::connect(sender: &d->serviceWatcher, SIGNAL(serviceUnregistered(QString)), receiver: this, SLOT(busUnregistered(QString))); | 
| 134 |  | 
| 135 |     connectToContextSignals(); | 
| 136 |  | 
| 137 |     QInputMethod *p = qApp->inputMethod(); | 
| 138 |     connect(sender: p, SIGNAL(cursorRectangleChanged()), receiver: this, SLOT(cursorRectChanged())); | 
| 139 |     m_eventFilterUseSynchronousMode = false; | 
| 140 |     if (qEnvironmentVariableIsSet(varName: "IBUS_ENABLE_SYNC_MODE" )) { | 
| 141 |         bool ok; | 
| 142 |         int enableSync = qEnvironmentVariableIntValue(varName: "IBUS_ENABLE_SYNC_MODE" , ok: &ok); | 
| 143 |         if (ok && enableSync == 1) | 
| 144 |             m_eventFilterUseSynchronousMode = true; | 
| 145 |     } | 
| 146 | } | 
| 147 |  | 
| 148 | QIBusPlatformInputContext::~QIBusPlatformInputContext (void) | 
| 149 | { | 
| 150 |     delete d; | 
| 151 | } | 
| 152 |  | 
| 153 | bool QIBusPlatformInputContext::isValid() const | 
| 154 | { | 
| 155 |     return d->valid && d->busConnected; | 
| 156 | } | 
| 157 |  | 
| 158 | 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 |  | 
| 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 |  | 
| 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 |  | 
| 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(commitString: d->predit); | 
| 207 |         QCoreApplication::sendEvent(receiver: input, event: &event); | 
| 208 |     } | 
| 209 |  | 
| 210 |     d->context->Reset(); | 
| 211 |     d->predit = QString(); | 
| 212 |     d->attributes.clear(); | 
| 213 | } | 
| 214 |  | 
| 215 |  | 
| 216 | void QIBusPlatformInputContext::update(Qt::InputMethodQueries q) | 
| 217 | { | 
| 218 |     QObject *input = qApp->focusObject(); | 
| 219 |  | 
| 220 |     if (d->needsSurroundingText && input | 
| 221 |             && (q.testFlag(flag: Qt::ImSurroundingText) | 
| 222 |                 || q.testFlag(flag: Qt::ImCursorPosition) | 
| 223 |                 || q.testFlag(flag: Qt::ImAnchorPosition))) { | 
| 224 |  | 
| 225 |         QInputMethodQueryEvent query(Qt::ImSurroundingText | Qt::ImCursorPosition | Qt::ImAnchorPosition); | 
| 226 |  | 
| 227 |         QCoreApplication::sendEvent(receiver: input, event: &query); | 
| 228 |  | 
| 229 |         QString surroundingText = query.value(query: Qt::ImSurroundingText).toString(); | 
| 230 |         uint cursorPosition = query.value(query: Qt::ImCursorPosition).toUInt(); | 
| 231 |         uint anchorPosition = query.value(query: 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(text: dbusText, cursor_pos: cursorPosition, anchor_pos: anchorPosition); | 
| 241 |     } | 
| 242 |     QPlatformInputContext::update(q); | 
| 243 | } | 
| 244 |  | 
| 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(p: inputWindow->mapToGlobal(pos: r.topLeft())); | 
| 258 |     if (debug) | 
| 259 |         qDebug() << "microFocus"  << r; | 
| 260 |     d->context->SetCursorLocation(x: r.x(), y: r.y(), w: r.width(), h: r.height()); | 
| 261 | } | 
| 262 |  | 
| 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 |  | 
| 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>(v: 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(commitString: t.text); | 
| 299 |     QCoreApplication::sendEvent(receiver: input, event: &event); | 
| 300 |  | 
| 301 |     d->predit = QString(); | 
| 302 |     d->attributes.clear(); | 
| 303 | } | 
| 304 |  | 
| 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>(v: 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(receiver: input, event: &event); | 
| 327 |  | 
| 328 |     d->predit = t.text; | 
| 329 | } | 
| 330 |  | 
| 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(keysym: keyval, modifiers); | 
| 358 |     QString text = QXkbCommon::lookupStringNoKeysymTransformations(keysym: 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(receiver: input, event: &event); | 
| 365 | } | 
| 366 |  | 
| 367 | void QIBusPlatformInputContext::surroundingTextRequired() | 
| 368 | { | 
| 369 |     if (debug) | 
| 370 |         qDebug(msg: "surroundingTextRequired" ); | 
| 371 |     d->needsSurroundingText = true; | 
| 372 |     update(q: Qt::ImSurroundingText); | 
| 373 | } | 
| 374 |  | 
| 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(commitString: "" , replaceFrom: offset, replaceLength: n_chars); | 
| 386 |     QCoreApplication::sendEvent(receiver: input, event: &event); | 
| 387 | } | 
| 388 |  | 
| 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(receiver: input, event: &event); | 
| 398 | } | 
| 399 |  | 
| 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(receiver: input, event: &event); | 
| 408 | } | 
| 409 |  | 
| 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(keyval: sym, keycode: code - 8, state: 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(value: keyEvent->timestamp()); | 
| 459 |     args << QVariant::fromValue(value: static_cast<uint>(keyEvent->type())); | 
| 460 |     args << QVariant::fromValue(value: qtcode); | 
| 461 |     args << QVariant::fromValue(value: code) << QVariant::fromValue(value: sym) << QVariant::fromValue(value: state); | 
| 462 |     args << QVariant::fromValue(value: keyEvent->text()); | 
| 463 |     args << QVariant::fromValue(value: keyEvent->isAutoRepeat()); | 
| 464 |  | 
| 465 |     QIBusFilterEventWatcher *watcher = new QIBusFilterEventWatcher(reply, this, QGuiApplication::focusWindow(), modifiers, args); | 
| 466 |     QObject::connect(sender: watcher, signal: &QDBusPendingCallWatcher::finished, receiver: this, slot: &QIBusPlatformInputContext::filterEventFinished); | 
| 467 |  | 
| 468 |     return true; | 
| 469 | } | 
| 470 |  | 
| 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(i: 0).toUInt()); | 
| 493 |     const QEvent::Type type = static_cast<QEvent::Type>(args.at(i: 1).toUInt()); | 
| 494 |     const int qtcode = args.at(i: 2).toInt(); | 
| 495 |     const quint32 code = args.at(i: 3).toUInt(); | 
| 496 |     const quint32 sym = args.at(i: 4).toUInt(); | 
| 497 |     const quint32 state = args.at(i: 5).toUInt(); | 
| 498 |     const QString string = args.at(i: 6).toString(); | 
| 499 |     const bool isAutoRepeat = args.at(i: 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(pos: globalPos); | 
| 510 |             QWindowSystemInterfacePrivate::ContextMenuEvent (window, false, pos, | 
| 511 |                                                                              globalPos, modifiers); | 
| 512 |             QGuiApplicationPrivate::processWindowSystemEvent(e: &contextMenuEvent); | 
| 513 |         } | 
| 514 | #endif | 
| 515 |         QWindowSystemInterfacePrivate::KeyEvent keyEvent(window, time, type, qtcode, modifiers, | 
| 516 |                                                          code, sym, state, string, isAutoRepeat); | 
| 517 |         QGuiApplicationPrivate::processWindowSystemEvent(e: &keyEvent); | 
| 518 |     } | 
| 519 |     call->deleteLater(); | 
| 520 | } | 
| 521 |  | 
| 522 | 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 |  | 
| 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(receiver: d->context); | 
| 539 |     if (d->bus && d->bus->isValid()) | 
| 540 |         disconnect(receiver: d->bus); | 
| 541 |     if (d->connection) | 
| 542 |         d->connection->disconnectFromBus(name: QLatin1String("QIBusProxy" )); | 
| 543 |  | 
| 544 |     m_timer.start(msec: 100); | 
| 545 | } | 
| 546 |  | 
| 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 |  | 
| 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. | 
| 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(file: QIBusPlatformInputContextPrivate::getSocketPath()); | 
| 574 | #endif | 
| 575 | } | 
| 576 |  | 
| 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 |  | 
| 591 | void QIBusPlatformInputContext::connectToContextSignals() | 
| 592 | { | 
| 593 |     if (d->bus && d->bus->isValid()) { | 
| 594 |         connect(sender: d->bus, SIGNAL(GlobalEngineChanged(QString)), receiver: this, SLOT(globalEngineChanged(QString))); | 
| 595 |     } | 
| 596 |  | 
| 597 |     if (d->context) { | 
| 598 |         connect(asender: d->context, SIGNAL(CommitText(QDBusVariant)), SLOT(commitText(QDBusVariant))); | 
| 599 |         connect(sender: d->context, SIGNAL(UpdatePreeditText(QDBusVariant,uint,bool)), receiver: this, SLOT(updatePreeditText(QDBusVariant,uint,bool))); | 
| 600 |         connect(sender: d->context, SIGNAL(ForwardKeyEvent(uint,uint,uint)), receiver: this, SLOT(forwardKeyEvent(uint,uint,uint))); | 
| 601 |         connect(sender: d->context, SIGNAL(DeleteSurroundingText(int,uint)), receiver: this, SLOT(deleteSurroundingText(int,uint))); | 
| 602 |         connect(sender: d->context, SIGNAL(RequireSurroundingText()), receiver: this, SLOT(surroundingTextRequired())); | 
| 603 |         connect(sender: d->context, SIGNAL(HidePreeditText()), receiver: this, SLOT(hidePreeditText())); | 
| 604 |         connect(sender: d->context, SIGNAL(ShowPreeditText()), receiver: this, SLOT(showPreeditText())); | 
| 605 |     } | 
| 606 | } | 
| 607 |  | 
| 608 | static inline bool checkRunningUnderFlatpak() | 
| 609 | { | 
| 610 |     return !QStandardPaths::locate(type: QStandardPaths::RuntimeLocation, fileName: QLatin1String("flatpak-info" )).isEmpty(); | 
| 611 | } | 
| 612 |  | 
| 613 | static bool shouldConnectIbusPortal() | 
| 614 | { | 
| 615 |     // honor the same env as ibus-gtk | 
| 616 |     return (checkRunningUnderFlatpak() || !qgetenv(varName: "IBUS_USE_PORTAL" ).isNull()); | 
| 617 | } | 
| 618 |  | 
| 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(executableName: QString::fromLocal8Bit(str: "ibus-daemon" ), paths: 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 |  | 
| 646 | void QIBusPlatformInputContextPrivate::initBus() | 
| 647 | { | 
| 648 |     connection = createConnection(); | 
| 649 |     busConnected = false; | 
| 650 |     createBusProxy(); | 
| 651 | } | 
| 652 |  | 
| 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(msg: "QIBusPlatformInputContext: invalid portal bus." ); | 
| 666 |             return; | 
| 667 |         } | 
| 668 |  | 
| 669 |         ic = portalBus->CreateInputContext(name: QLatin1String("QIBusInputContext" )); | 
| 670 |     } else { | 
| 671 |         bus = new QIBusProxy(QLatin1String(ibusService), | 
| 672 |                              QLatin1String("/org/freedesktop/IBus" ), | 
| 673 |                              *connection); | 
| 674 |         if (!bus->isValid()) { | 
| 675 |             qWarning(msg: "QIBusPlatformInputContext: invalid bus." ); | 
| 676 |             return; | 
| 677 |         } | 
| 678 |  | 
| 679 |         ic = bus->CreateInputContext(name: QLatin1String("QIBusInputContext" )); | 
| 680 |     } | 
| 681 |  | 
| 682 |     serviceWatcher.removeWatchedService(service: ibusService); | 
| 683 |     serviceWatcher.setConnection(*connection); | 
| 684 |     serviceWatcher.addWatchedService(newService: ibusService); | 
| 685 |  | 
| 686 |     if (!ic.isValid()) { | 
| 687 |         qWarning(msg: "QIBusPlatformInputContext: CreateInputContext failed." ); | 
| 688 |         return; | 
| 689 |     } | 
| 690 |  | 
| 691 |     context = new QIBusInputContextProxy(QLatin1String(ibusService), ic.value().path(), *connection); | 
| 692 |  | 
| 693 |     if (!context->isValid()) { | 
| 694 |         qWarning(msg: "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(msg: ">>>> bus connected!" ); | 
| 710 |     busConnected = true; | 
| 711 | } | 
| 712 |  | 
| 713 | QString QIBusPlatformInputContextPrivate::getSocketPath() | 
| 714 | { | 
| 715 |     QByteArray display; | 
| 716 |     QByteArray displayNumber = "0" ; | 
| 717 |     bool isWayland = false; | 
| 718 |  | 
| 719 |     if (qEnvironmentVariableIsSet(varName: "IBUS_ADDRESS_FILE" )) { | 
| 720 |         QByteArray path = qgetenv(varName: "IBUS_ADDRESS_FILE" ); | 
| 721 |         return QString::fromLocal8Bit(str: path); | 
| 722 |     } else  if (qEnvironmentVariableIsSet(varName: "WAYLAND_DISPLAY" )) { | 
| 723 |         display = qgetenv(varName: "WAYLAND_DISPLAY" ); | 
| 724 |         isWayland = true; | 
| 725 |     } else { | 
| 726 |         display = qgetenv(varName: "DISPLAY" ); | 
| 727 |     } | 
| 728 |     QByteArray host = "unix" ; | 
| 729 |  | 
| 730 |     if (isWayland) { | 
| 731 |         displayNumber = display; | 
| 732 |     } else { | 
| 733 |         int pos = display.indexOf(c: ':'); | 
| 734 |         if (pos > 0) | 
| 735 |             host = display.left(len: pos); | 
| 736 |         ++pos; | 
| 737 |         int pos2 = display.indexOf(c: '.', from: pos); | 
| 738 |         if (pos2 > 0) | 
| 739 |             displayNumber = display.mid(index: pos, len: pos2 - pos); | 
| 740 |          else | 
| 741 |             displayNumber = display.mid(index: pos); | 
| 742 |     } | 
| 743 |  | 
| 744 |     if (debug) | 
| 745 |         qDebug() << "host="  << host << "displayNumber"  << displayNumber; | 
| 746 |  | 
| 747 |     return QStandardPaths::writableLocation(type: QStandardPaths::ConfigLocation) + | 
| 748 |                QLatin1String("/ibus/bus/" ) + | 
| 749 |                QLatin1String(QDBusConnection::localMachineId()) + | 
| 750 |                QLatin1Char('-') + QString::fromLocal8Bit(str: host) + QLatin1Char('-') + QString::fromLocal8Bit(str: displayNumber); | 
| 751 | } | 
| 752 |  | 
| 753 | QDBusConnection *QIBusPlatformInputContextPrivate::createConnection() | 
| 754 | { | 
| 755 |     if (usePortal) | 
| 756 |         return new QDBusConnection(QDBusConnection::connectToBus(type: QDBusConnection::SessionBus, name: QLatin1String("QIBusProxy" ))); | 
| 757 |     QFile file(getSocketPath()); | 
| 758 |  | 
| 759 |     if (!file.open(flags: 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(c: '#')) | 
| 768 |             continue; | 
| 769 |  | 
| 770 |         if (line.startsWith(c: "IBUS_ADDRESS=" )) | 
| 771 |             address = line.mid(index: sizeof("IBUS_ADDRESS=" ) - 1); | 
| 772 |         if (line.startsWith(c: "IBUS_DAEMON_PID=" )) | 
| 773 |             pid = line.mid(index: 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: pid, sig: 0) != 0) | 
| 779 |         return 0; | 
| 780 |  | 
| 781 |     return new QDBusConnection(QDBusConnection::connectToBus(address: QString::fromLatin1(str: address), name: QLatin1String("QIBusProxy" ))); | 
| 782 | } | 
| 783 |  | 
| 784 | QT_END_NAMESPACE | 
| 785 |  |