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