From fbc5f69b74d47616a1fac65918fce826ee37e008 Mon Sep 17 00:00:00 2001 From: Weng Xuetian Date: Tue, 22 Nov 2022 17:21:58 -0800 Subject: [PATCH] Try to workaround inconsistency if QWidget focus proxy is used. --- CMakeLists.txt | 2 +- qt5/platforminputcontext/CMakeLists.txt | 1 + .../fcitxcandidatewindow.cpp | 10 +- .../fcitxcandidatewindow.h | 6 +- .../qfcitxplatforminputcontext.cpp | 186 ++++++++++++++---- .../qfcitxplatforminputcontext.h | 69 ++----- qt6/platforminputcontext/CMakeLists.txt | 1 + 7 files changed, 183 insertions(+), 92 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7385873..b645a87 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,7 +79,7 @@ if(ENABLE_QT5) endif() if(ENABLE_QT6) - find_package(Qt6 ${REQUIRED_QT6_VERSION} CONFIG REQUIRED Core DBus) + find_package(Qt6 ${REQUIRED_QT6_VERSION} CONFIG REQUIRED Core DBus Widgets) find_package(Qt6Gui ${REQUIRED_QT6_VERSION} REQUIRED Private) add_subdirectory(qt6) endif() diff --git a/qt5/platforminputcontext/CMakeLists.txt b/qt5/platforminputcontext/CMakeLists.txt index 31d0709..ed19dc6 100644 --- a/qt5/platforminputcontext/CMakeLists.txt +++ b/qt5/platforminputcontext/CMakeLists.txt @@ -40,6 +40,7 @@ target_link_libraries(fcitx5platforminputcontextplugin Qt5::Core Qt5::Gui Qt5::DBus + Qt5::Widgets XCB::XCB Fcitx5Qt5::DBusAddons XKBCommon::XKBCommon diff --git a/qt5/platforminputcontext/fcitxcandidatewindow.cpp b/qt5/platforminputcontext/fcitxcandidatewindow.cpp index b413eb0..9aee971 100644 --- a/qt5/platforminputcontext/fcitxcandidatewindow.cpp +++ b/qt5/platforminputcontext/fcitxcandidatewindow.cpp @@ -83,8 +83,9 @@ class MultilineText { QRect boundingRect_; }; -FcitxCandidateWindow::FcitxCandidateWindow(QWindow *window, FcitxTheme *theme) - : QWindow(), theme_(theme), parent_(window) { +FcitxCandidateWindow::FcitxCandidateWindow(QWindow *window, + QFcitxPlatformInputContext *context) + : QWindow(), context_(context), theme_(context->theme()), parent_(window) { setFlags(Qt::ToolTip | Qt::FramelessWindowHint | Qt::BypassWindowManagerHint | Qt::WindowDoesNotAcceptFocus | Qt::NoDropShadowWindowHint); @@ -356,7 +357,7 @@ void FcitxCandidateWindow::updateClientSideUI( bool candidatesVisible = !candidates.isEmpty(); bool visible = preeditVisible || auxUpVisbile || auxDownVisible || candidatesVisible; - auto window = QGuiApplication::focusWindow(); + auto window = context_->focusWindowWrapper(); if (!theme_ || !visible || !window || window != parent_) { hide(); return; @@ -413,8 +414,7 @@ void FcitxCandidateWindow::updateClientSideUI( sizeWithoutShadow.setHeight(0); } - QRect cursorRect = - QGuiApplication::inputMethod()->cursorRectangle().toRect(); + QRect cursorRect = context_->cursorRectangleWrapper(); QRect screenGeometry; // Try to apply the screen edge detection over the window, because if we // intent to use this with wayland. It we have no information above screen diff --git a/qt5/platforminputcontext/fcitxcandidatewindow.h b/qt5/platforminputcontext/fcitxcandidatewindow.h index 064cbb2..2f4e4a7 100644 --- a/qt5/platforminputcontext/fcitxcandidatewindow.h +++ b/qt5/platforminputcontext/fcitxcandidatewindow.h @@ -21,14 +21,15 @@ namespace fcitx { -struct FcitxQtICData; class FcitxTheme; class MultilineText; +class QFcitxPlatformInputContext; class FcitxCandidateWindow : public QWindow { Q_OBJECT public: - explicit FcitxCandidateWindow(QWindow *window, FcitxTheme *theme); + explicit FcitxCandidateWindow(QWindow *window, + QFcitxPlatformInputContext *context); ~FcitxCandidateWindow(); void render(QPainter *painter); @@ -69,6 +70,7 @@ public Q_SLOTS: const bool isWayland_ = QGuiApplication::platformName().startsWith("wayland"); QSize actualSize_; + QPointer context_; QPointer theme_; QBackingStore *backingStore_; QTextLayout upperLayout_; diff --git a/qt5/platforminputcontext/qfcitxplatforminputcontext.cpp b/qt5/platforminputcontext/qfcitxplatforminputcontext.cpp index d02860d..a0afeeb 100644 --- a/qt5/platforminputcontext/qfcitxplatforminputcontext.cpp +++ b/qt5/platforminputcontext/qfcitxplatforminputcontext.cpp @@ -5,13 +5,15 @@ * */ +#include #include #include -#include #include #include +#include #include #include +#include #include #include #include @@ -132,18 +134,6 @@ static inline const char *get_locale() { return locale; } -static bool objectAcceptsInputMethod() { - bool enabled = false; - QObject *object = qApp->focusObject(); - if (object) { - QInputMethodQueryEvent query(Qt::ImEnabled); - QGuiApplication::sendEvent(object, &query); - enabled = query.value(Qt::ImEnabled).toBool(); - } - - return enabled; -} - struct xkb_context *_xkb_context_new_helper() { struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); if (context) { @@ -153,6 +143,52 @@ struct xkb_context *_xkb_context_new_helper() { return context; } +FcitxQtICData::FcitxQtICData(QFcitxPlatformInputContext *context, + QWindow *window) + : proxy(new FcitxQtInputContextProxy(context->watcher(), context)), + context_(context), window_(window) { + proxy->setProperty("icData", + QVariant::fromValue(static_cast(this))); + QObject::connect(window, &QWindow::visibilityChanged, proxy, + [this](bool visible) { + if (!visible) { + resetCandidateWindow(); + } + }); + QObject::connect(context_->watcher(), &FcitxQtWatcher::availabilityChanged, + proxy, [this](bool avail) { + if (!avail) { + resetCandidateWindow(); + } + }); +} +FcitxQtICData::~FcitxQtICData() { + delete proxy; + resetCandidateWindow(); +} + +FcitxCandidateWindow *FcitxQtICData::candidateWindow() { + if (!candidateWindow_) { + candidateWindow_ = new FcitxCandidateWindow(window(), context_); + QObject::connect( + candidateWindow_, &FcitxCandidateWindow::candidateSelected, proxy, + [proxy = proxy](int index) { proxy->selectCandidate(index); }); + QObject::connect(candidateWindow_, &FcitxCandidateWindow::prevClicked, + proxy, [proxy = proxy]() { proxy->prevPage(); }); + QObject::connect(candidateWindow_, &FcitxCandidateWindow::nextClicked, + proxy, [proxy = proxy]() { proxy->nextPage(); }); + } + return candidateWindow_; +} + +void FcitxQtICData::resetCandidateWindow() { + if (auto *w = candidateWindow_.data()) { + candidateWindow_ = nullptr; + w->deleteLater(); + return; + } +} + QFcitxPlatformInputContext::QFcitxPlatformInputContext() : watcher_(new FcitxQtWatcher( QDBusConnection::connectToBus(QDBusConnection::SessionBus, "fcitx"), @@ -171,6 +207,13 @@ QFcitxPlatformInputContext::QFcitxPlatformInputContext() registerFcitxQtDBusTypes(); watcher_->setWatchPortal(true); watcher_->watch(); + + // Monitor the focus widget from QApplication, because + // QGuiApplication::focusObject does not respect the QWidget's focusProxy. + if (qobject_cast(qGuiApp)) { + connect(qApp, &QApplication::focusChanged, this, + &QFcitxPlatformInputContext::focusWidgetChanged); + } } QFcitxPlatformInputContext::~QFcitxPlatformInputContext() { @@ -180,6 +223,24 @@ QFcitxPlatformInputContext::~QFcitxPlatformInputContext() { delete watcher_; } +bool QFcitxPlatformInputContext::objectAcceptsInputMethod() const { + bool enabled = false; + QObject *object = qGuiApp->focusObject(); + if (object) { + QInputMethodQueryEvent query(Qt::ImEnabled); + QGuiApplication::sendEvent(object, &query); + enabled = query.value(Qt::ImEnabled).toBool(); + } + + if (focusWidget_ && focusWidget_ != object && !enabled) { + QInputMethodQueryEvent query(Qt::ImEnabled); + QGuiApplication::sendEvent(focusWidget_, &query); + enabled = query.value(Qt::ImEnabled).toBool(); + } + + return enabled; +} + void QFcitxPlatformInputContext::cleanUp() { icMap_.clear(); @@ -243,7 +304,7 @@ void QFcitxPlatformInputContext::reset() { } void QFcitxPlatformInputContext::update(Qt::InputMethodQueries queries) { - QWindow *window = qApp->focusWindow(); + QWindow *window = this->focusWindowWrapper(); FcitxQtInputContextProxy *proxy = validICByWindow(window); if (!proxy) return; @@ -251,7 +312,7 @@ void QFcitxPlatformInputContext::update(Qt::InputMethodQueries queries) { FcitxQtICData &data = *static_cast( proxy->property("icData").value()); - QObject *input = qApp->focusObject(); + QObject *input = focusObjectWrapper(); if (!input) return; @@ -263,7 +324,7 @@ void QFcitxPlatformInputContext::update(Qt::InputMethodQueries queries) { } if (queries & Qt::ImEnabled) { - if (!inputMethodAccepted() || !objectAcceptsInputMethod()) { + if (!inputMethodAccepted() && !objectAcceptsInputMethod()) { addCapability(data, FcitxCapabilityFlag_Disable); } else { removeCapability(data, FcitxCapabilityFlag_Disable); @@ -367,7 +428,7 @@ void QFcitxPlatformInputContext::setFocusObject(QObject *object) { data.resetCandidateWindow(); } - QWindow *window = qApp->focusWindow(); + QWindow *window = this->focusWindowWrapper(); lastWindow_ = window; lastObject_ = object; // Always create IC Data for window. @@ -400,6 +461,13 @@ void QFcitxPlatformInputContext::setFocusObject(QObject *object) { } } +void QFcitxPlatformInputContext::focusWidgetChanged(QWidget *, QWidget *now) { + focusWidget_ = now; + if (focusWidget_ != qGuiApp->focusObject()) { + update(Qt::ImEnabled); + } +} + void QFcitxPlatformInputContext::updateCursorRect() { if (validICByWindow(lastWindow_.data())) { cursorRectChanged(); @@ -412,7 +480,7 @@ void QFcitxPlatformInputContext::windowDestroyed(QObject *object) { } void QFcitxPlatformInputContext::cursorRectChanged() { - QWindow *inputWindow = qApp->focusWindow(); + QWindow *inputWindow = this->focusWindowWrapper(); if (!inputWindow) return; FcitxQtInputContextProxy *proxy = validICByWindow(inputWindow); @@ -422,7 +490,7 @@ void QFcitxPlatformInputContext::cursorRectChanged() { FcitxQtICData &data = *static_cast( proxy->property("icData").value()); - QRect r = qApp->inputMethod()->cursorRectangle().toRect(); + QRect r = this->cursorRectangleWrapper(); if (!r.isValid()) return; @@ -467,7 +535,7 @@ void QFcitxPlatformInputContext::createInputContextFinished( data->rect = QRect(); if (proxy->isValid()) { - QWindow *window = qApp->focusWindow(); + QWindow *window = this->focusWindowWrapper(); setFocusGroupForX11(uuid); if (window && window == w) { cursorRectChanged(); @@ -514,7 +582,7 @@ void QFcitxPlatformInputContext::commitString(const QString &str) { cursorPos_ = 0; preeditList_.clear(); commitPreedit_.clear(); - QObject *input = qApp->focusObject(); + QObject *input = qGuiApp->focusObject(); if (!input) return; @@ -525,7 +593,7 @@ void QFcitxPlatformInputContext::commitString(const QString &str) { void QFcitxPlatformInputContext::updateFormattedPreedit( const FcitxQtFormattedPreeditList &preeditList, int cursorPos) { - QObject *input = qApp->focusObject(); + QObject *input = qGuiApp->focusObject(); if (!input) return; if (cursorPos == cursorPos_ && preeditList == preeditList_) @@ -586,7 +654,7 @@ void QFcitxPlatformInputContext::updateClientSideUI( const FcitxQtFormattedPreeditList &auxDown, const FcitxQtStringKeyValueList &candidates, int candidateIndex, int layoutHint, bool hasPrev, bool hasNext) { - QObject *input = qApp->focusObject(); + QObject *input = qGuiApp->focusObject(); if (!input) { return; } @@ -599,12 +667,9 @@ void QFcitxPlatformInputContext::updateClientSideUI( FcitxQtICData *data = static_cast(proxy->property("icData").value()); auto w = data->window(); - auto window = qApp->focusWindow(); + auto window = this->focusWindowWrapper(); if (window && w == window) { - if (!theme_) { - theme_ = new FcitxTheme(this); - } - data->candidateWindow(theme_)->updateClientSideUI( + data->candidateWindow()->updateClientSideUI( preedit, cursorpos, auxUp, auxDown, candidates, candidateIndex, layoutHint, hasPrev, hasNext); } @@ -612,7 +677,7 @@ void QFcitxPlatformInputContext::updateClientSideUI( void QFcitxPlatformInputContext::deleteSurroundingText(int offset, unsigned int _nchar) { - QObject *input = qApp->focusObject(); + QObject *input = qGuiApp->focusObject(); if (!input) return; @@ -678,8 +743,8 @@ void QFcitxPlatformInputContext::forwardKey(unsigned int keyval, FcitxQtICData &data = *static_cast( proxy->property("icData").value()); auto *w = data.window(); - QObject *input = qApp->focusObject(); - auto window = qApp->focusWindow(); + QObject *input = qGuiApp->focusObject(); + auto window = this->focusWindowWrapper(); if (input && window && w == window) { std::unique_ptr keyevent{ createKeyEvent(keyval, state, type, data.event.get())}; @@ -711,7 +776,7 @@ void QFcitxPlatformInputContext::createICData(QWindow *w) { if (iter == icMap_.end()) { auto result = icMap_.emplace(std::piecewise_construct, std::forward_as_tuple(w), - std::forward_as_tuple(watcher_, w)); + std::forward_as_tuple(this, w)); connect(w, &QObject::destroyed, this, &QFcitxPlatformInputContext::windowDestroyed); iter = result.first; @@ -838,13 +903,14 @@ bool QFcitxPlatformInputContext::filterEvent(const QEvent *event) { if (!inputMethodAccepted() && !objectAcceptsInputMethod()) break; - QObject *input = qApp->focusObject(); + QObject *input = qGuiApp->focusObject(); if (!input) { break; } - FcitxQtInputContextProxy *proxy = validICByWindow(qApp->focusWindow()); + FcitxQtInputContextProxy *proxy = + validICByWindow(this->focusWindowWrapper()); if (!proxy) { if (filterEventFallback(keyval, keycode, state, isRelease)) { @@ -880,7 +946,7 @@ bool QFcitxPlatformInputContext::filterEvent(const QEvent *event) { } } else { ProcessKeyWatcher *watcher = new ProcessKeyWatcher( - *keyEvent, qApp->focusWindow(), reply, proxy); + *keyEvent, this->focusWindowWrapper(), reply, proxy); connect(watcher, &QDBusPendingCallWatcher::finished, this, &QFcitxPlatformInputContext::processKeyEventFinished); return true; @@ -960,7 +1026,7 @@ FcitxQtInputContextProxy *QFcitxPlatformInputContext::validIC() { if (icMap_.empty()) { return nullptr; } - QWindow *window = qApp->focusWindow(); + QWindow *window = this->focusWindowWrapper(); return validICByWindow(window); } @@ -1017,4 +1083,52 @@ bool QFcitxPlatformInputContext::processCompose(unsigned int keyval, return true; } + +QWindow *QFcitxPlatformInputContext::focusWindowWrapper() const { + QWindow *focusWindow = nullptr; + if (focusWidget_ && qGuiApp->focusObject() && + qGuiApp->focusObject() != focusWidget_) { + focusWindow = focusWidget_->topLevelWidget()->windowHandle(); + } + if (!focusWindow) { + focusWindow = qGuiApp->focusWindow(); + } + return focusWindow; +} + +QObject *QFcitxPlatformInputContext::focusObjectWrapper() const { + if (focusWidget_ && qGuiApp->focusObject() && + qGuiApp->focusObject() != focusWidget_) { + return focusWidget_; + } + return qGuiApp->focusObject(); +} + +QRect QFcitxPlatformInputContext::cursorRectangleWrapper() const { + QRect r; + if (focusWidget_ && focusWidget_ != qGuiApp->focusObject()) { + // Logic is borrowed from QWidgetPrivate::updateWidgetTransform. + // If focusObject mismatches, the inputItemTransform will also mismatch, + // so we need to do our own calculation. + QTransform t; + const QPoint p = + focusWidget_->mapTo(focusWidget_->topLevelWidget(), QPoint(0, 0)); + t.translate(p.x(), p.y()); + r = focusWidget_->inputMethodQuery(Qt::ImCursorRectangle).toRect(); + if (r.isValid()) { + r = t.mapRect(r); + } + } else { + r = qGuiApp->inputMethod()->cursorRectangle().toRect(); + } + return r; +} + +FcitxTheme *QFcitxPlatformInputContext::theme() { + if (!theme_) { + theme_ = new FcitxTheme(this); + } + return theme_; +} + } // namespace fcitx diff --git a/qt5/platforminputcontext/qfcitxplatforminputcontext.h b/qt5/platforminputcontext/qfcitxplatforminputcontext.h index dc88824..c61e76f 100644 --- a/qt5/platforminputcontext/qfcitxplatforminputcontext.h +++ b/qt5/platforminputcontext/qfcitxplatforminputcontext.h @@ -23,63 +23,23 @@ #include #include +class QWidget; + namespace fcitx { class FcitxQtConnection; -class FcitxTheme; +class QFcitxPlatformInputContext; struct FcitxQtICData { - FcitxQtICData(FcitxQtWatcher *watcher, QWindow *window) - : proxy(new FcitxQtInputContextProxy(watcher, watcher)), - watcher_(watcher), window_(window) { - proxy->setProperty("icData", - QVariant::fromValue(static_cast(this))); - QObject::connect(window, &QWindow::visibilityChanged, proxy, - [this](bool visible) { - if (!visible) { - resetCandidateWindow(); - } - }); - QObject::connect(watcher, &FcitxQtWatcher::availabilityChanged, proxy, - [this](bool avail) { - if (!avail) { - resetCandidateWindow(); - } - }); - } + FcitxQtICData(QFcitxPlatformInputContext *context, QWindow *window); FcitxQtICData(const FcitxQtICData &that) = delete; - ~FcitxQtICData() { - delete proxy; - resetCandidateWindow(); - } + ~FcitxQtICData(); - FcitxCandidateWindow *candidateWindow(FcitxTheme *theme) { - if (!candidateWindow_) { - candidateWindow_ = new FcitxCandidateWindow(window(), theme); - QObject::connect( - candidateWindow_, &FcitxCandidateWindow::candidateSelected, - proxy, - [proxy = proxy](int index) { proxy->selectCandidate(index); }); - QObject::connect(candidateWindow_, - &FcitxCandidateWindow::prevClicked, proxy, - [proxy = proxy]() { proxy->prevPage(); }); - QObject::connect(candidateWindow_, - &FcitxCandidateWindow::nextClicked, proxy, - [proxy = proxy]() { proxy->nextPage(); }); - } - return candidateWindow_; - } + FcitxCandidateWindow *candidateWindow(); QWindow *window() { return window_.data(); } - auto *watcher() { return watcher_; } - void resetCandidateWindow() { - if (auto *w = candidateWindow_.data()) { - candidateWindow_ = nullptr; - w->deleteLater(); - return; - } - } + void resetCandidateWindow(); quint64 capability = 0; FcitxQtInputContextProxy *proxy; @@ -91,7 +51,7 @@ struct FcitxQtICData { int surroundingCursor = -1; private: - FcitxQtWatcher *watcher_; + QFcitxPlatformInputContext *context_; QPointer window_; QPointer candidateWindow_; }; @@ -156,6 +116,16 @@ class QFcitxPlatformInputContext : public QPlatformInputContext { QLocale locale() const override; bool hasCapability(Capability capability) const override; + FcitxQtWatcher *watcher() { return watcher_; } + + // Use Wrapper as suffix to avoid upstream add function with same name. + QObject *focusObjectWrapper() const; + QWindow *focusWindowWrapper() const; + QRect cursorRectangleWrapper() const; + + // Initialize theme object on demand. + FcitxTheme *theme(); + public Q_SLOTS: void cursorRectChanged(); void commitString(const QString &str); @@ -177,6 +147,7 @@ public Q_SLOTS: bool hasNext); private Q_SLOTS: void processKeyEventFinished(QDBusPendingCallWatcher *); + void focusWidgetChanged(QWidget *old, QWidget *now); private: bool processCompose(unsigned int keyval, unsigned int state, @@ -212,6 +183,7 @@ private Q_SLOTS: unsigned int state, bool isRelaese); void updateCursorRect(); + bool objectAcceptsInputMethod() const; FcitxQtWatcher *watcher_; QString preedit_; @@ -231,6 +203,7 @@ private Q_SLOTS: xkbComposeState_; QLocale locale_; FcitxTheme *theme_ = nullptr; + QPointer focusWidget_; }; } // namespace fcitx diff --git a/qt6/platforminputcontext/CMakeLists.txt b/qt6/platforminputcontext/CMakeLists.txt index 430ba42..6d39553 100644 --- a/qt6/platforminputcontext/CMakeLists.txt +++ b/qt6/platforminputcontext/CMakeLists.txt @@ -41,6 +41,7 @@ target_link_libraries(fcitx5platforminputcontextplugin-qt6 Qt6::Core Qt6::Gui Qt6::DBus + Qt6::Widgets XCB::XCB Fcitx5Qt6::DBusAddons XKBCommon::XKBCommon