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