Skip to content

DisableOnStylus can split one touch gesture into multiple sequences after stylus proximity-out #218

Description

@serika12345

Summary

With Touchscreen.DisableOnStylus = true, one physical multi-touch
gesture can be split into multiple independent touch sequences shortly
after using the stylus.

This was most visible in Krita: one intentional two-finger Undo gesture
occasionally triggered Undo three times. Raw evdev and internal iptsd
instrumentation showed that Krita correctly received three separate
two-contact sequences. The gesture was already fragmented before it
reached Krita.

Environment

  • Device: Microsoft Surface Pro 7
  • Kernel: Linux 6.19.8
  • Distribution: NixOS 26.05.20260722.b3fe958
  • iptsd: 3.1.0
  • Application used for reproduction: Krita 6.0.2.1
  • Stylus: Microsoft Surface Pen, Model 1776

Relevant configuration:

[Touchscreen]
DisableOnPalm = true
DisableOnStylus = true

My build also contains a local 300 ms post-stylus touch holdoff. That
change only delays touch re-enabling; it does not alter the unconditional
m_touch->disable() call described below.

Steps to reproduce

  1. Enable Touchscreen.DisableOnStylus.
  2. Draw a few strokes with the stylus.
  3. Move the stylus out of proximity.
  4. Perform one two-finger tap on the touchscreen.
  5. Observe the touch events with evdev, or bind a two-finger tap to Undo
    in Krita.

The problem is timing-dependent, so it may take several attempts.

Expected behavior

One physical two-finger tap should be emitted as one continuous touch
sequence and trigger one Undo.

Actual behavior

One physical two-finger tap can be emitted as several independent
two-contact sequences, triggering Undo multiple times.

In one raw evdev capture, a single physical tap was emitted as:

two contacts down  11.157549
two contacts up    11.160090

two contacts down  11.166247
two contacts up    11.166679

two contacts down  11.188842
two contacts up    11.194638

Krita received three corresponding TouchBegin / TouchEnd pairs.

I also added temporary instrumentation after search_lifted(). During
another reproduction it reported:

16:03:18.942 contacts=2 blocked=false current=2 last=0
16:03:18.966 contacts=2 blocked=false current=2 last=0
16:03:18.972 contacts=2 blocked=false current=2 last=0

Both contacts were valid=1 and stable=1. There were no zero-contact
frames between these entries, and palm blocking was never active.
Nevertheless, the previous-contact state was cleared between frames.

Cause

Daemon::on_stylus() disables the touch device for every stylus sample,
without checking stylus.proximity:

https://github.com/linux-surface/iptsd/blob/v3.1.0/src/apps/daemon/daemon.hpp#L79-L90

TouchDevice::disable() lifts all active contacts and clears all contact
tracking state:

https://github.com/linux-surface/iptsd/blob/v3.1.0/src/apps/daemon/touch.hpp#L166-L179

Therefore, a non-proximity stylus sample arriving during a touch gesture
produces an artificial touch release. The following touch frame sees the
stylus as inactive, re-enables the touchscreen, and emits the same
physical fingers as a new gesture.

This behavior appears to have been introduced by the 2023 daemon
refactoring:

cff1189

Before that change, the heatmap handler checked whether the stylus was
actually active before disabling touch.

Minimal fix tested

I tested limiting disable() to the current proximity state or the
transition out of the previous proximity state:

if (m_config.touchscreen_disable_on_stylus && m_touch.has_value()) {
    if ((stylus.proximity || m_stylus->active()) &&
        m_touch->enabled())
        m_touch->disable();
}

At this point m_stylus->active() still contains the previous sample
because m_stylus->update(stylus) occurs afterward.

This preserves the intended behavior:

  • current sample is in proximity: disable touch immediately;
  • previous sample was in proximity: remain disabled through the
    proximity-out transition;
  • current and previous samples are both non-proximity: do not repeatedly
    disable and clear an active touch gesture.

After applying this condition, the same reproduction consistently
produced one Undo for one two-finger tap. Stylus-based touch suppression
and the post-stylus holdoff continued to work.

Related reports

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions