Merge branch 'firmware25' into feat/trackpad

This commit is contained in:
Florian Didron
2025-10-15 16:23:28 +07:00
4 changed files with 135 additions and 8 deletions

View File

@@ -1,6 +1,31 @@
// Copyright 2025 ZSA Technology Labs, Inc <contact@zsa.io>
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Navigator Trackball Smooth Scrolling
*
* Enhanced scrolling algorithm that eliminates deadzones and provides natural,
* responsive scrolling for both slow and fast movements.
*
* Key Features:
* - No initial deadzone - scrolling starts immediately with any movement
* - Smooth acceleration - speed increases naturally with faster movement
* - Fractional accumulation - sub-pixel movements accumulate until triggering scroll
* - Reduced jitter - consistent consumption prevents oscillation
*
* Configuration Parameters (add to keymap config.h):
* - NAVIGATOR_SCROLL_DIVIDER: Lower = more sensitive (default: 10)
* - NAVIGATOR_SCROLL_THRESHOLD: Minimum to scroll (default: 0f)
* - NAVIGATOR_SCROLL_ACCELERATION: Speed multiplier (default: 1.5f)
* - NAVIGATOR_SCROLL_MAX_SPEED: Maximum speed limit (default: 8.0f)
*
* Algorithm:
* 1. Accumulate input as floating-point values
* 2. When accumulated >= 1.0, trigger scrolling with acceleration
* 3. Subtract exactly 1.0 from accumulation regardless of output
* 4. Gentle decay (2% per frame) only after 20 frames of inactivity
*/
#include "quantum.h"
#include "navigator.h"
@@ -25,17 +50,76 @@ report_mouse_t pointing_device_task_kb(report_mouse_t mouse_report) {
mouse_report.y /= NAVIGATOR_AIM_DIVIDER;
}
if (set_scrolling) {
// Accumulate scroll movement
scroll_accumulated_h += (float)mouse_report.x / NAVIGATOR_SCROLL_DIVIDER;
scroll_accumulated_v += (float)mouse_report.y / NAVIGATOR_SCROLL_DIVIDER;
mouse_report.h = (int8_t)scroll_accumulated_h;
#ifdef NAVIGATOR_SCROLL_INVERT
mouse_report.v = (int8_t)-scroll_accumulated_v;
// This allows fractional accumulation to build up before triggering scroll
float abs_h = (scroll_accumulated_h < 0) ? -scroll_accumulated_h : scroll_accumulated_h;
float abs_v = (scroll_accumulated_v < 0) ? -scroll_accumulated_v : scroll_accumulated_v;
float scroll_h = 0.0f;
float scroll_v = 0.0f;
if (abs_h >= 1.0f) {
// Simple acceleration for faster movements
float speed_h = 1.0f + ((abs_h - 1.0f) * NAVIGATOR_SCROLL_ACCELERATION);
if (speed_h > NAVIGATOR_SCROLL_MAX_SPEED) {
speed_h = NAVIGATOR_SCROLL_MAX_SPEED;
}
scroll_h = (scroll_accumulated_h > 0) ? speed_h : -speed_h;
}
if (abs_v >= 1.0f) {
float speed_v = 1.0f + ((abs_v - 1.0f) * NAVIGATOR_SCROLL_ACCELERATION);
if (speed_v > NAVIGATOR_SCROLL_MAX_SPEED) {
speed_v = NAVIGATOR_SCROLL_MAX_SPEED;
}
scroll_v = (scroll_accumulated_v > 0) ? speed_v : -speed_v;
}
#ifdef NAVIGATOR_SCROLL_INVERT_X
mouse_report.h = (int8_t)scroll_h;
#else
mouse_report.v = (int8_t)scroll_accumulated_v;
mouse_report.h = (int8_t)-scroll_h;
#endif
scroll_accumulated_h -= (int8_t)scroll_accumulated_h;
scroll_accumulated_v -= (int8_t)scroll_accumulated_v;
#ifdef NAVIGATOR_SCROLL_INVERT_Y
mouse_report.v = (int8_t)-scroll_v;
#else
mouse_report.v = (int8_t)scroll_v;
#endif
// Subtract proportional to the base scroll (before acceleration) to prevent jitter
if (abs_h >= 1.0f) {
scroll_accumulated_h -= (scroll_accumulated_h > 0) ? 1.0f : -1.0f;
}
if (abs_v >= 1.0f) {
scroll_accumulated_v -= (scroll_accumulated_v > 0) ? 1.0f : -1.0f;
}
// Much gentler decay and only after longer idle periods
static uint8_t idle_counter_h = 0, idle_counter_v = 0;
if (mouse_report.x == 0 && mouse_report.h == 0) {
idle_counter_h++;
if (idle_counter_h > 20) { // Only decay after 20 frames of no input
scroll_accumulated_h *= 0.98f; // Very gentle decay
}
} else {
idle_counter_h = 0;
}
if (mouse_report.y == 0 && mouse_report.v == 0) {
idle_counter_v++;
if (idle_counter_v > 20) {
scroll_accumulated_v *= 0.98f;
}
} else {
idle_counter_v = 0;
}
mouse_report.x = 0;
mouse_report.y = 0;

View File

@@ -6,6 +6,19 @@
#define NAVIGATOR_SCROLL_DIVIDER 10
#endif
#ifndef NAVIGATOR_SCROLL_THRESHOLD
#define NAVIGATOR_SCROLL_THRESHOLD 0f
#endif
#ifndef NAVIGATOR_SCROLL_ACCELERATION
#define NAVIGATOR_SCROLL_ACCELERATION 1.5f
#endif
#ifndef NAVIGATOR_SCROLL_MAX_SPEED
#define NAVIGATOR_SCROLL_MAX_SPEED 8.0f
#endif
#ifdef POINTING_DEVICE_DRIVER_navigator_trackball
#define NAVIGATOR_TURBO_MULTIPLIER 3
#define NAVIGATOR_AIM_DIVIDER 3

View File

@@ -32,6 +32,9 @@ static auto_mouse_context_t auto_mouse_context = {
.config.layer = (uint8_t)(AUTO_MOUSE_DEFAULT_LAYER),
.config.timeout = (uint16_t)(AUTO_MOUSE_TIME),
.config.debounce = (uint8_t)(AUTO_MOUSE_DEBOUNCE),
#ifdef AUTO_MOUSE_ONESHOT
.one_shot = false,
#endif
};
/* local functions */
@@ -49,7 +52,11 @@ static inline bool layer_hold_check(void) {
/* check all layer activation criteria */
bool is_auto_mouse_active(void) {
#ifdef AUTO_MOUSE_ONESHOT
return auto_mouse_context.status.is_activated || auto_mouse_context.status.mouse_key_tracker || layer_hold_check() || auto_mouse_context.one_shot;
#else
return auto_mouse_context.status.is_activated || auto_mouse_context.status.mouse_key_tracker || layer_hold_check();
#endif
}
/**
@@ -267,6 +274,11 @@ void pointing_device_task_auto_mouse(report_mouse_t mouse_report) {
if (!layer_state_is((AUTO_MOUSE_TARGET_LAYER))) {
layer_on((AUTO_MOUSE_TARGET_LAYER));
}
#ifdef AUTO_MOUSE_ONESHOT
if (!auto_mouse_context.one_shot) {
auto_mouse_context.one_shot = true;
}
#endif
} else if (layer_state_is((AUTO_MOUSE_TARGET_LAYER)) && timer_elapsed(auto_mouse_context.timer.active) > auto_mouse_context.config.timeout) {
#ifdef LAYER_LOCK_ENABLE
if(is_layer_locked(AUTO_MOUSE_DEFAULT_LAYER)) return;
@@ -343,7 +355,8 @@ bool process_auto_mouse(uint16_t keycode, keyrecord_t* record) {
if (!(AUTO_MOUSE_ENABLED)) return true;
switch (keycode) {
// Skip Mod keys and layer lock to avoid layer reset
// Skip Mod keys, KC_NO, and layer lock to avoid layer reset
case KC_NO:
case KC_LEFT_CTRL ... KC_RIGHT_GUI:
case QK_MODS ... QK_MODS_MAX:
case QK_LLCK:
@@ -428,6 +441,13 @@ bool process_auto_mouse(uint16_t keycode, keyrecord_t* record) {
auto_mouse_context.status.mouse_key_tracker = 0;
dprintf("key tracker error (<0) \n");
}
#ifdef AUTO_MOUSE_ONESHOT
if (is_auto_mouse_active()) {
auto_mouse_context.one_shot = false;
}
#endif
return true;
}
@@ -442,7 +462,14 @@ bool process_auto_mouse(uint16_t keycode, keyrecord_t* record) {
*/
static bool is_mouse_record(uint16_t keycode, keyrecord_t* record) {
// allow for keyboard to hook in and override if need be
if (is_mouse_record_kb(keycode, record) || IS_MOUSEKEY(keycode)) return true;
if (is_mouse_record_kb(keycode, record)) return true;
// if it's a mouse key, only treat it as a mouse record if we're currently on the auto mouse target layer
// this prevents mouse keys from activating the auto mouse layer when pressed on other layers
if (IS_MOUSEKEY(keycode)) {
return layer_state_is((AUTO_MOUSE_TARGET_LAYER));
}
return false;
}

View File

@@ -73,6 +73,9 @@ typedef struct {
int8_t mouse_key_tracker;
} status;
total_mouse_movement_t total_mouse_movement;
#ifdef AUTO_MOUSE_ONESHOT
bool one_shot;
#endif
} auto_mouse_context_t;
/* ----------Set up and control------------------------------------------------------------------------------ */