https://bugs.kde.org/show_bug.cgi?id=523673

--- Comment #3 from [email protected] ---
I hit the same thing (KWin 6.7.4, Arch, Wayland, Huawei MateBook X Pro 2024
with an ICNT9288 touchscreen): cursor moves, keyboard works, but no client gets
any wl_pointer event (no enter/button/axis) from either the touchpad or a USB
mouse. It happened twice in three days, both times right after closing the lid
→ suspend → opening the lid. I tracked down the cause and can reproduce it
every time.

## Root cause

While it was broken I attached gdb to kwin_wayland:

- `SeatInterface::isTouchSequence()` == true, but there was no physical touch
- so `PointerInputRedirection::focusUpdatesBlocked()` == true → pointer focus
stays nullptr permanently
- (`areButtonsPressed()`, `isSelectingWindow()`, `isDrag()` were all false)
- `TouchInputRedirection::cancel()` did nothing, because `m_activeTouchPoints`
was already empty

How the seat and TouchInputRedirection get out of sync:

1. A touch down happens while the screen is on. It passes the filters and
`ForwardInputFilter` (or `LockScreenFilter`) calls `seat->notifyTouchDown()`.
2. The outputs turn off while that touch is still held. `Workspace` installs
`DpmsInputEventFilter` (the same thing happens with
`PlaceholderInputEventFilter` when the last output goes away).
3. The touch up comes in. `TouchInputRedirection::processUp()` removes the id
from `m_activeTouchPoints`, then runs the filters.
`DpmsInputEventFilter::touchUp()` always returns true, so
`seat->notifyTouchUp()` is never called.
4. The outputs come back on and the DPMS filter is removed. The seat still
holds the TouchPoint, and nothing ever clears it: `cancel()` returns early
because `m_activeTouchPoints` is empty, and removing the touch device doesn't
clear it either.

On a laptop this is easy to hit: your hand is on the screen bezel or glass
while you close the lid, KWin turns the screens off before suspend, and the
finger lifts while DPMS is off.

## Reproducer (no physical hardware needed)

Run as root: a uinput multitouch device (INPUT_PROP_DIRECT,
ABS_MT_SLOT/TRACKING_ID/POSITION_X/Y, BTN_TOUCH), plus `kscreen-doctor` running
in the user session:

```
touch down slot 0 at screen center
kscreen-doctor --dpms off
sleep 2
touch up slot 0
kscreen-doctor --dpms on
→ SeatInterface::isTouchSequence() == 1, focusUpdatesBlocked() == 1, no clicks
reach any client
```

Reproduced 5/6 runs on a live 6.7.4 session (it failed once). The control run
(same down/up without DPMS) is clean. The relevant code
(`dpmsinputeventfilter.cpp`, `placeholderinputeventfilter.cpp`,
`touch_input.cpp`, the touch parts of `input.cpp`) has the same semantics in
6.8.1 and master.

Why the existing workarounds work:
- A one-finger tap fixes it when the leaked id is 0. `notifyTouchDown()`
overwrites `touchPoints[id]` (the `Q_ASSERT(!contains(id))` is compiled out)
and the following up erases it.
- A three-finger tap always fixes it. `GlobalShortcutFilter` sends a synthetic
`touchCancel`, `ForwardInputFilter` calls `seat->notifyTouchCancel()`, and that
clears all points.
- A VT switch also fixes it (as reported here).

## Fix (with integration tests)

Cancel the ongoing touch sequence before installing either filter. At that
point TouchInputRedirection still has the points in `m_activeTouchPoints`, so
the cancel goes through the filter chain to `ForwardInputFilter` ->
`seat->notifyTouchCancel()`. The touch up that comes later is then dropped in
`processUp()` because the id is no longer active.

```cpp
// Workspace::requestDpmsState()
    } else {
        input()->touch()->cancel();
        m_dpmsFilter = std::make_unique<DpmsInputEventFilter>();
        input()->installInputEventFilter(m_dpmsFilter.get());
// Workspace::updateOutputs(), same before installing m_placeholderFilter
```

I added two integration tests to `autotests/integration/touch_input_test.cpp`
(on top of v6.8.1):
- `testDpmsOffDuringTouch`: touch down -> `requestDpmsState(Off)` -> touch up
-> `requestDpmsState(On)`. Asserts `!seat->isTouchSequence()`,
`!focusUpdatesBlocked()` and that the client gets the cancel.
- `testOutputsRemovedDuringTouch`: the same with `Test::setOutputConfig({})`
(placeholder output).

Without the fix, both tests fail on
`!waylandServer()->seat()->isTouchSequence()`. With the fix they pass.
testTouchInput, testLockScreen, testOutputChanges, testScreenEdges and
testPointerInput (307 tests) all pass under dbus-run-session.
testPointerInput::testTabletCursorSync is flaky in my environment both with and
without the patch.

I also built the Arch kwin 6.8.1 package with the fix and ran it in a live
session. The same uinput + DPMS reproducer came out clean 5/5. Checking each
step: the seat has the touch point while the finger is held, it is cancelled as
soon as DPMS turns off, and nothing is left after the up / DPMS on.

The patch with the tests is attached (diff against v6.8.1, applies with `git
apply`). I can open a merge request on invent if you prefer.

-- 
You are receiving this mail because:
You are watching all bug changes.

Reply via email to