diff --git a/daemon/x52d.conf b/daemon/x52d.conf index b6a11cb..889557e 100644 --- a/daemon/x52d.conf +++ b/daemon/x52d.conf @@ -82,12 +82,44 @@ LED=128 # Enabled controls whether the virtual mouse is enabled or not. Enabled=yes -# Speed is proportional to the speed of updates to the virtual mouse +# Sensitivity is the sensitivity percentage of the virtual mouse. This +# replaces the old Speed option, and is a percentage value by which to +# scale the input. The sensitivity can vary from 10% to 500%. +Sensitivity=100 + +# DEPRECATED: Speed is proportional to the speed of updates to the virtual mouse +# This used a calculation with delays and multiplication factors to simulate +# the mouse moves, but it felt choppy at lower speeds. Speed=0 # ReverseScroll reverses the direction of the virtual scroll wheel ReverseScroll=no +# Isometric mode controls if the mouse movement is computed based on +# both X and Y movements. If enabled, the behavior is similar to the +# mouse nubs found on some laptops. Otherwise, the X and Y movements +# are independent of each other. +IsometricMode=no + +# Curve factor controls the speed curve in an exponential manner, so +# that the user can get finer control at the lower end of motion, while +# increasing speeds at the upper end. Values range from 1-5, with the +# following descriptions. Values are clamped in this range. +# 1: Linear motion - no curve +# 2: Soft curve: slight dampening in the lower ranges +# 3: Standard: Feels like a Thinkpad +# 4: Precision: heavy dampening in lower ranges, high speed elsewhere +# 5: Aggressive: "sniper" mode in the lower rnages, "flick" elsewhere +CurveFactor=3 + +# Deadzone is a configurable value from 0-11, with 0 being no deadzone +# and the deadzone size increasing with increasing values. This is useful +# when there is a loose thumbstick and you want to restrict the motion +# when there's no user input. A deadzone of 0 is perfectly fine for a +# new joystick, but keep in mind that the higher values will require +# you to push more to get any motion out of the virtual mouse. +Deadzone=0 + ###################################################################### # Profiles - only valid on Linux ###################################################################### diff --git a/daemon/x52d_config.def b/daemon/x52d_config.def index 3ba713b..7517e2a 100644 --- a/daemon/x52d_config.def +++ b/daemon/x52d_config.def @@ -71,13 +71,26 @@ CFG(Brightness, LED, brightness[1], int, 128) // Enabled controls whether the virtual mouse is enabled or not. CFG(Mouse, Enabled, mouse_enabled, bool, true) -// Speed is a value that is proportional to the speed of updates to the -// virtual mouse +// DEPRECATED: Speed is a value that is proportional to the speed of updates to +// the virtual mouse CFG(Mouse, Speed, mouse_speed, int, 0) +// Sensitivity is a percentage that is used to scale the speed of the virtual +// mouse. This replaces the old speed value. +CFG(Mouse, Sensitivity, mouse_sensitivity, int, 0) + // ReverseScroll controls the scrolling direction CFG(Mouse, ReverseScroll, mouse_reverse_scroll, bool, false) +// IsometricMode controls whether to use linear or isometric speed calculations +CFG(Mouse, IsometricMode, mouse_isometric_mode, bool, false) + +// CurveFactor controls the speed curve +CFG(Mouse, CurveFactor, mouse_curve_factor, int, 3) + +// Deadzone controls the deadzone range for the thumbstick +CFG(Mouse, Deadzone, mouse_deadzone_factor, int, 0) + /********************************************************************** * Profiles - only valid on Linux *********************************************************************/ diff --git a/daemon/x52d_config.h b/daemon/x52d_config.h index 4ba7f3d..930f16c 100644 --- a/daemon/x52d_config.h +++ b/daemon/x52d_config.h @@ -39,7 +39,11 @@ struct x52d_config { bool mouse_enabled; int mouse_speed; + int mouse_sensitivity; bool mouse_reverse_scroll; + bool mouse_isometric_mode; + int mouse_curve_factor; + int mouse_deadzone_factor; bool clutch_enabled; bool clutch_latched; @@ -72,7 +76,11 @@ void x52d_cfg_set_Brightness_MFD(uint16_t param); void x52d_cfg_set_Brightness_LED(uint16_t param); void x52d_cfg_set_Mouse_Enabled(bool param); void x52d_cfg_set_Mouse_Speed(int param); +void x52d_cfg_set_Mouse_Sensitivity(int param); void x52d_cfg_set_Mouse_ReverseScroll(bool param); +void x52d_cfg_set_Mouse_IsometricMode(bool param); +void x52d_cfg_set_Mouse_CurveFactor(int param); +void x52d_cfg_set_Mouse_Deadzone(int param); void x52d_cfg_set_Profiles_Directory(char* param); void x52d_cfg_set_Profiles_ClutchEnabled(bool param); void x52d_cfg_set_Profiles_ClutchLatched(bool param); diff --git a/daemon/x52d_mouse.c b/daemon/x52d_mouse.c index 4ad0c52..48bee52 100644 --- a/daemon/x52d_mouse.c +++ b/daemon/x52d_mouse.c @@ -9,6 +9,7 @@ #include "config.h" #include #include +#include #define PINELOG_MODULE X52D_MOD_MOUSE #include "pinelog.h" @@ -20,11 +21,41 @@ #define DEFAULT_MOUSE_DELAY 70000 #define MOUSE_DELAY_DELTA 5000 #define MOUSE_DELAY_MIN 10000 +#define MOUSE_MULT_FACTOR 4 #define MAX_MOUSE_MULT 5 +#define MIN_SENSITIVITY 10 +#define MAX_SENSITIVITY 500 + +static const double MOUSE_CURVE_FACTORS[5] = { + 1.0, 1.2, 1.5, 1.8, 2.2 +}; + +static const double MOUSE_DEADZONES[12] = { + 0.0, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.4, 1.5 +}; -volatile int mouse_delay = DEFAULT_MOUSE_DELAY; -volatile int mouse_mult = MOUSE_MULT_FACTOR; volatile int mouse_scroll_dir = 1; +volatile bool mouse_isometric_mode = false; +volatile double mouse_curve_factor = MOUSE_CURVE_FACTORS[2]; // default +volatile double mouse_deadzone = MOUSE_DEADZONES[0]; +volatile int mouse_sensitivity = 0; + +static int clamp_int(const char *description, int value, int min, int max) +{ + if (value < min) { + PINELOG_DEBUG(_("Clamping %s value %d to range [%d..%d]"), + description, value, min, max); + return min; + } + + if (value > max) { + PINELOG_DEBUG(_("Clamping %s value %d to range [%d..%d]"), + description, value, min, max); + return max; + } + + return value; +} void x52d_cfg_set_Mouse_Enabled(bool enabled) { @@ -35,17 +66,21 @@ void x52d_cfg_set_Mouse_Enabled(bool enabled) void x52d_cfg_set_Mouse_Speed(int speed) { + // DEPRECATED, calculate the sensitivity instead int new_delay; int new_mult; int max_base_speed = (DEFAULT_MOUSE_DELAY - MOUSE_DELAY_MIN) / MOUSE_DELAY_DELTA; int max_speed = max_base_speed + MAX_MOUSE_MULT * MOUSE_MULT_FACTOR; - if (speed < 0 || speed > max_speed) { - PINELOG_INFO(_("Ignoring mouse speed %d outside supported range (0-%d)"), - speed, max_speed); - return; - } else if (speed <= max_base_speed) { + double sensitivity; + + if (mouse_sensitivity == 0) { + PINELOG_WARN(_("Config option 'mouse.speed' is DEPRECATED. Please use 'mouse.sensitivity' instead")); + } + + speed = clamp_int("mouse speed", speed, 0, max_speed); + if (speed <= max_base_speed) { new_delay = DEFAULT_MOUSE_DELAY - speed * MOUSE_DELAY_DELTA; new_mult = MOUSE_MULT_FACTOR; } else { @@ -54,10 +89,12 @@ void x52d_cfg_set_Mouse_Speed(int speed) new_mult = MOUSE_MULT_FACTOR + (speed - max_base_speed); } - PINELOG_DEBUG(_("Setting mouse speed to %d (delay %d ms, multiplier %f)"), - speed, new_delay / 1000, new_mult / (double)MOUSE_MULT_FACTOR); - mouse_delay = new_delay; - mouse_mult = new_mult; + sensitivity = round(10000000.0 / new_delay * new_mult / (double)MOUSE_MULT_FACTOR); + + PINELOG_INFO(_("Migrating legacy mouse speed '%d' to sensitivity '%d%%'"), + speed, (int)sensitivity); + mouse_sensitivity = clamp_int(_("speed -> sensitivity"), (int)sensitivity, + MIN_SENSITIVITY, MAX_SENSITIVITY); } void x52d_cfg_set_Mouse_ReverseScroll(bool enabled) @@ -71,3 +108,36 @@ void x52d_cfg_set_Mouse_ReverseScroll(bool enabled) mouse_scroll_dir = 1; } } + +void x52d_config_set_Mouse_IsometricMode(bool enabled) +{ + PINELOG_DEBUG(_("Setting mouse isometric mode to %s"), + enabled ? _("on") : _("off")); + mouse_isometric_mode = enabled; +} + +void x52d_config_set_Mouse_Sensitivity(int factor) +{ + mouse_sensitivity = clamp_int(_("sensitivity"), factor, + MIN_SENSITIVITY, MAX_SENSITIVITY); + + PINELOG_DEBUG(_("Setting mouse sensitivity to %d%%"), mouse_sensitivity); +} + +void x52d_config_set_Mouse_CurveFactor(int factor) +{ + // Factor ranges from 1-5, clamp it in this range + factor = clamp_int(_("curve factor"), factor, 1, 5); + + mouse_curve_factor = MOUSE_CURVE_FACTORS[factor-1]; + PINELOG_DEBUG(_("Setting mouse curve factor to %f"), mouse_curve_factor); +} + +void x52d_config_set_Mouse_Deadzone(int factor) +{ + // Factor ranges from 0-12, clamp it in this range + factor = clamp_int(_("deadzone factor"), factor, 0, 11); + + mouse_deadzone = MOUSE_DEADZONES[factor]; + PINELOG_DEBUG(_("Setting mouse deadzone to %f"), mouse_deadzone); +} diff --git a/daemon/x52d_mouse.h b/daemon/x52d_mouse.h index d201a12..b001ebf 100644 --- a/daemon/x52d_mouse.h +++ b/daemon/x52d_mouse.h @@ -12,11 +12,11 @@ #include #include "libx52io.h" -extern volatile int mouse_delay; -extern volatile int mouse_mult; +extern volatile bool mouse_isometric_mode; extern volatile int mouse_scroll_dir; - -#define MOUSE_MULT_FACTOR 4 +extern volatile double mouse_curve_factor; +extern volatile double mouse_deadzone; +extern volatile int mouse_sensitivity; void x52d_mouse_thread_control(bool enabled); void x52d_mouse_handler_init(void); diff --git a/daemon/x52d_mouse_handler.c b/daemon/x52d_mouse_handler.c index 6ca4151..9c68b98 100644 --- a/daemon/x52d_mouse_handler.c +++ b/daemon/x52d_mouse_handler.c @@ -11,6 +11,7 @@ #include #include #include +#include #include "libx52io.h" #include "vkm.h" @@ -85,40 +86,94 @@ static int report_wheel(void) return (rc == VKM_SUCCESS); } -static int get_axis_val(int index) +static inline int fsgn(double f) { - int axis_val = new_report.axis[index]; - - /* - * Axis value ranges from 0 to 15, with the default midpoint at 8. - * We need to translate this to a range of -7 to +7. Since the midpoint - * is slightly off-center, we will shift the values left, and subtract - * 15, effectively, giving us a range of -15 to +15. Shifting right again - * will reduce the range to -7 to +7, and effectively ignore the reported - * values of 7 and 8. - */ - axis_val = ((axis_val << 1) - 15) >> 1; - - /* - * Factor in the multiplicative factor for the axis. This deliberately - * uses integer division, since the uinput event only accepts integers. - * For the speed purposes, this should be good enough. - */ - axis_val = (axis_val * mouse_mult) / MOUSE_MULT_FACTOR; - - return axis_val; + return (f >= 0 ? 1 : -1); } static int report_axis(void) { - vkm_result rc; - int dx = get_axis_val(LIBX52IO_AXIS_THUMBX); - int dy = get_axis_val(LIBX52IO_AXIS_THUMBY); + #define MAX_TICK_SPEED 250.0 - rc = vkm_mouse_move(mouse_context, dx, dy); + static double accum_x = 0.0; + static double accum_y = 0.0; + + /* Center raw HID values (0,15) => (-8, 7) */ + int dx = new_report.axis[LIBX52IO_AXIS_THUMBX] - 8; + int dy = new_report.axis[LIBX52IO_AXIS_THUMBY] - 8; + + /* Calculate radial magnitude */ + double mag = sqrt((double)(dx * dx + dy * dy)); + + /* Radial deadzone check */ + if (mag <= mouse_deadzone) { + accum_x = 0.0; + accum_y = 0.0; + return 0; + } + + /* Calculate gain */ + double gain = (double)mouse_sensitivity / 100.0; + + /* Normalize magnitude */ + double adj_mag = mag - mouse_deadzone; + double out_x = 0.0; + double out_y = 0.0; + + if (mouse_isometric_mode) { + /* Isometric mode: speed is a function of total distance */ + double speed = gain * pow(adj_mag, mouse_curve_factor); + + /* Clamp total speed before breaking into components */ + if (speed > MAX_TICK_SPEED) { + speed = MAX_TICK_SPEED; + } + + /* Unit vector * speed */ + out_x = (dx / mag) * speed; + out_y = (dy / mag) * speed; + } else { + /* Linear mode: speed is independently calculated for X & Y axes */ + double ratio = adj_mag / mag; + double cur_x = dx * ratio; + double cur_y = dy * ratio; + + out_x = fsgn(cur_x) * gain * pow(fabs(cur_x), mouse_curve_factor); + out_y = fsgn(cur_y) * gain * pow(fabs(cur_y), mouse_curve_factor); + + /* Clamp individual axis speeds */ + if (fabs(out_x) > MAX_TICK_SPEED) { + out_x = fsgn(out_x) * MAX_TICK_SPEED; + } + + if (fabs(out_y) > MAX_TICK_SPEED) { + out_y = fsgn(out_y) * MAX_TICK_SPEED; + } + } + + /* Accumulate movement and independent resets */ + accum_x += out_x; + accum_y += out_y; + + if (dx == 0) { + accum_x = 0.0; + } + if (dy == 0) { + accum_y = 0.0; + } + + /* Extract integer values for VKM injection */ + int move_x = (int)accum_x; + int move_y = (int)accum_y; + + accum_x -= move_x; + accum_y -= move_y; + + vkm_result rc; + rc = vkm_mouse_move(mouse_context, move_x, move_y); if (rc != VKM_SUCCESS && rc != VKM_ERROR_NO_CHANGE) { PINELOG_ERROR(_("Error %d writing mouse axis event (dx %d, dy %d)"), - rc, dx, dy); + rc, move_x, move_y); } return (rc == VKM_SUCCESS); @@ -154,7 +209,7 @@ static void * x52_mouse_thr(void *param) report_sync(); } - usleep(mouse_delay); + usleep(10000); } return NULL; diff --git a/daemon/x52d_mouse_test.c b/daemon/x52d_mouse_test.c index 2b7fe84..885bdcb 100644 --- a/daemon/x52d_mouse_test.c +++ b/daemon/x52d_mouse_test.c @@ -44,75 +44,61 @@ static void test_mouse_thread_disabled(void **state) x52d_cfg_set_Mouse_Enabled(false); } +/* The following tests are dependent on the values in x52d_mouse.c */ static void test_mouse_speed_negative(void **state) { (void)state; - int orig_mouse_delay = mouse_delay; - int orig_mouse_mult = mouse_mult; - x52d_cfg_set_Mouse_Speed(-1); - assert_int_equal(mouse_delay, orig_mouse_delay); - assert_int_equal(mouse_mult, orig_mouse_mult); + assert_int_equal(mouse_sensitivity, 14); } -/* The following tests are dependent on the values in x52d_mouse.c */ static void test_mouse_speed_0(void **state) { (void)state; x52d_cfg_set_Mouse_Speed(0); - assert_int_equal(mouse_delay, 70000); - assert_int_equal(mouse_mult, 4); + assert_int_equal(mouse_sensitivity, 14); } static void test_mouse_speed_mid_base(void **state) { (void)state; x52d_cfg_set_Mouse_Speed(6); - assert_int_equal(mouse_delay, 40000); - assert_int_equal(mouse_mult, 4); + assert_int_equal(mouse_sensitivity, 25); } static void test_mouse_speed_max_base(void **state) { (void)state; x52d_cfg_set_Mouse_Speed(12); - assert_int_equal(mouse_delay, 10000); - assert_int_equal(mouse_mult, 4); + assert_int_equal(mouse_sensitivity, 100); } static void test_mouse_speed_min_hyper(void **state) { (void)state; x52d_cfg_set_Mouse_Speed(13); - assert_int_equal(mouse_delay, 10000); - assert_int_equal(mouse_mult, 5); + assert_int_equal(mouse_sensitivity, 125); } static void test_mouse_speed_mid_hyper(void **state) { (void)state; x52d_cfg_set_Mouse_Speed(22); - assert_int_equal(mouse_delay, 10000); - assert_int_equal(mouse_mult, 14); + assert_int_equal(mouse_sensitivity, 350); } static void test_mouse_speed_max_hyper(void **state) { (void)state; x52d_cfg_set_Mouse_Speed(32); - assert_int_equal(mouse_delay, 10000); - assert_int_equal(mouse_mult, 24); + assert_int_equal(mouse_sensitivity, 600); } static void test_mouse_speed_above_max(void **state) { - int orig_mouse_delay = mouse_delay; - int orig_mouse_mult = mouse_mult; (void)state; - x52d_cfg_set_Mouse_Speed(33); - assert_int_equal(mouse_delay, orig_mouse_delay); - assert_int_equal(mouse_mult, orig_mouse_mult); + assert_int_equal(mouse_sensitivity, 600); } static void test_mouse_reverse_scroll_enabled(void **state)