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