From 87f5587bd40f07fac621cfaf3c167f275005af38 Mon Sep 17 00:00:00 2001 From: Levi Neely Date: Wed, 7 Oct 2026 17:18:01 +0200 Subject: [PATCH] Chord: suppress middle-click primary paste on both press and release The X11 primary-selection paste was leaking through on the middle-button release because only the press was consumed. Now the middle button is consumed inside the editor on BOTH press and release (tracked via m_swallowMiddleRelease), fully suppressing primary paste there. The chord Cut uses edit_cut, which writes to the standard KDE clipboard (Klipper), so paste is the normal Ctrl+V / palette paste. All 5 ctest suites green. --- docs/PLAN.md | 11 +++++++---- src/plugin/ollieplugin.cpp | 29 +++++++++++++++++++++-------- src/plugin/ollieplugin.h | 1 + 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/docs/PLAN.md b/docs/PLAN.md index 903414b..d8c3871 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -131,10 +131,13 @@ Therefore the palette is replaced, not extended, and the replacement is built on ### Mouse chording (Acme-style) — DONE (first increment) - Left+middle chord = **Cut**: with the left button held while selecting (button 1 anchor), a tap of the middle button (button 2) cuts the current - selection. Driven by the same application event filter — it tracks the left - button held inside the view and, on a middle press while held, triggers - `edit_cut` and consumes the event. Middle-click alone (no left held) still - does the usual primary-selection paste. + selection via `edit_cut`, which uses the **standard KDE clipboard** + (Klipper-backed), not the X11 primary selection. +- **Middle-click primary paste is suppressed** inside the editor. Kate pastes + the primary selection on the middle event; the filter consumes the middle + button on **both press and release** (swallowing only the press let the paste + leak through on release). So middle-click no longer pastes in the editor — + paste is the normal `Ctrl+V` / palette paste from the KDE clipboard. - Right-button chords are intentionally **not** ported — the right button is the radial gesture. (Revisit later.) - The radial gesture is suppressed while the left button is held, so an RMB tap diff --git a/src/plugin/ollieplugin.cpp b/src/plugin/ollieplugin.cpp index c39b2ba..070f849 100644 --- a/src/plugin/ollieplugin.cpp +++ b/src/plugin/ollieplugin.cpp @@ -347,25 +347,38 @@ bool OllieView::eventFilter(QObject *obj, QEvent *event) } } - // Acme-style left+middle chord = Cut. Button 1 (left) held while selecting - // is the anchor; a tap of button 2 (middle) cuts the current selection. + // Acme-style left+middle chord = Cut, and suppression of X11 primary- + // selection paste. Middle-button events inside the view are consumed on + // BOTH press and release (Kate pastes the primary selection on the middle + // event; swallowing only the press lets the paste leak through on release). + // When the left button is held, the middle tap cuts the current selection + // via edit_cut, which uses the standard KDE clipboard (Klipper-backed). // Right-button chords are intentionally not ported (RMB is the radial). - // Middle-click alone (no left held) still does the usual primary paste. if (event->type() == QEvent::MouseButtonRelease) { - if (static_cast(event)->button() == Qt::LeftButton) { + auto *mev = static_cast(event); + if (mev->button() == Qt::LeftButton) { m_leftDownInView = false; } + if (mev->button() == Qt::MiddleButton && m_swallowMiddleRelease) { + m_swallowMiddleRelease = false; + return true; // suppress the primary paste that fires on release + } } else if (event->type() == QEvent::MouseButtonPress) { auto *mev = static_cast(event); KTextEditor::View *v = m_mainWindow->activeView(); auto *w = qobject_cast(obj); const bool inView = v && w && v->isAncestorOf(w); - if (mev->button() == Qt::MiddleButton && m_leftDownInView && inView) { - if (QAction *cut = findActionByName(QStringLiteral("edit_cut"))) { - cut->trigger(); + if (mev->button() == Qt::MiddleButton && inView) { + if (m_leftDownInView) { + if (QAction *cut = findActionByName(QStringLiteral("edit_cut"))) { + cut->trigger(); + } } - return true; // consume: this is the chord, not a primary paste + // Consume press (and its release) to suppress middle-click primary + // paste entirely inside the editor. + m_swallowMiddleRelease = true; + return true; } if (mev->button() == Qt::LeftButton && inView) { m_leftDownInView = true; // anchor set; selection proceeds normally diff --git a/src/plugin/ollieplugin.h b/src/plugin/ollieplugin.h index cf449a1..9d0db1d 100644 --- a/src/plugin/ollieplugin.h +++ b/src/plugin/ollieplugin.h @@ -90,6 +90,7 @@ private: QHash m_buttonToRadial; // Qt::MouseButton (int) -> def index bool m_gestureActive = false; // an RMB drag gesture is in progress bool m_leftDownInView = false; // LMB held inside the view (Acme chord anchor) + bool m_swallowMiddleRelease = false; // swallow the release paired with a consumed middle press QHash> m_actionsById; };