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.
This commit is contained in:
Levi Neely 2026-10-07 17:18:01 +02:00
parent 75367f85ef
commit 87f5587bd4
3 changed files with 29 additions and 12 deletions

View File

@ -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

View File

@ -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<QMouseEvent *>(event)->button() == Qt::LeftButton) {
auto *mev = static_cast<QMouseEvent *>(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<QMouseEvent *>(event);
KTextEditor::View *v = m_mainWindow->activeView();
auto *w = qobject_cast<QWidget *>(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

View File

@ -90,6 +90,7 @@ private:
QHash<int, int> 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<QString, QPointer<QAction>> m_actionsById;
};