summaryrefslogtreecommitdiffstats
path: root/input/input.c
diff options
context:
space:
mode:
Diffstat (limited to 'input/input.c')
-rw-r--r--input/input.c107
1 files changed, 101 insertions, 6 deletions
diff --git a/input/input.c b/input/input.c
index b8cc142da2..04b61cf3b4 100644
--- a/input/input.c
+++ b/input/input.c
@@ -95,6 +95,11 @@ struct cmd_queue {
struct mp_cmd *first;
};
+struct axis_state {
+ double dead_zone_accum;
+ double unit_accum;
+};
+
struct input_ctx {
pthread_mutex_t mutex;
struct mp_log *log;
@@ -130,6 +135,12 @@ struct input_ctx {
bool mouse_mangle, mouse_src_mangle;
struct mp_rect mouse_src, mouse_dst;
+ // Axis state (MP_AXIS_*)
+ struct axis_state axis_state_y; // MP_AXIS_UP/MP_AXIS_DOWN
+ struct axis_state axis_state_x; // MP_AXIS_LEFT/MP_AXIS_RIGHT
+ struct axis_state *axis_current; // Points to axis currently being scrolled
+ double last_axis_time; // mp_time_sec() of the last axis event
+
// List of command binding sections
struct cmd_bind_section *cmd_bind_sections;
@@ -542,7 +553,8 @@ static struct mp_cmd *resolve_key(struct input_ctx *ictx, int code)
return NULL;
}
-static void interpret_key(struct input_ctx *ictx, int code, double scale)
+static void interpret_key(struct input_ctx *ictx, int code, double scale,
+ int scale_units)
{
int state = code & (MP_KEY_STATE_DOWN | MP_KEY_STATE_UP);
code = code & ~(unsigned)state;
@@ -604,8 +616,86 @@ static void interpret_key(struct input_ctx *ictx, int code, double scale)
memset(ictx->key_history, 0, sizeof(ictx->key_history));
- cmd->scale = scale;
- mp_input_queue_cmd(ictx, cmd);
+ if (mp_input_is_scalable_cmd(cmd)) {
+ cmd->scale = scale;
+ mp_input_queue_cmd(ictx, cmd);
+ } else {
+ // Non-scalable commands won't understand cmd->scale, so synthesize
+ // multiple commands with cmd->scale = 1
+ cmd->scale = 1;
+ // Avoid spamming the player with too many commands
+ scale_units = FFMIN(scale_units, 20);
+ for (int i = 0; i < scale_units - 1; i++)
+ mp_input_queue_cmd(ictx, mp_cmd_clone(cmd));
+ if (scale_units)
+ mp_input_queue_cmd(ictx, cmd);
+ }
+}
+
+// Pre-processing for MP_AXIS_* events. If this returns false, the caller
+// should discard the event.
+static bool process_axis(struct input_ctx *ictx, int code, double *scale,
+ int *scale_units)
+{
+ // Size of the deadzone in scroll units. The user must scroll at least this
+ // much in any direction before their scroll is registered.
+ static const double DEADZONE_DIST = 0.125;
+ // The deadzone accumulator is reset if no scrolls happened in this many
+ // seconds, eg. the user is assumed to have finished scrolling.
+ static const double DEADZONE_SCROLL_TIME = 0.2;
+ // The scale_units accumulator is reset if no scrolls happened in this many
+ // seconds. This value should be fairly large, so commands will still be
+ // sent when the user scrolls slowly.
+ static const double UNIT_SCROLL_TIME = 0.5;
+
+ // Determine which axis is being scrolled
+ double dir;
+ struct axis_state *state;
+ switch (code) {
+ case MP_AXIS_UP: dir = -1; state = &ictx->axis_state_y; break;
+ case MP_AXIS_DOWN: dir = +1; state = &ictx->axis_state_y; break;
+ case MP_AXIS_LEFT: dir = -1; state = &ictx->axis_state_x; break;
+ case MP_AXIS_RIGHT: dir = +1; state = &ictx->axis_state_x; break;
+ default:
+ return true;
+ }
+
+ // Reset accumulators if it's determined that the user finished scrolling
+ double now = mp_time_sec();
+ if (now > ictx->last_axis_time + DEADZONE_SCROLL_TIME) {
+ ictx->axis_current = NULL;
+ ictx->axis_state_y.dead_zone_accum = 0;
+ ictx->axis_state_x.dead_zone_accum = 0;
+ }
+ if (now > ictx->last_axis_time + UNIT_SCROLL_TIME) {
+ ictx->axis_state_y.unit_accum = 0;
+ ictx->axis_state_x.unit_accum = 0;
+ }
+ ictx->last_axis_time = now;
+
+ // Process axis deadzone. A lot of touchpad drivers don't filter scroll
+ // input, which makes it difficult for the user to send AXIS_UP/DOWN
+ // without accidentally triggering AXIS_LEFT/RIGHT. We try to fix this by
+ // implementing a deadzone. When the value of either axis breaks out of the
+ // deadzone, events from the other axis will be ignored until the user
+ // finishes scrolling.
+ if (ictx->axis_current == NULL) {
+ state->dead_zone_accum += *scale * dir;
+ if (state->dead_zone_accum * dir > DEADZONE_DIST) {
+ ictx->axis_current = state;
+ *scale = state->dead_zone_accum * dir;
+ }
+ }
+ if (ictx->axis_current != state)
+ return false;
+
+ // Determine scale_units. This is incremented every time the accumulated
+ // scale value crosses 1.0. Non-scalable input commands will be ran that
+ // many times.
+ state->unit_accum += *scale * dir;
+ *scale_units = trunc(state->unit_accum * dir);
+ state->unit_accum -= *scale_units * dir;
+ return true;
}
static void mp_input_feed_key(struct input_ctx *ictx, int code, double scale,
@@ -631,14 +721,19 @@ static void mp_input_feed_key(struct input_ctx *ictx, int code, double scale,
// ignore system-doubleclick if we generate these events ourselves
if (!force_mouse && opts->doubleclick_time && MP_KEY_IS_MOUSE_BTN_DBL(unmod))
return;
- interpret_key(ictx, code, scale);
+ int units = 1;
+ if (MP_KEY_IS_AXIS(unmod) && !process_axis(ictx, unmod, &scale, &units))
+ return;
+ interpret_key(ictx, code, scale, units);
if (code & MP_KEY_STATE_DOWN) {
code &= ~MP_KEY_STATE_DOWN;
if (ictx->last_doubleclick_key_down == code &&
now - ictx->last_doubleclick_time < opts->doubleclick_time / 1000.0)
{
- if (code >= MP_MOUSE_BTN0 && code <= MP_MOUSE_BTN2)
- interpret_key(ictx, code - MP_MOUSE_BTN0 + MP_MOUSE_BTN0_DBL, 1);
+ if (code >= MP_MOUSE_BTN0 && code <= MP_MOUSE_BTN2) {
+ interpret_key(ictx, code - MP_MOUSE_BTN0 + MP_MOUSE_BTN0_DBL,
+ 1, 1);
+ }
}
ictx->last_doubleclick_key_down = code;
ictx->last_doubleclick_time = now;