| 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 | |
| 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(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 | |
| 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(d->predit); |
| 207 | QCoreApplication::sendEvent(input, &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(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 | |
| 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 | |
| 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>(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 | |
| 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 | |
| 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 | |
| 367 | void QIBusPlatformInputContext::surroundingTextRequired() |
| 368 | { |
| 369 | if (debug) |
| 370 | qDebug("surroundingTextRequired" ); |
| 371 | d->needsSurroundingText = true; |
| 372 | update(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("" , offset, n_chars); |
| 386 | QCoreApplication::sendEvent(input, &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(input, &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(input, &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(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 | |
| 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 (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 | |
| 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(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 | |
| 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(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(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 | |
| 608 | static inline bool checkRunningUnderFlatpak() |
| 609 | { |
| 610 | return !QStandardPaths::locate(QStandardPaths::RuntimeLocation, QLatin1String("flatpak-info" )).isEmpty(); |
| 611 | } |
| 612 | |
| 613 | static bool shouldConnectIbusPortal() |
| 614 | { |
| 615 | // honor the same env as ibus-gtk |
| 616 | return (checkRunningUnderFlatpak() || !qgetenv("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(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 | |
| 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("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 | |
| 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 | |
| 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 | |