Skip to content

fix: build bundled fcitx5 plugin against the bundled Qt#5142

Merged
abdnh merged 4 commits into
ankitects:mainfrom
xiaohan484:fix/fcitx5-plugin-qt-abi
Jul 15, 2026
Merged

fix: build bundled fcitx5 plugin against the bundled Qt#5142
abdnh merged 4 commits into
ankitects:mainfrom
xiaohan484:fix/fcitx5-plugin-qt-abi

Conversation

@xiaohan484

Copy link
Copy Markdown
Contributor

Linked issue (required)

Fixes #5110

Summary / motivation (required)

CI builds the fcitx5 plugin against one Qt but ships it inside PyQt6's Qt — a
version mismatch that crashes Anki on startup for every fcitx5 user.

Root cause: the plugin subclasses QPlatformInputContext, a Qt private API
with no ABI guarantee across minor versions. Built against Qt 6.2.4 headers but
run inside Qt 6.11.0, it dereferences a misplaced member and SIGSEGVs on first
focus.

Solution: build the plugin against the same Qt as the bundled pyqt6-qt6
and install it where the packaging step (qt/tools/build_installer.py) looks.
The per-flag reasoning is in the commit message.

Steps to reproduce (required, use N/A if not applicable)

  1. On a Linux desktop configured to use fcitx5 as the input method, install the
    official Anki 26.05 release.
  2. Launch Anki normally.
  3. It dies with SIGSEGV on startup, as soon as the main window takes focus.

How to test (required)

Checklist (minimum)

  • I ran ./ninja check or an equivalent relevant check locally.
  • I added or updated tests when the change is non-trivial or behavior changed.

Details

release.yml is workflow_dispatch-only, so it can't run on the PR. I
reproduced its build step locally: rebuilt the plugin against Qt 6.11.0
(confirmed the Qt_6.11 ELF tag and a clean dlopen(RTLD_NOW)), dropped it into
an Anki 26.05 bundle, and typed Chinese and Japanese without the crash.

Risk / compatibility / migration (optional)

  1. Fixes build-linux-x86 only. build-linux-arm-installer installs a prebuilt
    fcitx5-frontend-qt6 from apt, built against Ubuntu's Qt rather than the
    bundled 6.11.0 — the same class of mismatch. I have no arm hardware to confirm
    or fix it, so it's left for a follow-up.
  2. Adds an aqtinstall step (~1.5 GB Qt download) to the Linux release build.
  3. Not validated on CI. release.yml is workflow_dispatch-only, so an
    external contributor can't run it; only its build step is reproduced locally
    (see How to test). Please confirm it in a real release build.

UI evidence (required for visual changes; otherwise N/A)

N/A

Scope

  • This PR is focused on one change (no unrelated edits).

The fcitx5 plugin subclasses QPlatformInputContext, a Qt private API with no ABI
guarantee across minor versions. CI built it against Qt 6.2.4 but shipped it
inside PyQt6's Qt 6.11.0, so it SIGSEGVs on startup for every fcitx5 user.

Build the plugin against the same Qt as the bundled pyqt6-qt6, and install it
where the packaging step looks:

- Fetch that Qt as qt_prefix (CMAKE_PREFIX_PATH); install to the qt6/plugins path
  qt/tools/build_installer.py scans (CMAKE_INSTALL_QT6PLUGINDIR).
- QT_FIND_PRIVATE_MODULES=ON — Qt 6.11 won't expose the private includes without it.
- Drop qt6-base-dev / qt6-base-private-dev so find_package(Qt6) can't silently fall
  back to the distro Qt (that would rebuild the bug with CI still green).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@xiaohan484
xiaohan484 marked this pull request as ready for review July 11, 2026 04:58
Comment thread .github/workflows/release.yml Outdated
gettext \
patchelf
# Build against the same Qt as the bundled pyqt6-qt6
qt_version=$(grep 'name = "pyqt6-qt6"' uv.lock \

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haven't tested yet, but a small nitpick: doubt we can rely on uv.lock's ordering here, maybe try parsing uv export --package aqt --extra qt's output instead?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Checked, and uv.lock has four pyqt6-qt6 entries — 6.11.0, 6.10.2,
6.8.1, 6.9.1 (one per extra). The original grep was picking one by ordering alone.

Switched to your suggestion:

qt_version=$(uv export --frozen --no-hashes --package aqt --extra qt \
  | grep -oP '^pyqt6-qt6==\K\S+')

Pushed as a follow-up commit. Thanks!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having re-read the linked forum post, building against qt6.2 doesn't seem to be the cause here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The thread establishes that deleting the .so stops the crash — so the plugin is what's crashing. It never gets to why; it stops at the workaround. So it isn't evidence against a Qt-version cause, it just never reaches one. Here's what I found when I went looking.

Qt's BC guarantee doesn't cover this plugin

Qt's docs:

There are no source or binary compatibility guarantees for the QPA classes, meaning that the API is only guaranteed to work with the Qt version it was developed against.

and the header itself, qtbase/src/gui/kernel/qplatforminputcontext.h:

This file is part of the QPA API ... Usage of this API may make your code source and binary incompatible with future versions of Qt.

Qt even installs it under a version-stamped private include dir — include/QtGui/6.11.0/QtGui/qpa/, reachable only via Qt6::GuiPrivate — which is why this PR has to pass -DQT_FIND_PRIVATE_MODULES=ON to compile at all.

fcitx5-qt is built directly on it — qt5/platforminputcontext/qfcitxplatforminputcontext.h (the qt6/ copy is a symlink to it):

 22: #include <qpa/qplatforminputcontext.h>
143: class QFcitxPlatformInputContext : public QPlatformInputContext {
216:     FcitxQtWatcher *watcher_;      // first data member

What actually breaks

qtbase d9bb8c0a17, "Automatically reflect new input context input direction when locale changes", first released in 6.7.0, adds both halves of this:

  // qplatforminputcontext.h
      friend class QInputMethod;
+
+     Qt::LayoutDirection m_inputDirection;

  // qplatforminputcontext.cpp
  QPlatformInputContext::QPlatformInputContext()
      : QObject(*(new QPlatformInputContextPrivate))
  {
+     // Delay initialization of cached input direction
+     // until super class has finished constructing.
+     QMetaObject::invokeMethod(this, [this]{
+         m_inputDirection = inputDirection();
+     }, Qt::QueuedConnection);
  }

That member takes sizeof(QPlatformInputContext) from 16 to 24.

The plugin is compiled against the 6.2 headers, where the base is still 16 bytes — so QFcitxPlatformInputContext's first data member, watcher_, lands at offset 16. In the 6.11 QtGui it is actually running against, offset 16 is m_inputDirection. The two overlap. And because the write is queued, it lands after the plugin's constructor has already set watcher_.

The result: QObject::connect(fcitx::FcitxQtWatcher, fcitx::FcitxQtInputContextProxy): invalid nullptr parameter, then SIGSEGV at 0x10. No keystroke needed — and that constructor is the frame in the forum backtrace.

There's a second, independent break, straight from the shipped 26.05 tarball:

$ cd anki-26.05-linux-x86_64/app_packages/PyQt6/Qt6

# what the bundled plugin (ELF tag Qt_6.2) needs:
$ nm -DC plugins/platforminputcontexts/libfcitx5platforminputcontextplugin.so | grep handleExtendedKeyEvent
     U QWindowSystemInterface::handleExtendedKeyEvent(..., QString const&, bool, unsigned short, bool)@Qt_6_PRIVATE_API

# what the Qt sitting next to it (6.11.0) provides:
$ nm -DC lib/libQt6Gui.so.6 | grep handleExtendedKeyEvent
0000000000264510 T QWindowSystemInterface::handleExtendedKeyEvent(..., QString const&, bool, unsigned short)@@Qt_6_PRIVATE_API

Both files come out of the same installer. The trailing bool was dropped after 6.2, so the symbol the shipped plugin imports does not exist in the Qt it is shipped with. It gets past dlopen only because the plugin isn't BIND_NOW.

Reproduction

On a machine running fcitx5 with no fcitx4 installed at all — so the fcitx4 angle raised in the thread isn't in play — QT_IM_MODULE=fcitx, which is what makes Qt load this plugin at all → SIGSEGV; unset, the plugin isn't loaded and there's no crash, the same effect as deleting it.

Rebuilding the same fcitx5-qt 5.0.10 source against 6.11, with nothing else changed → no crash, and CJK input works.

xiaohan484 and others added 2 commits July 12, 2026 10:06
uv.lock carries four pyqt6-qt6 specifiers, one per qt/qt68/qt69/qt610 extra;
picking the first one relied on an ordering uv never promised. Ask uv for the
qt extra directly — the same one the build resolves (build/configure/src/python.rs).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
# Conflicts:
#	CONTRIBUTORS
@read-the-docs-community

read-the-docs-community Bot commented Jul 13, 2026

Copy link
Copy Markdown

Documentation build overview

📚 Anki | 🛠️ Build #33599320 | 📁 Comparing bf68cb0 against latest (cfe0077)

  🔍 Preview build  

4 files changed
± genindex.html
± autoapi/anki/collection/index.html
± autoapi/aqt/mediasrv/index.html
± autoapi/aqt/webview/index.html

@iamllama iamllama left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I confirmed that it builds the plugin properly, but was unable to reproduce the issue on a new ubuntu22 vm. fcitx5/mozc worked on there without it for me as well

@abdnh Thoughts?

@abdnh
abdnh self-requested a review July 14, 2026 15:08
@abdnh

abdnh commented Jul 15, 2026

Copy link
Copy Markdown
Member

Thank you. Let's give it a test in the next beta.

Can you make a change to the CONTRIBUTORS file from the GitHub UI to fix the CI check?

@xiaohan484

Copy link
Copy Markdown
Contributor Author

Done — updated it via the web UI, thanks!

@abdnh
abdnh merged commit f13c15a into ankitects:main Jul 15, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bundled Fcitx plugin causing segmentation faults on some systems

3 participants