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 (qEnvironmentVariableIsSet(varName: "IBUS_ADDRESS_FILE" )) { |
724 | QByteArray path = qgetenv(varName: "IBUS_ADDRESS_FILE" ); |
725 | return QString::fromLocal8Bit(ba: path); |
726 | } else if (qEnvironmentVariableIsSet(varName: "WAYLAND_DISPLAY" )) { |
727 | display = qgetenv(varName: "WAYLAND_DISPLAY" ); |
728 | isWayland = true; |
729 | } else { |
730 | display = qgetenv(varName: "DISPLAY" ); |
731 | } |
732 | QByteArray host = "unix" ; |
733 | |
734 | if (isWayland) { |
735 | displayNumber = display; |
736 | } else { |
737 | int pos = display.indexOf(ch: ':'); |
738 | if (pos > 0) |
739 | host = display.left(n: pos); |
740 | ++pos; |
741 | int pos2 = display.indexOf(ch: '.', from: pos); |
742 | if (pos2 > 0) |
743 | displayNumber = display.mid(index: pos, len: pos2 - pos); |
744 | else |
745 | displayNumber = display.mid(index: pos); |
746 | } |
747 | |
748 | qCDebug(lcQpaInputMethods) << "host=" << host << "displayNumber" << displayNumber; |
749 | |
750 | return QStandardPaths::writableLocation(type: QStandardPaths::ConfigLocation) + |
751 | "/ibus/bus/"_L1 + |
752 | QLatin1StringView(QDBusConnection::localMachineId()) + |
753 | u'-' + QString::fromLocal8Bit(ba: host) + u'-' + QString::fromLocal8Bit(ba: displayNumber); |
754 | } |
755 | |
756 | void QIBusPlatformInputContextPrivate::createConnection() |
757 | { |
758 | if (usePortal) { |
759 | QDBusConnection::connectToBus(type: QDBusConnection::SessionBus, name: "QIBusProxy"_L1 ); |
760 | return; |
761 | } |
762 | |
763 | QFile file(getSocketPath()); |
764 | if (!file.open(flags: QFile::ReadOnly)) |
765 | return; |
766 | |
767 | QByteArray address; |
768 | int pid = -1; |
769 | |
770 | while (!file.atEnd()) { |
771 | QByteArray line = file.readLine().trimmed(); |
772 | if (line.startsWith(c: '#')) |
773 | continue; |
774 | |
775 | if (line.startsWith(bv: "IBUS_ADDRESS=" )) |
776 | address = line.mid(index: sizeof("IBUS_ADDRESS=" ) - 1); |
777 | if (line.startsWith(bv: "IBUS_DAEMON_PID=" )) |
778 | pid = line.mid(index: sizeof("IBUS_DAEMON_PID=" ) - 1).toInt(); |
779 | } |
780 | |
781 | qCDebug(lcQpaInputMethods) << "IBUS_ADDRESS=" << address << "PID=" << pid; |
782 | if (address.isEmpty() || pid < 0 || kill(pid: pid, sig: 0) != 0) |
783 | return; |
784 | |
785 | QDBusConnection::connectToBus(address: QString::fromLatin1(ba: address), name: "QIBusProxy"_L1 ); |
786 | } |
787 | |
788 | QT_END_NAMESPACE |
789 | |
790 | #include "moc_qibusplatforminputcontext.cpp" |
791 | |