#2170·kanata

Bug(macos): kanata-generated drag motion (move_mouse) does not cancel clickState on release, so a long drag can register as a click

Author: bambooRakeCreated Aug 27, 2026Updated Sep 7, 2026
Labelsbugmacos

Follow-up to #2155 (merged). In https://github.com/jtroo/kanata/pull/2155#issuecomment-5421500547 @jtroo agreed to leave travelled-drag cancellation for a follow-up; this issue tracks it.

Summary

When a drag is driven by kanata-generated pointer motion (move_mouse / movemouse-*, CGEventPost-based) rather than physical trackpad/mouse motion, macOS never cancels the click on release. The MouseUp goes out with clickState >= 1, so an application that distinguishes a click from a drag via NSEvent.clickCount sees a click at the end of a long drag.

Why this happens

macOS click-cancellation is decided by the motion path, not the button path (@MadPigeon's measurements on #2061 / #2155):

button motion clickState on MouseUp
physical physical 0 (cancels after ~single-digit px)
kanata HID physical 0
kanata HID CGEventPost (move_mouse) 1 (never cancels; still 1 at 596 px)

In the current code, DRAG_CANCELLED is only set by the event tap when it rewrites a physical MouseMoved into a *MouseDragged (src/oskbd/macos.rs, drag-assist path). move_mouse stamps the press's click_state on each synthesized drag event but never arms cancellation, so the release keeps the click count.

Scope / options

  • Preferred long-term fix: route move_mouse motion through the Virtual HID pointing device, so macOS performs click-cancellation itself (matches hardware, no emulation). This is the larger motion-path migration already noted as a follow-up in #2155.
  • Interim option on the CGEvent path: track travelled distance in move_mouse and arm DRAG_CANCELLED past the ~8 px window, mirroring the tap-side rule — with the caveat raised in #2155 that the physical baseline keeps the count on the drag events and only drops to 0 on MouseUp, and that on the pointing-sink path the release is a genuine HID event kanata does not stamp.

Related known limitations (from #2155, noted for context — not the subject of this issue)

  • CGEvent fallback: a short tap with ~8–15 px of hand drift can be misread as a cancelled click (displacement-only arming, no time allowance).
  • CGEvent → HID → CGEvent within one double-click interval can yield a 1,1,3 sequence.

Not this issue

  • #2165 (mouse event tap fails to install at boot from a LaunchDaemon, never retries) — tap install lifecycle, unrelated to clickState/drag.
  • #2147 (movemouse distance loss) and #2133 are tracked separately.

Environment where measured: macOS 26 / Apple Silicon, Karabiner-DriverKit-VirtualHIDDevice 8.0.0.