Fix taps not producing clicks on noisy digitizers (#13) - #28
Draft
sapkra wants to merge 2 commits into
Draft
Conversation
A touch only produced a click on lift-off if `cursorTouchQualifiedForTap` was still set, and that flag was cleared the moment two consecutive HID reports differed by more than 0.1 mm. That is below the resolution of many digitizers and well below the shift of the reported contact centroid as a finger flattens onto the glass, so on affected panels every tap was read as the start of a drag: the cursor moved to the touch point and no click was ever generated. Hold-and-drag could never arm for the same reason, since it also required the tap flag. Measure both decisions against an anchor instead of the previous report: - A touch stays a tap until the finger leaves a slop radius around where it landed, configurable as "Tap Zone" (default 2.5 mm). - The hold clock runs while the finger stays within 1 mm of its anchor and restarts when it wanders off, so a slow deliberate drag still never turns into a hold. It is evaluated on every report rather than only on stationary ones, which a flickering phase used to swallow. - Scroll and drag no longer start until the tap slop has been exceeded, so digitizer noise can no longer leak a few pixels of scroll into a tap. The per-report threshold is kept, but only to classify the touch phase, where a small value keeps fine slow scrolling responsive. Also in the same paths: - Distances were computed from relative coordinates scaled by the panel width on both axes, which made every vertical threshold wrong on non-square panels. `-[TUCScreen millimetreDistanceBetweenRelativePoint:and:]` now scales each axis by its own physical extent. - Panels reporting a zero EDID physical size turned every mm threshold into 0 or infinity. `-[TUCScreen effectivePhysicalSize]` falls back to an assumed density. - Dropped a dead branch that re-posted `identifiedMultitouchGesture` after `stopCurrentGesture` had already cleared it. - Fixed the "$errorResistance" key typo that stopped Error Resistance from ever persisting. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The distance guard in the click-sequence counter compared the two signed axis deltas against the tolerance and required *both* to exceed it. Two quick taps therefore only started a new sequence when the second landed down and to the right of the first; in every other direction the guard could not fire, and the tap inherited click state 2. On a large touchscreen, where consecutive taps are naturally far apart, that turned any pair of quick taps anywhere on the glass into a double click. Compare the distance as a radius instead, which is what the "Double Click Zone" setting already describes. The counter is shared with the start of a drag, which deliberately inherits an elevated click state so that a drag right after a tap selects by word. That still works — the drag begins inside the tap slop, well within the zone — while a drag starting far from the last tap now correctly begins a plain drag instead of a word selection. This is the first release in which the tolerance actually binds, so the value range is adjusted to match: - The slider no longer offers 0 mm, which would mean two taps can never be close enough to double click, and a stored 0 from when the setting was inert is lifted to 1 on load. - Its ceiling goes from 8 to 16 mm. Until now the guard was effectively unbounded, so nothing tested how far apart a deliberate double tap lands on a wall-sized panel; the headroom keeps those devices working. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #13.
The bug
A touch only produces a click on lift-off if
cursorTouchQualifiedForTapis still set whenthe finger leaves the glass. That flag was cleared the moment two consecutive HID reports
differed by more than 0.1 mm:
0.1 mm is below the reporting resolution of many digitizers, and well below the shift of the
reported contact centroid as a finger flattens onto the glass and lifts off again. On panels
that cross that line, every tap was read as the start of a drag: the cursor moved to the
touch point and no click was ever generated. Hold-and-drag could never arm either, since it
also required the tap flag.
This matches the reports in the issue — including that "On Finger Drag → Point and Click"
works around it, because that mode clicks on
NSTouchPhaseEndedregardless of the flag (andcosts you one-finger scrolling in exchange).
Whether the threshold trips is a property of the panel, which is why the bug is
screen-specific rather than universal.
The fix
Measure the tap and hold decisions against an anchor point rather than against the
previous report:
configurable as Tap Zone (default 2.5 mm).
wanders off, so a slow deliberate drag still never turns into a hold. It is now evaluated
on every report instead of only stationary ones, which a flickering phase used to swallow.
leak a few pixels of stray scroll into a tap.
The per-report threshold stays, but only to classify the touch phase, where a small value
keeps fine slow scrolling responsive. Nothing consequential hangs off it anymore.
Also fixed
Three things in the same code paths, each with its own commit message detail:
both axes, so every vertical mm threshold was off by the aspect ratio.
-[TUCScreen millimetreDistanceBetweenRelativePoint:and:]scales each axis by its ownphysical extent.
into 0 or infinity — which would have made the tap slop infinite and disabled scrolling
outright.
-[TUCScreen effectivePhysicalSize]falls back to an assumed ~100 dpi."$errorResistance"key typo meant Error Resistance never persisted.identifiedMultitouchGestureafterstopCurrentGesturehad already cleared it.stopMagnifyingalready posts theterminating magnify, so nothing is lost.
Second commit: quick taps far apart registering as a double click
Found while tracing the tap path. The distance guard in the click-sequence counter compared
the two signed axis deltas and required both to exceed the tolerance:
So a new sequence only started when the second tap landed down and to the right of the
first. In every other direction the guard could not fire and the tap inherited click state 2
— meaning any two quick taps anywhere on a large screen became a double click.
Now compared as a radius, which is what the "Double Click Zone" setting already describes.
The counter is shared with the start of a drag, which deliberately inherits an elevated click
state so a drag right after a tap selects by word. That still works — the drag begins inside
the tap slop, well within the zone — while a drag starting far from the last tap now
correctly begins a plain drag instead of a word selection.
Since this is the first release where the tolerance actually binds, the range is adjusted to
match: the slider no longer offers 0 mm (unsatisfiable; a stored 0 from when the setting was
inert is lifted to 1 on load), and the ceiling goes 8 → 16 mm. Until now the guard was
effectively unbounded, so nothing has ever tested how far apart a deliberate double tap lands
on a wall-sized panel — the headroom keeps those devices working. The default stays 8.
Compatibility
This is a universal driver, so the intent throughout is that no working configuration
changes behavior:
TouchDownalready places the cursor before the dead zoneAnyone whose screen worked before had a digitizer quiet enough that the old 0.1 mm check was
never tripping, so the new slop radius doesn't change what they see.
Testing
Builds clean with no new warnings. I don't have a touchscreen to verify against yet (that's why its a draft right now, will order one soon), so the
behavior above is traced through the code rather than measured — worth a check on real
hardware before merging, particularly the double-click zone, which is the easiest to feel by
hand. The debug overlay will show whether taps now stay inside the slop radius.
Two notes on things I deliberately left out, in case you'd rather they were in scope