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 | enum { debug = 0 }; |
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 | if (debug) |
251 | qDebug() << "microFocus"<< newRect; |
252 | d->context->SetCursorLocationRelative(x: newRect.x(), y: newRect.y(), |
253 | w: newRect.width(), h: newRect.height()); |
254 | return; |
255 | } |
256 | |
257 | // x11/xcb |
258 | auto screenGeometry = inputWindow->screen()->geometry(); |
259 | auto point = inputWindow->mapToGlobal(pos: r.topLeft()); |
260 | qreal scale = inputWindow->devicePixelRatio(); |
261 | auto native = (point - screenGeometry.topLeft()) * scale + screenGeometry.topLeft(); |
262 | QRect newRect(native, r.size() * scale); |
263 | if (debug) |
264 | qDebug() << "microFocus"<< newRect; |
265 | d->context->SetCursorLocation(x: newRect.x(), y: newRect.y(), |
266 | w: newRect.width(), h: newRect.height()); |
267 | } |
268 | |
269 | void QIBusPlatformInputContext::setFocusObject(QObject *object) |
270 | { |
271 | if (!d->busConnected) |
272 | return; |
273 | |
274 | // It would seem natural here to call FocusOut() on the input method if we |
275 | // transition from an IME accepted focus object to one that does not accept it. |
276 | // Mysteriously however that is not sufficient to fix bug QTBUG-63066. |
277 | if (object && !inputMethodAccepted()) |
278 | return; |
279 | |
280 | if (debug) |
281 | qDebug() << "setFocusObject"<< object; |
282 | if (object) |
283 | d->context->FocusIn(); |
284 | else |
285 | d->context->FocusOut(); |
286 | } |
287 | |
288 | void QIBusPlatformInputContext::commitText(const QDBusVariant &text) |
289 | { |
290 | QObject *input = qApp->focusObject(); |
291 | if (!input) |
292 | return; |
293 | |
294 | const QDBusArgument arg = qvariant_cast<QDBusArgument>(v: text.variant()); |
295 | |
296 | QIBusText t; |
297 | if (debug) |
298 | qDebug() << arg.currentSignature(); |
299 | arg >> t; |
300 | if (debug) |
301 | qDebug() << "commit text:"<< t.text; |
302 | |
303 | QInputMethodEvent event; |
304 | event.setCommitString(commitString: t.text); |
305 | QCoreApplication::sendEvent(receiver: input, event: &event); |
306 | |
307 | d->predit = QString(); |
308 | d->attributes.clear(); |
309 | } |
310 | |
311 | void QIBusPlatformInputContext::updatePreeditText(const QDBusVariant &text, uint cursorPos, bool visible) |
312 | { |
313 | if (!qApp) |
314 | return; |
315 | |
316 | QObject *input = qApp->focusObject(); |
317 | if (!input) |
318 | return; |
319 | |
320 | const QDBusArgument arg = qvariant_cast<QDBusArgument>(v: text.variant()); |
321 | |
322 | QIBusText t; |
323 | arg >> t; |
324 | if (debug) |
325 | qDebug() << "preedit text:"<< t.text; |
326 | |
327 | d->attributes = t.attributes.imAttributes(); |
328 | if (!t.text.isEmpty()) |
329 | d->attributes += QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, cursorPos, visible ? 1 : 0, QVariant()); |
330 | |
331 | QInputMethodEvent event(t.text, d->attributes); |
332 | QCoreApplication::sendEvent(receiver: input, event: &event); |
333 | |
334 | d->predit = t.text; |
335 | } |
336 | |
337 | void QIBusPlatformInputContext::updatePreeditTextWithMode(const QDBusVariant &text, uint cursorPos, bool visible, uint mode) |
338 | { |
339 | updatePreeditText(text, cursorPos, visible); |
340 | if (mode > 0) |
341 | d->preeditFocusMode = QIBusPlatformInputContextPrivate::PreeditFocusMode::PREEDIT_COMMIT; |
342 | else |
343 | d->preeditFocusMode = QIBusPlatformInputContextPrivate::PreeditFocusMode::PREEDIT_CLEAR; |
344 | } |
345 | |
346 | void QIBusPlatformInputContext::forwardKeyEvent(uint keyval, uint keycode, uint state) |
347 | { |
348 | if (!qApp) |
349 | return; |
350 | |
351 | QObject *input = qApp->focusObject(); |
352 | if (!input) |
353 | return; |
354 | |
355 | QEvent::Type type = QEvent::KeyPress; |
356 | if (state & IBUS_RELEASE_MASK) |
357 | type = QEvent::KeyRelease; |
358 | |
359 | state &= ~IBUS_RELEASE_MASK; |
360 | keycode += 8; |
361 | |
362 | Qt::KeyboardModifiers modifiers = Qt::NoModifier; |
363 | if (state & IBUS_SHIFT_MASK) |
364 | modifiers |= Qt::ShiftModifier; |
365 | if (state & IBUS_CONTROL_MASK) |
366 | modifiers |= Qt::ControlModifier; |
367 | if (state & IBUS_MOD1_MASK) |
368 | modifiers |= Qt::AltModifier; |
369 | if (state & IBUS_META_MASK) |
370 | modifiers |= Qt::MetaModifier; |
371 | |
372 | int qtcode = QXkbCommon::keysymToQtKey(keysym: keyval, modifiers); |
373 | QString text = QXkbCommon::lookupStringNoKeysymTransformations(keysym: keyval); |
374 | |
375 | if (debug) |
376 | qDebug() << "forwardKeyEvent"<< keyval << keycode << state << modifiers << qtcode << text; |
377 | |
378 | QKeyEvent event(type, qtcode, modifiers, keycode, keyval, state, text); |
379 | QCoreApplication::sendEvent(receiver: input, event: &event); |
380 | } |
381 | |
382 | void QIBusPlatformInputContext::surroundingTextRequired() |
383 | { |
384 | if (debug) |
385 | qDebug(msg: "surroundingTextRequired"); |
386 | d->needsSurroundingText = true; |
387 | update(q: Qt::ImSurroundingText); |
388 | } |
389 | |
390 | void QIBusPlatformInputContext::deleteSurroundingText(int offset, uint n_chars) |
391 | { |
392 | QObject *input = qApp->focusObject(); |
393 | if (!input) |
394 | return; |
395 | |
396 | if (debug) |
397 | qDebug() << "deleteSurroundingText"<< offset << n_chars; |
398 | |
399 | QInputMethodEvent event; |
400 | event.setCommitString(commitString: "", replaceFrom: offset, replaceLength: n_chars); |
401 | QCoreApplication::sendEvent(receiver: input, event: &event); |
402 | } |
403 | |
404 | void QIBusPlatformInputContext::hidePreeditText() |
405 | { |
406 | QObject *input = QGuiApplication::focusObject(); |
407 | if (!input) |
408 | return; |
409 | |
410 | QList<QInputMethodEvent::Attribute> attributes; |
411 | QInputMethodEvent event(QString(), attributes); |
412 | QCoreApplication::sendEvent(receiver: input, event: &event); |
413 | } |
414 | |
415 | void QIBusPlatformInputContext::showPreeditText() |
416 | { |
417 | QObject *input = QGuiApplication::focusObject(); |
418 | if (!input) |
419 | return; |
420 | |
421 | QInputMethodEvent event(d->predit, d->attributes); |
422 | QCoreApplication::sendEvent(receiver: input, event: &event); |
423 | } |
424 | |
425 | bool QIBusPlatformInputContext::filterEvent(const QEvent *event) |
426 | { |
427 | if (!d->busConnected) |
428 | return false; |
429 | |
430 | if (!inputMethodAccepted()) |
431 | return false; |
432 | |
433 | const QKeyEvent *keyEvent = static_cast<const QKeyEvent *>(event); |
434 | quint32 sym = keyEvent->nativeVirtualKey(); |
435 | quint32 code = keyEvent->nativeScanCode(); |
436 | quint32 state = keyEvent->nativeModifiers(); |
437 | quint32 ibusState = state; |
438 | |
439 | if (keyEvent->type() != QEvent::KeyPress) |
440 | ibusState |= IBUS_RELEASE_MASK; |
441 | |
442 | QDBusPendingReply<bool> reply = d->context->ProcessKeyEvent(keyval: sym, keycode: code - 8, state: ibusState); |
443 | |
444 | if (m_eventFilterUseSynchronousMode || reply.isFinished()) { |
445 | bool filtered = reply.value(); |
446 | qCDebug(qtQpaInputMethods) << "filterEvent return"<< code << sym << state << filtered; |
447 | return filtered; |
448 | } |
449 | |
450 | Qt::KeyboardModifiers modifiers = keyEvent->modifiers(); |
451 | const int qtcode = keyEvent->key(); |
452 | |
453 | // From QKeyEvent::modifiers() |
454 | switch (qtcode) { |
455 | case Qt::Key_Shift: |
456 | modifiers ^= Qt::ShiftModifier; |
457 | break; |
458 | case Qt::Key_Control: |
459 | modifiers ^= Qt::ControlModifier; |
460 | break; |
461 | case Qt::Key_Alt: |
462 | modifiers ^= Qt::AltModifier; |
463 | break; |
464 | case Qt::Key_Meta: |
465 | modifiers ^= Qt::MetaModifier; |
466 | break; |
467 | case Qt::Key_AltGr: |
468 | modifiers ^= Qt::GroupSwitchModifier; |
469 | break; |
470 | } |
471 | |
472 | QVariantList args; |
473 | args << QVariant::fromValue(value: keyEvent->timestamp()); |
474 | args << QVariant::fromValue(value: static_cast<uint>(keyEvent->type())); |
475 | args << QVariant::fromValue(value: qtcode); |
476 | args << QVariant::fromValue(value: code) << QVariant::fromValue(value: sym) << QVariant::fromValue(value: state); |
477 | args << QVariant::fromValue(value: keyEvent->text()); |
478 | args << QVariant::fromValue(value: keyEvent->isAutoRepeat()); |
479 | |
480 | QIBusFilterEventWatcher *watcher = new QIBusFilterEventWatcher(reply, this, QGuiApplication::focusWindow(), modifiers, args); |
481 | QObject::connect(sender: watcher, signal: &QDBusPendingCallWatcher::finished, context: this, slot: &QIBusPlatformInputContext::filterEventFinished); |
482 | |
483 | return true; |
484 | } |
485 | |
486 | void QIBusPlatformInputContext::filterEventFinished(QDBusPendingCallWatcher *call) |
487 | { |
488 | QIBusFilterEventWatcher *watcher = (QIBusFilterEventWatcher *) call; |
489 | QDBusPendingReply<bool> reply = *call; |
490 | |
491 | if (reply.isError()) { |
492 | call->deleteLater(); |
493 | return; |
494 | } |
495 | |
496 | // Use watcher's window instead of the current focused window |
497 | // since there is a time lag until filterEventFinished() returns. |
498 | QWindow *window = watcher->window(); |
499 | |
500 | if (!window) { |
501 | call->deleteLater(); |
502 | return; |
503 | } |
504 | |
505 | Qt::KeyboardModifiers modifiers = watcher->modifiers(); |
506 | QVariantList args = watcher->arguments(); |
507 | const ulong time = static_cast<ulong>(args.at(i: 0).toUInt()); |
508 | const QEvent::Type type = static_cast<QEvent::Type>(args.at(i: 1).toUInt()); |
509 | const int qtcode = args.at(i: 2).toInt(); |
510 | const quint32 code = args.at(i: 3).toUInt(); |
511 | const quint32 sym = args.at(i: 4).toUInt(); |
512 | const quint32 state = args.at(i: 5).toUInt(); |
513 | const QString string = args.at(i: 6).toString(); |
514 | const bool isAutoRepeat = args.at(i: 7).toBool(); |
515 | |
516 | // copied from QXcbKeyboard::handleKeyEvent() |
517 | bool filtered = reply.value(); |
518 | qCDebug(qtQpaInputMethods) << "filterEventFinished return"<< code << sym << state << filtered; |
519 | if (!filtered) { |
520 | #ifndef QT_NO_CONTEXTMENU |
521 | if (type == QEvent::KeyPress && qtcode == Qt::Key_Menu |
522 | && window != nullptr) { |
523 | const QPoint globalPos = window->screen()->handle()->cursor()->pos(); |
524 | const QPoint pos = window->mapFromGlobal(pos: globalPos); |
525 | QWindowSystemInterfacePrivate::ContextMenuEvent contextMenuEvent(window, false, pos, |
526 | globalPos, modifiers); |
527 | QGuiApplicationPrivate::processWindowSystemEvent(e: &contextMenuEvent); |
528 | } |
529 | #endif |
530 | QWindowSystemInterfacePrivate::KeyEvent keyEvent(window, time, type, qtcode, modifiers, |
531 | code, sym, state, string, isAutoRepeat); |
532 | QGuiApplicationPrivate::processWindowSystemEvent(e: &keyEvent); |
533 | } |
534 | call->deleteLater(); |
535 | } |
536 | |
537 | QLocale QIBusPlatformInputContext::locale() const |
538 | { |
539 | // d->locale is not updated when IBus portal is used |
540 | if (d->usePortal) |
541 | return QPlatformInputContext::locale(); |
542 | return d->locale; |
543 | } |
544 | |
545 | void QIBusPlatformInputContext::socketChanged(const QString &str) |
546 | { |
547 | qCDebug(qtQpaInputMethods) << "socketChanged"; |
548 | Q_UNUSED (str); |
549 | |
550 | m_timer.stop(); |
551 | |
552 | // dereference QDBusConnection to actually disconnect |
553 | d->serviceWatcher.setConnection(QDBusConnection(QString())); |
554 | d->context = nullptr; |
555 | d->bus = nullptr; |
556 | d->busConnected = false; |
557 | QDBusConnection::disconnectFromBus(name: "QIBusProxy"_L1); |
558 | |
559 | m_timer.start(msec: 100); |
560 | } |
561 | |
562 | void QIBusPlatformInputContext::busRegistered(const QString &str) |
563 | { |
564 | qCDebug(qtQpaInputMethods) << "busRegistered"; |
565 | Q_UNUSED (str); |
566 | if (d->usePortal) { |
567 | connectToBus(); |
568 | } |
569 | } |
570 | |
571 | void QIBusPlatformInputContext::busUnregistered(const QString &str) |
572 | { |
573 | qCDebug(qtQpaInputMethods) << "busUnregistered"; |
574 | Q_UNUSED (str); |
575 | d->busConnected = false; |
576 | } |
577 | |
578 | // When getSocketPath() is modified, the bus is not established yet |
579 | // so use m_timer. |
580 | void QIBusPlatformInputContext::connectToBus() |
581 | { |
582 | qCDebug(qtQpaInputMethods) << "QIBusPlatformInputContext::connectToBus"; |
583 | d->initBus(); |
584 | connectToContextSignals(); |
585 | |
586 | #if QT_CONFIG(filesystemwatcher) |
587 | if (!d->usePortal && m_socketWatcher.files().size() == 0) |
588 | m_socketWatcher.addPath(file: QIBusPlatformInputContextPrivate::getSocketPath()); |
589 | #endif |
590 | } |
591 | |
592 | void QIBusPlatformInputContext::globalEngineChanged(const QString &engine_name) |
593 | { |
594 | if (!d->bus || !d->bus->isValid()) |
595 | return; |
596 | |
597 | QIBusEngineDesc desc = d->bus->getGlobalEngine(); |
598 | Q_ASSERT(engine_name == desc.engine_name); |
599 | QLocale locale(desc.language); |
600 | if (d->locale != locale) { |
601 | d->locale = locale; |
602 | emitLocaleChanged(); |
603 | } |
604 | } |
605 | |
606 | void QIBusPlatformInputContext::connectToContextSignals() |
607 | { |
608 | if (d->bus && d->bus->isValid()) { |
609 | connect(sender: d->bus.get(), SIGNAL(GlobalEngineChanged(QString)), receiver: this, SLOT(globalEngineChanged(QString))); |
610 | } |
611 | |
612 | if (d->context) { |
613 | connect(asender: d->context.get(), SIGNAL(CommitText(QDBusVariant)), SLOT(commitText(QDBusVariant))); |
614 | connect(sender: d->context.get(), SIGNAL(UpdatePreeditText(QDBusVariant,uint,bool)), receiver: this, SLOT(updatePreeditText(QDBusVariant,uint,bool))); |
615 | connect(sender: d->context.get(), SIGNAL(UpdatePreeditTextWithMode(QDBusVariant,uint,bool,uint)), receiver: this, SLOT(updatePreeditTextWithMode(QDBusVariant,uint,bool,uint))); |
616 | connect(sender: d->context.get(), SIGNAL(ForwardKeyEvent(uint,uint,uint)), receiver: this, SLOT(forwardKeyEvent(uint,uint,uint))); |
617 | connect(sender: d->context.get(), SIGNAL(DeleteSurroundingText(int,uint)), receiver: this, SLOT(deleteSurroundingText(int,uint))); |
618 | connect(sender: d->context.get(), SIGNAL(RequireSurroundingText()), receiver: this, SLOT(surroundingTextRequired())); |
619 | connect(sender: d->context.get(), SIGNAL(HidePreeditText()), receiver: this, SLOT(hidePreeditText())); |
620 | connect(sender: d->context.get(), SIGNAL(ShowPreeditText()), receiver: this, SLOT(showPreeditText())); |
621 | } |
622 | } |
623 | |
624 | static inline bool checkNeedPortalSupport() |
625 | { |
626 | return QFileInfo::exists(file: "/.flatpak-info"_L1) || qEnvironmentVariableIsSet(varName: "SNAP"); |
627 | } |
628 | |
629 | static bool shouldConnectIbusPortal() |
630 | { |
631 | // honor the same env as ibus-gtk |
632 | return (checkNeedPortalSupport() || qEnvironmentVariableIsSet(varName: "IBUS_USE_PORTAL")); |
633 | } |
634 | |
635 | QIBusPlatformInputContextPrivate::QIBusPlatformInputContextPrivate() |
636 | : usePortal(shouldConnectIbusPortal()), |
637 | valid(false), |
638 | busConnected(false), |
639 | needsSurroundingText(false) |
640 | { |
641 | if (usePortal) { |
642 | valid = true; |
643 | if (debug) |
644 | qDebug() << "use IBus portal"; |
645 | } else { |
646 | valid = !QStandardPaths::findExecutable(executableName: QString::fromLocal8Bit(ba: "ibus-daemon"), paths: QStringList()).isEmpty(); |
647 | } |
648 | if (!valid) |
649 | return; |
650 | initBus(); |
651 | |
652 | if (bus && bus->isValid()) { |
653 | QIBusEngineDesc desc = bus->getGlobalEngine(); |
654 | locale = QLocale(desc.language); |
655 | } |
656 | } |
657 | |
658 | void QIBusPlatformInputContextPrivate::initBus() |
659 | { |
660 | createConnection(); |
661 | busConnected = false; |
662 | createBusProxy(); |
663 | } |
664 | |
665 | void QIBusPlatformInputContextPrivate::createBusProxy() |
666 | { |
667 | QDBusConnection connection("QIBusProxy"_L1); |
668 | if (!connection.isConnected()) |
669 | return; |
670 | |
671 | const char* ibusService = usePortal ? "org.freedesktop.portal.IBus": "org.freedesktop.IBus"; |
672 | QDBusReply<QDBusObjectPath> ic; |
673 | if (usePortal) { |
674 | portalBus = std::make_unique<QIBusProxyPortal>(args: QLatin1StringView(ibusService), |
675 | args: "/org/freedesktop/IBus"_L1, |
676 | args&: connection); |
677 | if (!portalBus->isValid()) { |
678 | qWarning(msg: "QIBusPlatformInputContext: invalid portal bus."); |
679 | return; |
680 | } |
681 | |
682 | ic = portalBus->CreateInputContext(name: "QIBusInputContext"_L1); |
683 | } else { |
684 | bus = std::make_unique<QIBusProxy>(args: QLatin1StringView(ibusService), |
685 | args: "/org/freedesktop/IBus"_L1, |
686 | args&: connection); |
687 | if (!bus->isValid()) { |
688 | qWarning(msg: "QIBusPlatformInputContext: invalid bus."); |
689 | return; |
690 | } |
691 | |
692 | ic = bus->CreateInputContext(name: "QIBusInputContext"_L1); |
693 | } |
694 | |
695 | serviceWatcher.removeWatchedService(service: ibusService); |
696 | serviceWatcher.setConnection(connection); |
697 | serviceWatcher.addWatchedService(newService: ibusService); |
698 | |
699 | if (!ic.isValid()) { |
700 | qWarning(msg: "QIBusPlatformInputContext: CreateInputContext failed."); |
701 | return; |
702 | } |
703 | |
704 | context = std::make_unique<QIBusInputContextProxy>(args: QLatin1StringView(ibusService), args: ic.value().path(), args&: connection); |
705 | |
706 | if (!context->isValid()) { |
707 | qWarning(msg: "QIBusPlatformInputContext: invalid input context."); |
708 | return; |
709 | } |
710 | |
711 | enum Capabilities { |
712 | IBUS_CAP_PREEDIT_TEXT = 1 << 0, |
713 | IBUS_CAP_AUXILIARY_TEXT = 1 << 1, |
714 | IBUS_CAP_LOOKUP_TABLE = 1 << 2, |
715 | IBUS_CAP_FOCUS = 1 << 3, |
716 | IBUS_CAP_PROPERTY = 1 << 4, |
717 | IBUS_CAP_SURROUNDING_TEXT = 1 << 5 |
718 | }; |
719 | context->SetCapabilities(IBUS_CAP_PREEDIT_TEXT|IBUS_CAP_FOCUS|IBUS_CAP_SURROUNDING_TEXT); |
720 | |
721 | context->setClientCommitPreedit(QIBusPropTypeClientCommitPreedit(true)); |
722 | |
723 | if (debug) |
724 | qDebug(msg: ">>>> bus connected!"); |
725 | busConnected = true; |
726 | } |
727 | |
728 | QString QIBusPlatformInputContextPrivate::getSocketPath() |
729 | { |
730 | QByteArray display; |
731 | QByteArray displayNumber = "0"; |
732 | bool isWayland = false; |
733 | |
734 | if (qEnvironmentVariableIsSet(varName: "IBUS_ADDRESS_FILE")) { |
735 | QByteArray path = qgetenv(varName: "IBUS_ADDRESS_FILE"); |
736 | return QString::fromLocal8Bit(ba: path); |
737 | } else if (qEnvironmentVariableIsSet(varName: "WAYLAND_DISPLAY")) { |
738 | display = qgetenv(varName: "WAYLAND_DISPLAY"); |
739 | isWayland = true; |
740 | } else { |
741 | display = qgetenv(varName: "DISPLAY"); |
742 | } |
743 | QByteArray host = "unix"; |
744 | |
745 | if (isWayland) { |
746 | displayNumber = display; |
747 | } else { |
748 | int pos = display.indexOf(ch: ':'); |
749 | if (pos > 0) |
750 | host = display.left(n: pos); |
751 | ++pos; |
752 | int pos2 = display.indexOf(ch: '.', from: pos); |
753 | if (pos2 > 0) |
754 | displayNumber = display.mid(index: pos, len: pos2 - pos); |
755 | else |
756 | displayNumber = display.mid(index: pos); |
757 | } |
758 | |
759 | if (debug) |
760 | qDebug() << "host="<< host << "displayNumber"<< displayNumber; |
761 | |
762 | return QStandardPaths::writableLocation(type: QStandardPaths::ConfigLocation) + |
763 | "/ibus/bus/"_L1+ |
764 | QLatin1StringView(QDBusConnection::localMachineId()) + |
765 | u'-' + QString::fromLocal8Bit(ba: host) + u'-' + QString::fromLocal8Bit(ba: displayNumber); |
766 | } |
767 | |
768 | void QIBusPlatformInputContextPrivate::createConnection() |
769 | { |
770 | if (usePortal) { |
771 | QDBusConnection::connectToBus(type: QDBusConnection::SessionBus, name: "QIBusProxy"_L1); |
772 | return; |
773 | } |
774 | |
775 | QFile file(getSocketPath()); |
776 | if (!file.open(flags: QFile::ReadOnly)) |
777 | return; |
778 | |
779 | QByteArray address; |
780 | int pid = -1; |
781 | |
782 | while (!file.atEnd()) { |
783 | QByteArray line = file.readLine().trimmed(); |
784 | if (line.startsWith(c: '#')) |
785 | continue; |
786 | |
787 | if (line.startsWith(bv: "IBUS_ADDRESS=")) |
788 | address = line.mid(index: sizeof("IBUS_ADDRESS=") - 1); |
789 | if (line.startsWith(bv: "IBUS_DAEMON_PID=")) |
790 | pid = line.mid(index: sizeof("IBUS_DAEMON_PID=") - 1).toInt(); |
791 | } |
792 | |
793 | if (debug) |
794 | qDebug() << "IBUS_ADDRESS="<< address << "PID="<< pid; |
795 | if (address.isEmpty() || pid < 0 || kill(pid: pid, sig: 0) != 0) |
796 | return; |
797 | |
798 | QDBusConnection::connectToBus(address: QString::fromLatin1(ba: address), name: "QIBusProxy"_L1); |
799 | } |
800 | |
801 | QT_END_NAMESPACE |
802 | |
803 | #include "moc_qibusplatforminputcontext.cpp" |
804 |
Definitions
- QIBusPlatformInputContextPrivate
- QIBusPlatformInputContextPrivate
- PreeditFocusMode
- ~QIBusPlatformInputContextPrivate
- QIBusPlatformInputContext
- ~QIBusPlatformInputContext
- isValid
- hasCapability
- invokeAction
- reset
- commit
- update
- cursorRectChanged
- setFocusObject
- commitText
- updatePreeditText
- updatePreeditTextWithMode
- forwardKeyEvent
- surroundingTextRequired
- deleteSurroundingText
- hidePreeditText
- showPreeditText
- filterEvent
- filterEventFinished
- locale
- socketChanged
- busRegistered
- busUnregistered
- connectToBus
- globalEngineChanged
- connectToContextSignals
- checkNeedPortalSupport
- shouldConnectIbusPortal
- QIBusPlatformInputContextPrivate
- initBus
- createBusProxy
- getSocketPath
Learn Advanced QML with KDAB
Find out more