Bug(macos): kanata-generated drag motion (move_mouse) does not cancel clickState on release, so a long drag can register as a click
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_mousemotion 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_mouseand armDRAG_CANCELLEDpast 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 onMouseUp, 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,3sequence.
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 (
movemousedistance loss) and #2133 are tracked separately.
Environment where measured: macOS 26 / Apple Silicon, Karabiner-DriverKit-VirtualHIDDevice 8.0.0.
Source: jtroo/kanata