8#include <QTextCharFormat>
9#include <QGuiApplication>
14#include <QStandardPaths>
15#include <QDBusVariant>
16#include <QDBusPendingReply>
18#include <QDBusServiceWatcher>
25#include <qpa/qplatformcursor.h>
26#include <qpa/qplatformscreen.h>
27#include <qpa/qwindowsysteminterface_p.h>
29#include <private/qguiapplication_p.h>
30#include <private/qxkbcommon_p.h>
38#ifndef IBUS_RELEASE_MASK
39#define IBUS_RELEASE_MASK (1
<< 30
)
40#define IBUS_SHIFT_MASK (1
<< 0
)
41#define IBUS_CONTROL_MASK (1
<< 2
)
42#define IBUS_MOD1_MASK (1
<< 3
)
43#define IBUS_META_MASK (1
<< 28
)
48using namespace Qt::StringLiterals;
67 serviceWatcher.setConnection(QDBusConnection(QString()));
71 QDBusConnection::disconnectFromBus(
"QIBusProxy"_L1);
100 QString socketPath = QIBusPlatformInputContextPrivate::getSocketPath();
101 QFile file(socketPath);
102 if (file.open(QFile::ReadOnly)) {
103#if QT_CONFIG(filesystemwatcher)
104 qCDebug(qtQpaInputMethods) <<
"socketWatcher.addPath" << socketPath;
108 m_socketWatcher.addPath(socketPath);
109 connect(&m_socketWatcher, SIGNAL(fileChanged(QString)),
this, SLOT(socketChanged(QString)));
112 m_timer.setSingleShot(
true);
113 connect(&m_timer, SIGNAL(timeout()),
this, SLOT(connectToBus()));
116 QObject::connect(&d->serviceWatcher, SIGNAL(serviceRegistered(QString)),
this, SLOT(busRegistered(QString)));
117 QObject::connect(&d->serviceWatcher, SIGNAL(serviceUnregistered(QString)),
this, SLOT(busUnregistered(QString)));
119 connectToContextSignals();
121 QInputMethod *p = qApp->inputMethod();
123 m_eventFilterUseSynchronousMode =
false;
124 if (qEnvironmentVariableIsSet(
"IBUS_ENABLE_SYNC_MODE")) {
126 int enableSync = qEnvironmentVariableIntValue(
"IBUS_ENABLE_SYNC_MODE", &ok);
127 if (ok && enableSync == 1)
128 m_eventFilterUseSynchronousMode =
true;
144 switch (capability) {
145 case QPlatformInputContext::HiddenTextCapability:
158 if (a == QInputMethod::Click)
168 d->predit = QString();
169 d->attributes.clear();
177 QObject *input = qApp->focusObject();
179 d->predit = QString();
180 d->attributes.clear();
184 if (d->preeditFocusMode == QIBusPlatformInputContextPrivate::PREEDIT_COMMIT) {
185 if (!d->predit.isEmpty()) {
187 event.setCommitString(d->predit);
188 QCoreApplication::sendEvent(input, &event);
191 if (!d->predit.isEmpty()) {
194 QCoreApplication::sendEvent(input, &event);
199 d->predit = QString();
200 d->attributes.clear();
206 QObject *input = qApp->focusObject();
208 if (d->needsSurroundingText && input
209 && (q.testFlag(Qt::ImSurroundingText)
210 || q.testFlag(Qt::ImCursorPosition)
211 || q.testFlag(Qt::ImAnchorPosition))) {
215 QCoreApplication::sendEvent(input, &query);
217 QString surroundingText = query.value(Qt::ImSurroundingText).toString();
218 uint cursorPosition = query.value(Qt::ImCursorPosition).toUInt();
219 uint anchorPosition = query.value(Qt::ImAnchorPosition).toUInt();
222 text.text = surroundingText;
225 variant.setValue(text);
228 d->context->SetSurroundingText(dbusText, cursorPosition, anchorPosition);
237 QRect r = qApp->inputMethod()->cursorRectangle().toRect();
241 QWindow *inputWindow = qApp->focusWindow();
244 if (!inputWindow->screen())
247 if (QGuiApplication::platformName().startsWith(
"wayland"_L1)) {
248 auto margins = inputWindow->frameMargins();
249 r.translate(margins.left(), margins.top());
250 qreal scale = inputWindow->devicePixelRatio();
251 QRect newRect = QRect(r.x() * scale, r.y() * scale, r.width() * scale, r.height() * scale);
252 qCDebug(lcQpaInputMethods) <<
"microFocus" << newRect;
253 d->context->SetCursorLocationRelative(newRect.x(), newRect.y(),
254 newRect.width(), newRect.height());
259 auto screenGeometry = inputWindow->screen()->geometry();
260 auto point = inputWindow->mapToGlobal(r.topLeft());
261 qreal scale = inputWindow->devicePixelRatio();
262 auto native = (point - screenGeometry.topLeft()) * scale + screenGeometry.topLeft();
263 QRect newRect(native, r.size() * scale);
264 qCDebug(lcQpaInputMethods) <<
"microFocus" << newRect;
265 d->context->SetCursorLocation(newRect.x(), newRect.y(),
266 newRect.width(), newRect.height());
277 if (object && !inputMethodAccepted())
280 qCDebug(lcQpaInputMethods) <<
"setFocusObject" << object;
282 d->context->FocusIn();
284 d->context->FocusOut();
289 QObject *input = qApp->focusObject();
293 const QDBusArgument arg = qvariant_cast<QDBusArgument>(text.variant());
296 qCDebug(lcQpaInputMethods) << arg.currentSignature();
298 qCDebug(lcQpaInputMethods) <<
"commit text:" << t.text;
301 event.setCommitString(t.text);
302 QCoreApplication::sendEvent(input, &event);
304 d->predit = QString();
305 d->attributes.clear();
313 QObject *input = qApp->focusObject();
317 const QDBusArgument arg = qvariant_cast<QDBusArgument>(text.variant());
321 qCDebug(lcQpaInputMethods) <<
"preedit text:" << t.text;
323 d->attributes = t.attributes.imAttributes();
324 if (!t.text.isEmpty())
325 d->attributes += QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, cursorPos, visible ? 1 : 0, QVariant());
328 QCoreApplication::sendEvent(input, &event);
335 updatePreeditText(text, cursorPos, visible);
337 d->preeditFocusMode = QIBusPlatformInputContextPrivate::PreeditFocusMode::PREEDIT_COMMIT;
339 d->preeditFocusMode = QIBusPlatformInputContextPrivate::PreeditFocusMode::PREEDIT_CLEAR;
347 QObject *input = qApp->focusObject();
351 QEvent::Type type = QEvent::KeyPress;
353 type = QEvent::KeyRelease;
358 Qt::KeyboardModifiers modifiers = Qt::NoModifier;
360 modifiers |= Qt::ShiftModifier;
362 modifiers |= Qt::ControlModifier;
364 modifiers |= Qt::AltModifier;
366 modifiers |= Qt::MetaModifier;
368 int qtcode = QXkbCommon::keysymToQtKey(keyval, modifiers);
369 QString text = QXkbCommon::lookupStringNoKeysymTransformations(keyval);
371 qCDebug(lcQpaInputMethods) <<
"forwardKeyEvent" << keyval << keycode << state << modifiers << qtcode << text;
373 QKeyEvent event(type, qtcode, modifiers, keycode, keyval, state, text);
374 QCoreApplication::sendEvent(input, &event);
379 qCDebug(lcQpaInputMethods) <<
"surroundingTextRequired";
381 update(Qt::ImSurroundingText);
386 QObject *input = qApp->focusObject();
390 qCDebug(lcQpaInputMethods) <<
"deleteSurroundingText" << offset << n_chars;
393 event.setCommitString(
"", offset, n_chars);
394 QCoreApplication::sendEvent(input, &event);
399 QObject *input = QGuiApplication::focusObject();
403 QList<QInputMethodEvent::Attribute> attributes;
405 QCoreApplication::sendEvent(input, &event);
410 QObject *input = QGuiApplication::focusObject();
415 QCoreApplication::sendEvent(input, &event);
423 if (!inputMethodAccepted())
426 const QKeyEvent *keyEvent =
static_cast<
const QKeyEvent *>(event);
427 quint32 sym = keyEvent->nativeVirtualKey();
428 quint32 code = keyEvent->nativeScanCode();
429 quint32 state = keyEvent->nativeModifiers();
430 quint32 ibusState = state;
432 if (keyEvent->type() != QEvent::KeyPress)
435 QDBusPendingReply<
bool> reply = d->context->ProcessKeyEvent(sym, code - 8, ibusState);
437 if (m_eventFilterUseSynchronousMode || reply.isFinished()) {
438 bool filtered = reply.value();
439 qCDebug(qtQpaInputMethods) <<
"filterEvent return" << code << sym << state << filtered;
443 Qt::KeyboardModifiers modifiers = keyEvent->modifiers();
444 const int qtcode = keyEvent->key();
449 modifiers ^= Qt::ShiftModifier;
451 case Qt::Key_Control:
452 modifiers ^= Qt::ControlModifier;
455 modifiers ^= Qt::AltModifier;
458 modifiers ^= Qt::MetaModifier;
461 modifiers ^= Qt::GroupSwitchModifier;
466 args << QVariant::fromValue(keyEvent->timestamp());
467 args << QVariant::fromValue(
static_cast<uint>(keyEvent->type()));
468 args << QVariant::fromValue(qtcode);
469 args << QVariant::fromValue(code) << QVariant::fromValue(sym) << QVariant::fromValue(state);
470 args << QVariant::fromValue(keyEvent->text());
471 args << QVariant::fromValue(keyEvent->isAutoRepeat());
473 QIBusFilterEventWatcher *watcher =
new QIBusFilterEventWatcher(reply,
this, QGuiApplication::focusWindow(), modifiers, args);
474 QObject::connect(watcher, &QDBusPendingCallWatcher::finished,
this, &QIBusPlatformInputContext::filterEventFinished);
482 QDBusPendingReply<
bool> reply = *call;
484 if (reply.isError()) {
491 QWindow *window = watcher->window();
498 Qt::KeyboardModifiers modifiers = watcher->modifiers();
499 QVariantList args = watcher->arguments();
500 const ulong time =
static_cast<ulong>(args.at(0).toUInt());
501 const QEvent::Type type =
static_cast<QEvent::Type>(args.at(1).toUInt());
502 const int qtcode = args.at(2).toInt();
503 const quint32 code = args.at(3).toUInt();
504 const quint32 sym = args.at(4).toUInt();
505 const quint32 state = args.at(5).toUInt();
506 const QString string = args.at(6).toString();
507 const bool isAutoRepeat = args.at(7).toBool();
510 bool filtered = reply.value();
511 qCDebug(qtQpaInputMethods) <<
"filterEventFinished return" << code << sym << state << filtered;
513#ifndef QT_NO_CONTEXTMENU
514 if (type == QEvent::KeyPress && qtcode == Qt::Key_Menu
515 && window !=
nullptr) {
516 const QPoint globalPos = window->screen()->handle()->cursor()->pos();
517 const QPoint pos = window->mapFromGlobal(globalPos);
518 QWindowSystemInterfacePrivate::ContextMenuEvent contextMenuEvent(window,
false, pos,
519 globalPos, modifiers);
520 QGuiApplicationPrivate::processWindowSystemEvent(&contextMenuEvent);
523 QWindowSystemInterfacePrivate::KeyEvent keyEvent(window, time, type, qtcode, modifiers,
524 code, sym, state, string, isAutoRepeat);
525 QGuiApplicationPrivate::processWindowSystemEvent(&keyEvent);
534 return QPlatformInputContext::locale();
540 qCDebug(qtQpaInputMethods) <<
"socketChanged";
546 d->serviceWatcher.setConnection(QDBusConnection(QString()));
547 d->context =
nullptr;
550 QDBusConnection::disconnectFromBus(
"QIBusProxy"_L1);
557 qCDebug(qtQpaInputMethods) <<
"busRegistered";
566 qCDebug(qtQpaInputMethods) <<
"busUnregistered";
575 qCDebug(qtQpaInputMethods) <<
"QIBusPlatformInputContext::connectToBus";
577 connectToContextSignals();
579#if QT_CONFIG(filesystemwatcher)
580 if (!d->usePortal && m_socketWatcher.files().size() == 0)
581 m_socketWatcher.addPath(QIBusPlatformInputContextPrivate::getSocketPath());
587 if (!d->bus || !d->bus->isValid())
591 Q_ASSERT(engine_name == desc.engine_name);
592 QLocale locale(desc.language);
593 if (d->locale != locale) {
601 if (d->bus && d->bus->isValid()) {
602 connect(d->bus.get(), SIGNAL(GlobalEngineChanged(QString)),
this, SLOT(globalEngineChanged(QString)));
606 connect(d->context.get(), SIGNAL(CommitText(QDBusVariant)), SLOT(commitText(QDBusVariant)));
607 connect(d->context.get(), SIGNAL(UpdatePreeditText(QDBusVariant,uint,
bool)),
this, SLOT(updatePreeditText(QDBusVariant,uint,
bool)));
608 connect(d->context.get(), SIGNAL(UpdatePreeditTextWithMode(QDBusVariant,uint,
bool,uint)),
this, SLOT(updatePreeditTextWithMode(QDBusVariant,uint,
bool,uint)));
609 connect(d->context.get(), SIGNAL(ForwardKeyEvent(uint,uint,uint)),
this, SLOT(forwardKeyEvent(uint,uint,uint)));
610 connect(d->context.get(), SIGNAL(DeleteSurroundingText(
int,uint)),
this, SLOT(deleteSurroundingText(
int,uint)));
612 connect(d->context.get(), SIGNAL(HidePreeditText()),
this, SLOT(
hidePreeditText()));
613 connect(d->context.get(), SIGNAL(ShowPreeditText()),
this, SLOT(
showPreeditText()));
619 return QFileInfo::exists(
"/.flatpak-info"_L1) || qEnvironmentVariableIsSet(
"SNAP");
636 qCDebug(lcQpaInputMethods) <<
"use IBus portal";
638 valid = !QStandardPaths::findExecutable(QString::fromLocal8Bit(
"ibus-daemon"), QStringList()).isEmpty();
644 if (bus && bus->isValid()) {
646 locale = QLocale(desc.language);
659 QDBusConnection connection(
"QIBusProxy"_L1);
660 if (!connection.isConnected())
663 const char* ibusService =
usePortal ?
"org.freedesktop.portal.IBus" :
"org.freedesktop.IBus";
664 QDBusReply<QDBusObjectPath> ic;
666 portalBus = std::make_unique<QIBusProxyPortal>(QLatin1StringView(ibusService),
667 "/org/freedesktop/IBus"_L1,
669 if (!portalBus->isValid()) {
670 qWarning(
"QIBusPlatformInputContext: invalid portal bus.");
674 ic = portalBus->CreateInputContext(
"QIBusInputContext"_L1);
676 bus = std::make_unique<QIBusProxy>(QLatin1StringView(ibusService),
677 "/org/freedesktop/IBus"_L1,
679 if (!bus->isValid()) {
680 qWarning(
"QIBusPlatformInputContext: invalid bus.");
684 ic = bus->CreateInputContext(
"QIBusInputContext"_L1);
687 serviceWatcher.removeWatchedService(ibusService);
688 serviceWatcher.setConnection(connection);
689 serviceWatcher.addWatchedService(ibusService);
692 qWarning(
"QIBusPlatformInputContext: CreateInputContext failed.");
696 context = std::make_unique<QIBusInputContextProxy>(QLatin1StringView(ibusService), ic.value().path(), connection);
698 if (!context->isValid()) {
699 qWarning(
"QIBusPlatformInputContext: invalid input context.");
704 IBUS_CAP_PREEDIT_TEXT = 1 << 0,
705 IBUS_CAP_AUXILIARY_TEXT = 1 << 1,
706 IBUS_CAP_LOOKUP_TABLE = 1 << 2,
707 IBUS_CAP_FOCUS = 1 << 3,
708 IBUS_CAP_PROPERTY = 1 << 4,
709 IBUS_CAP_SURROUNDING_TEXT = 1 << 5
711 context->SetCapabilities(IBUS_CAP_PREEDIT_TEXT|IBUS_CAP_FOCUS|IBUS_CAP_SURROUNDING_TEXT);
713 context->setClientCommitPreedit(QIBusPropTypeClientCommitPreedit(
true));
715 qCDebug(lcQpaInputMethods) <<
">>>> bus connected!";
722 QByteArray displayNumber =
"0";
723 bool isWayland =
false;
725 if (QString path = qEnvironmentVariable(
"IBUS_ADDRESS_FILE"); !path.isNull()) {
727 }
else if (display = qgetenv(
"WAYLAND_DISPLAY"); !display.isEmpty()) {
730 display = qgetenv(
"DISPLAY");
732 QByteArray host =
"unix";
735 displayNumber = display;
737 int pos = display.indexOf(
':');
739 host = display.left(pos);
741 int pos2 = display.indexOf(
'.', pos);
743 displayNumber = display.mid(pos, pos2 - pos);
745 displayNumber = display.mid(pos);
748 qCDebug(lcQpaInputMethods) <<
"host=" << host <<
"displayNumber" << displayNumber;
750 return QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) +
752 QLatin1StringView(QDBusConnection::localMachineId()) +
753 u'-' + QString::fromLocal8Bit(host) + u'-' + QString::fromLocal8Bit(displayNumber);
759 QDBusConnection::connectToBus(QDBusConnection::SessionBus,
"QIBusProxy"_L1);
763 QFile file(getSocketPath());
764 if (!file.open(QFile::ReadOnly))
769 QByteArray lineArray;
771 while (file.readLineInto(&lineArray)) {
772 QByteArrayView line = QByteArrayView(lineArray).trimmed();
773 if (line.startsWith(
'#'))
776 if (line.startsWith(
"IBUS_ADDRESS="))
777 address = line.mid(
sizeof(
"IBUS_ADDRESS=") - 1).toByteArray();
778 if (line.startsWith(
"IBUS_DAEMON_PID="))
779 pid = line.mid(
sizeof(
"IBUS_DAEMON_PID=") - 1).toInt();
782 qCDebug(lcQpaInputMethods) <<
"IBUS_ADDRESS=" << address <<
"PID=" << pid;
783 if (address.isEmpty() || pid < 0 || kill(pid, 0) != 0)
786 QDBusConnection::connectToBus(QString::fromLatin1(address),
"QIBusProxy"_L1);
791#include "moc_qibusplatforminputcontext.cpp"
bool needsSurroundingText
std::unique_ptr< QIBusInputContextProxy > context
QIBusPlatformInputContextPrivate()
std::unique_ptr< QIBusProxy > bus
~QIBusPlatformInputContextPrivate()
PreeditFocusMode preeditFocusMode
QList< QInputMethodEvent::Attribute > attributes
static QString getSocketPath()
QDBusServiceWatcher serviceWatcher
std::unique_ptr< QIBusProxyPortal > portalBus
void invokeAction(QInputMethod::Action a, int x) override
Called when the word currently being composed in the input item is tapped by the user.
void busUnregistered(const QString &str)
bool filterEvent(const QEvent *event) override
This function can be reimplemented to filter input events.
void globalEngineChanged(const QString &engine_name)
void filterEventFinished(QDBusPendingCallWatcher *call)
void update(Qt::InputMethodQueries) override
Notification on editor updates.
bool hasCapability(Capability capability) const override
Returns whether the implementation supports capability.
void socketChanged(const QString &str)
QLocale locale() const override
bool isValid() const override
Returns input context validity.
void setFocusObject(QObject *object) override
This virtual method gets called to notify updated focus to object.
~QIBusPlatformInputContext()
void surroundingTextRequired()
void reset() override
Method to be called when input method needs to be reset.
void busRegistered(const QString &str)
The QKeyEvent class describes a key event.
Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher")
static bool shouldConnectIbusPortal()
static bool checkNeedPortalSupport()
#define IBUS_CONTROL_MASK
#define IBUS_RELEASE_MASK