diff options
| author | Danilo M. <danix@danix.xyz> | 2026-08-04 10:03:06 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-08-04 12:54:30 +0200 |
| commit | 9016063582c75f6d9a5fbd770b1f0b2f363b3054 (patch) | |
| tree | 04d91c3ae539a1a2cfeae35376d8e505edcc0b69 /src | |
| parent | aa86d61c1a716c2e0a2cdc55ae396808eeb494d1 (diff) | |
| download | qtmaildir-9016063582c75f6d9a5fbd770b1f0b2f363b3054.tar.gz qtmaildir-9016063582c75f6d9a5fbd770b1f0b2f363b3054.zip | |
fix(completion): stop the key filter handing itself the key it forwards
Pressing Tab or an arrow key with the completion popup open crashed the
application outright.
QCoreApplication::sendEvent re-runs APPLICATION-level event filters. The
navigation branch forwarded the key to the popup from inside a filter
installed on qApp, so the very same event came back to the filter that
had just sent it. The popup was still visible, the popupVisible() guard
still passed, and it forwarded again: unbounded recursion ending in a
stack overflow rather than in any diagnosable error. Reproduced at 9176
recursive QueryCompleter::eventFilter frames, with a standalone Qt probe
confirming the re-entry independently.
Guard the filter with m_forwarding, checked before the switch so it
covers every branch rather than the navigation keys alone. A key the
filter is itself redelivering now falls through to the popup instead of
being claimed a second time.
The existing tests missed this because they exercised the accept path
without ever forwarding an event. arrowNavigationDoesNotRecurse drives
Down through the grabbing popup and asserts the selection actually
moved, so it fails on a fix that merely swallows the key; against the
old code it takes the process down with it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'src')
| -rw-r--r-- | src/querycompleter.cpp | 14 | ||||
| -rw-r--r-- | src/querycompleter.h | 5 |
2 files changed, 18 insertions, 1 deletions
diff --git a/src/querycompleter.cpp b/src/querycompleter.cpp index a6a1aeb..7e9dfd9 100644 --- a/src/querycompleter.cpp +++ b/src/querycompleter.cpp @@ -422,6 +422,12 @@ bool QueryCompleter::eventFilter(QObject *watched, QEvent *event) if (event->type() != QEvent::KeyPress || !popupVisible()) return QObject::eventFilter(watched, event); + // A key this filter is itself redelivering. sendEvent re-runs application + // event filters, so without this the forwarded key comes straight back and + // recurses until the stack is gone. + if (m_forwarding) + return QObject::eventFilter(watched, event); + auto *keyEvent = static_cast<QKeyEvent *>(event); switch (keyEvent->key()) { case Qt::Key_Tab: @@ -452,11 +458,17 @@ bool QueryCompleter::eventFilter(QObject *watched, QEvent *event) case Qt::Key_Up: case Qt::Key_Down: case Qt::Key_PageUp: - case Qt::Key_PageDown: + case Qt::Key_PageDown: { // Navigation belongs to the popup, which is not the focus widget while // the user is typing in the bar. + // + // m_forwarding is what keeps this from recursing; see the guard at the + // top of the filter. + m_forwarding = true; QCoreApplication::sendEvent(m_popup, event); + m_forwarding = false; return true; + } default: break; } diff --git a/src/querycompleter.h b/src/querycompleter.h index 6f658f2..27161bc 100644 --- a/src/querycompleter.h +++ b/src/querycompleter.h @@ -135,6 +135,11 @@ private: const Config &m_config; QStringList m_tags; + /// Set while the filter is redelivering a key to the popup. The filter is + /// installed on the application and sendEvent re-runs application filters, + /// so without this the forwarded key returns to the filter that sent it. + bool m_forwarding = false; + QCompleter *m_completer = nullptr; QStandardItemModel *m_model = nullptr; CompletionPopup *m_popup = nullptr; |
