Skip to content

Overhaul Azoteq IQS5XX touchpad scroll and drag behavior#8

Open
rnewman wants to merge 6 commits into
ilc:vialfrom
rnewman:rnewman-azo
Open

Overhaul Azoteq IQS5XX touchpad scroll and drag behavior#8
rnewman wants to merge 6 commits into
ilc:vialfrom
rnewman:rnewman-azo

Conversation

@rnewman
Copy link
Copy Markdown

@rnewman rnewman commented Mar 26, 2026

It is, in general, impossible for a QMK keyboard to implement smooth touchpad
scrolling on macOS: it ignores supplied resolution multipliers, doesn't ask for
reports, and seemingly only allows Magic Touchpads to deliver a good
experience. The recommended configuration is to use an app like SmoothScroll or
Mos to convert mouse wheel-style scrolls into smooth scroll; the work expected
of the firmware itself is to supply a reasonable range of scroll values.

This commit does so, though you might still wish to adjust configured values.

It includes three major changes to improve touchpad usability on macOS:

  1. Configurable scroll speed reduction via axis_scale: replace naive integer
    division (which truncated small per-report values to zero, causing a
    "dead zone then jump" effect) with a remainder-preserving accumulator.
    Uses CONSTRAIN_HID_HV (new macro) instead of CONSTRAIN_HID: clamps to the
    proper HV range (int16 when WHEEL_EXTENDED_REPORT is defined, int8 otherwise).

  2. Natural scrolling by default: invert the Y axis for two-finger scroll gestures
    so that content follows finger direction (swipe down = scroll down),
    matching macOS trackpad convention.

  3. Replace press_and_hold drag with tap-to-drag:

    Problem: press_and_hold fired BUTTON1 whenever a finger lingered on
    the pad beyond HOLD_TIME, making it impossible to move the cursor
    without accidentally drag-selecting. Disabling press_and_hold at the
    IQS5XX hardware level caused the gesture engine to stall and stop
    detecting taps entirely.

    Solution (two parts):

    a) Decouple hardware gesture detection from click behavior. A new
    AZOTEQ_IQS5XX_PRESS_AND_HOLD_AS_CLICK define (defaulting to
    PRESS_AND_HOLD_ENABLE for backward compat) controls whether
    press_and_hold events produce BUTTON1 in the report handler.
    The hardware gesture stays enabled so the IQS5XX state machine
    remains healthy, but we simply ignore the event.

    b) Implement macOS-style tap-to-drag: after a single_tap, BUTTON1
    is held continuously for a configurable window (TAP_DRAG_WINDOW_MS,
    default 200ms). If a finger touches the pad again within that
    window, drag mode engages and BUTTON1 remains held until the
    finger lifts. The continuous hold during the window is critical —
    an earlier implementation that released BUTTON1 between tap and
    drag worked for text selection but failed for macOS window
    dragging, which requires an unbroken button press from the
    initial click on the title bar.

    TAP_TIME raised from 100ms to 200ms since there is no longer a need
    to keep it short to disambiguate taps from press_and_hold.
    HOLD_TIME remains at default 300ms.

New configurable defines (all with backward-compatible defaults):

  • AZOTEQ_IQS5XX_SCROLL_DIVISOR (default 1, configured to 8 for Svalboard)
  • AZOTEQ_IQS5XX_PRESS_AND_HOLD_AS_CLICK (default matches ENABLE)
  • AZOTEQ_IQS5XX_TAP_DRAG_ENABLE (default false)
  • AZOTEQ_IQS5XX_TAP_DRAG_WINDOW_MS (default 200)

ziasquinn and others added 6 commits January 13, 2026 00:24
Increased HOLD_TIME from aggressive 20ms, to more forgiving 150ms. Since TAP_TIME default, without defining, is 150ms, also decrease this appropriately. Before, they were confusing each other.
Removed TAP_TIME and HOLD_TIME definitions to return behavior to baseline, longer 300ms hold-to-tap for dragging/selecting is the price for a consistent and intuitive interface.
Increased HOLD_TIME from aggressive 20ms, to more forgiving 150ms. Since TAP_TIME (when default/without defining), is 150ms, also decrease this appropriately. Before, they were confusing each other.
AZOTEQ config.h changes:

TAP_TIME = 100ms (default 150ms)
HOLD_TIME = 200ms (default 300ms)
It is, in general, impossible for a QMK keyboard to implement smooth touchpad
scrolling on macOS: it ignores supplied resolution multipliers, doesn't ask for
reports, and seemingly only allows Magic Touchpads to deliver a good
experience. The recommended configuration is to use an app like SmoothScroll or
Mos to convert mouse wheel-style scrolls into smooth scroll; the work expected
of the firmware itself is to supply a reasonable range of scroll values.

This commit does so, though you might still wish to adjust configured values.

It includes three major changes to improve touchpad usability on macOS:

1. Configurable scroll speed reduction via `axis_scale`: replace naive integer
   division (which truncated small per-report values to zero, causing a
   "dead zone then jump" effect) with a remainder-preserving accumulator.
   Uses `CONSTRAIN_HID_HV` (new macro) instead of `CONSTRAIN_HID`: clamps to the
   proper HV range (`int16` when `WHEEL_EXTENDED_REPORT` is defined, `int8` otherwise).

2. Natural scrolling by default: invert the Y axis for two-finger scroll gestures
   so that content follows finger direction (swipe down = scroll down),
   matching macOS trackpad convention.

3. Replace press_and_hold drag with tap-to-drag:

   Problem: press_and_hold fired BUTTON1 whenever a finger lingered on
   the pad beyond HOLD_TIME, making it impossible to move the cursor
   without accidentally drag-selecting. Disabling press_and_hold at the
   IQS5XX hardware level caused the gesture engine to stall and stop
   detecting taps entirely.

   Solution (two parts):

   a) Decouple hardware gesture detection from click behavior. A new
      `AZOTEQ_IQS5XX_PRESS_AND_HOLD_AS_CLICK` define (defaulting to
      `PRESS_AND_HOLD_ENABLE` for backward compat) controls whether
      press_and_hold events produce BUTTON1 in the report handler.
      The hardware gesture stays enabled so the IQS5XX state machine
      remains healthy, but we simply ignore the event.

   b) Implement macOS-style tap-to-drag: after a single_tap, BUTTON1
      is held continuously for a configurable window (`TAP_DRAG_WINDOW_MS`,
      default 200ms). If a finger touches the pad again within that
      window, drag mode engages and BUTTON1 remains held until the
      finger lifts. The continuous hold during the window is critical —
      an earlier implementation that released BUTTON1 between tap and
      drag worked for text selection but failed for macOS window
      dragging, which requires an unbroken button press from the
      initial click on the title bar.

   `TAP_TIME` raised from 100ms to 200ms since there is no longer a need
   to keep it short to disambiguate taps from press_and_hold.
   `HOLD_TIME` remains at default 300ms.

New configurable defines (all with backward-compatible defaults):
  - AZOTEQ_IQS5XX_SCROLL_DIVISOR (default 1, configured to 8 for Svalboard)
  - AZOTEQ_IQS5XX_PRESS_AND_HOLD_AS_CLICK (default matches ENABLE)
  - AZOTEQ_IQS5XX_TAP_DRAG_ENABLE (default false)
  - AZOTEQ_IQS5XX_TAP_DRAG_WINDOW_MS (default 200)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants