summaryrefslogtreecommitdiff
path: root/src/mouse_interpreter.cc
blob: a281a9a8f9bc629d8104317a7ad58c47efdb4b5e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
// Copyright 2012 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "include/mouse_interpreter.h"

#include <math.h>

#include "include/logging.h"
#include "include/macros.h"
#include "include/tracer.h"

namespace gestures {

/*
 * The number of subdivisions that `REL_WHEEL_HI_RES` and `REL_HWHEEL_HI_RES`
 * have relative to `REL_WHEEL` and `REL_HWHEEL` respectively.
 */
const static int REL_WHEEL_HI_RES_UNITS_PER_NOTCH = 120;

// Default value for mouse scroll sensitivity.
const static int kMouseScrollSensitivityDefaultValue = 3;

MouseInterpreter::MouseInterpreter(PropRegistry* prop_reg, Tracer* tracer)
    : Interpreter(nullptr, tracer, false),
      wheel_emulation_accu_x_(0.0),
      wheel_emulation_accu_y_(0.0),
      wheel_emulation_active_(false),
      reverse_scrolling_(prop_reg, "Mouse Reverse Scrolling", false),
      scroll_acceleration_(prop_reg, "Mouse Scroll Acceleration", true),
      scroll_sensitivity_(prop_reg,"Mouse Scroll Sensitivity",
        kMouseScrollSensitivityDefaultValue),
      hi_res_scrolling_(prop_reg, "Mouse High Resolution Scrolling", true),
      scroll_velocity_buffer_size_(prop_reg, "Scroll Wheel Velocity Buffer", 3),
      scroll_accel_curve_prop_(prop_reg, "Mouse Scroll Accel Curve",
          scroll_accel_curve_, sizeof(scroll_accel_curve_) / sizeof(double)),
      scroll_max_allowed_input_speed_(prop_reg,
                                      "Mouse Scroll Max Input Speed",
                                      177.0),
      force_scroll_wheel_emulation_(prop_reg,
                                     "Force Scroll Wheel Emulation",
                                     false),
      scroll_wheel_emulation_speed_(prop_reg,
                                    "Scroll Wheel Emulation Speed",
                                    100.0),
      scroll_wheel_emulation_thresh_(prop_reg,
                                    "Scroll Wheel Emulation Threshold",
                                    1.0),
      output_mouse_wheel_gestures_(prop_reg,
                                   "Output Mouse Wheel Gestures", false) {
  InitName();
  memset(&prev_state_, 0, sizeof(prev_state_));
  // Scroll acceleration curve coefficients. See the definition for more
  // details on how to generate them.
  scroll_accel_curve_[0] = 1.0374e+01;
  scroll_accel_curve_[1] = 4.1773e-01;
  scroll_accel_curve_[2] = 2.5737e-02;
  scroll_accel_curve_[3] = 8.0428e-05;
  scroll_accel_curve_[4] = -9.1149e-07;
  scroll_max_allowed_input_speed_.SetDelegate(this);
}

void MouseInterpreter::SyncInterpretImpl(HardwareState& hwstate,
                                         stime_t* timeout) {
  const char name[] = "MouseInterpreter::SyncInterpretImpl";
  LogHardwareStatePre(name, hwstate);

  if(!EmulateScrollWheel(hwstate)) {
    // Interpret mouse events in the order of pointer moves, scroll wheels and
    // button clicks.
    InterpretMouseMotionEvent(prev_state_, hwstate);
    // Note that unlike touchpad scrolls, we interpret and send separate events
    // for horizontal/vertical mouse wheel scrolls. This is partly to match what
    // the xf86-input-evdev driver does and is partly because not all code in
    // Chrome honors MouseWheelEvent that has both X and Y offsets.
    InterpretScrollWheelEvent(hwstate, true);
    InterpretScrollWheelEvent(hwstate, false);
    InterpretMouseButtonEvent(prev_state_, hwstate);
  }
  // Pass max_finger_cnt = 0 to DeepCopy() since we don't care fingers and
  // did not allocate any space for fingers.
  prev_state_.DeepCopy(hwstate, 0);

  LogHardwareStatePost(name, hwstate);
}

double MouseInterpreter::ComputeScrollAccelFactor(double input_speed) {
  double result = 0.0;
  double term = 1.0;
  double allowed_speed = fabs(input_speed);
  if (allowed_speed > scroll_max_allowed_input_speed_.val_)
    allowed_speed = scroll_max_allowed_input_speed_.val_;

  // Compute the scroll acceleration factor.
  for (size_t i = 0; i < arraysize(scroll_accel_curve_); i++) {
    result += term * scroll_accel_curve_[i];
    term *= allowed_speed;
  }
  return result;
}

bool MouseInterpreter::EmulateScrollWheel(const HardwareState& hwstate) {
  const char name[] = "MouseInterpreter::EmulateScrollWheel";

  if (!force_scroll_wheel_emulation_.val_ && hwprops_->has_wheel)
    return false;
  bool down = hwstate.buttons_down & GESTURES_BUTTON_MIDDLE ||
              (hwstate.buttons_down & GESTURES_BUTTON_LEFT &&
               hwstate.buttons_down & GESTURES_BUTTON_RIGHT);
  bool prev_down = prev_state_.buttons_down & GESTURES_BUTTON_MIDDLE ||
                   (prev_state_.buttons_down & GESTURES_BUTTON_LEFT &&
                    prev_state_.buttons_down & GESTURES_BUTTON_RIGHT);
  bool raising = down && !prev_down;
  bool falling = !down && prev_down;

  // Reset scroll emulation detection on button down.
  if (raising) {
    wheel_emulation_accu_x_ = 0;
    wheel_emulation_accu_y_ = 0;
    wheel_emulation_active_ = false;
  }

  // Send button event if button has been released without scrolling.
  if (falling && !wheel_emulation_active_) {
    auto button_change = Gesture(kGestureButtonsChange,
                           prev_state_.timestamp,
                           hwstate.timestamp,
                           prev_state_.buttons_down,
                           prev_state_.buttons_down,
                           false); // is_tap
    LogGestureProduce(name, button_change);
    ProduceGesture(button_change);
  }

  if (down) {
    // Detect scroll emulation
    if (!wheel_emulation_active_) {
      wheel_emulation_accu_x_ += hwstate.rel_x;
      wheel_emulation_accu_y_ += hwstate.rel_y;
      double dist_sq = wheel_emulation_accu_x_ * wheel_emulation_accu_x_ +
                       wheel_emulation_accu_y_ * wheel_emulation_accu_y_;
      double thresh_sq = scroll_wheel_emulation_thresh_.val_ *
                         scroll_wheel_emulation_thresh_.val_;
      if (dist_sq > thresh_sq) {
        // Lock into scroll emulation until button is released.
        wheel_emulation_active_ = true;
      }
    }

    // Transform motion into scrolling.
    if (wheel_emulation_active_) {
      double scroll_x = hwstate.rel_x * scroll_wheel_emulation_speed_.val_;
      double scroll_y = hwstate.rel_y * scroll_wheel_emulation_speed_.val_;

      auto scroll = Gesture(kGestureScroll, hwstate.timestamp,
                            hwstate.timestamp, scroll_x, scroll_y);
      LogGestureProduce(name, scroll);
      ProduceGesture(scroll);
    }
    return true;
  }

  return false;
}

void MouseInterpreter::InterpretScrollWheelEvent(const HardwareState& hwstate,
                                                 bool is_vertical) {
  const char name[] = "MouseInterpreter::InterpretScrollWheelEvent";

  const size_t max_buffer_size = scroll_velocity_buffer_size_.val_;
  const float scroll_wheel_event_time_delta_min = 0.008 * max_buffer_size;
  bool use_high_resolution =
      is_vertical && hwprops_->wheel_is_hi_res
      && hi_res_scrolling_.val_;
  // Vertical wheel or horizontal wheel.
  WheelRecord current_wheel;
  current_wheel.timestamp = hwstate.timestamp;
  int ticks;
  std::vector<WheelRecord>* last_wheels;
  if (is_vertical) {
    // Only vertical high-res scrolling is supported for now.
    if (use_high_resolution) {
      current_wheel.change = hwstate.rel_wheel_hi_res
          / REL_WHEEL_HI_RES_UNITS_PER_NOTCH;
      ticks = hwstate.rel_wheel_hi_res;
    } else {
      current_wheel.change = hwstate.rel_wheel;
      ticks = hwstate.rel_wheel * REL_WHEEL_HI_RES_UNITS_PER_NOTCH;
    }
    last_wheels = &last_vertical_wheels_;
  } else {
    last_wheels = &last_horizontal_wheels_;
    current_wheel.change = hwstate.rel_hwheel;
    ticks = hwstate.rel_hwheel * REL_WHEEL_HI_RES_UNITS_PER_NOTCH;
  }

  // Check if the wheel is scrolled.
  if (current_wheel.change) {
    stime_t start_time, end_time = hwstate.timestamp;
    // Check if this scroll is in same direction as previous scroll event.
    if (!last_wheels->empty() &&
        ((current_wheel.change < 0 && last_wheels->back().change < 0) ||
         (current_wheel.change > 0 && last_wheels->back().change > 0))) {
      start_time = last_wheels->begin()->timestamp;
    } else {
      last_wheels->clear();
      start_time = end_time;
    }

    // We will only accelerate scrolls if we have filled our buffer of scroll
    // events all in the same direction. If the buffer is full, then calculate
    // scroll velocity using the average velocity of the entire buffer.
    float velocity;
    if (last_wheels->size() < max_buffer_size) {
      velocity = 0.0;
    } else {
      stime_t dt = end_time - last_wheels->back().timestamp;
      if (dt < scroll_wheel_event_time_delta_min) {
        // The first packets received after BT wakeup may be delayed, causing
        // the time delta between that and the subsequent packets to be
        // artificially very small.
        // Prevent small time deltas from triggering large amounts of
        // acceleration by enforcing a minimum time delta.
        dt = scroll_wheel_event_time_delta_min;
      }

      last_wheels->pop_back();
      float buffer_scroll_distance = current_wheel.change;
      for (auto wheel : *last_wheels) {
        buffer_scroll_distance += wheel.change;
      }

      velocity = buffer_scroll_distance / dt;
    }
    last_wheels->insert(last_wheels->begin(), current_wheel);

    // When scroll acceleration is off, the scroll factor does not relate to
    // scroll velocity. It's simply a constant multiplier to the wheel value.
    const double unaccel_scroll_factors[] = { 20.0, 36.0, 72.0, 112.0, 164.0 };

    float offset = current_wheel.change * (
      scroll_acceleration_.val_?
      ComputeScrollAccelFactor(velocity) :
      unaccel_scroll_factors[scroll_sensitivity_.val_ - 1]);

    if (is_vertical) {
      // For historical reasons the vertical wheel (REL_WHEEL) is inverted
      if (!reverse_scrolling_.val_) {
        offset = -offset;
        ticks = -ticks;
      }
      auto scroll_wheel = CreateWheelGesture(start_time, end_time,
                                             0, offset, 0, ticks);
      LogGestureProduce(name, scroll_wheel);
      ProduceGesture(scroll_wheel);
    } else {
      auto scroll_wheel = CreateWheelGesture(start_time, end_time,
                                             offset, 0, ticks, 0);
      LogGestureProduce(name, scroll_wheel);
      ProduceGesture(scroll_wheel);
    }
  }
}

Gesture MouseInterpreter::CreateWheelGesture(
    stime_t start_time, stime_t end_time, float dx, float dy,
    int tick_120ths_dx, int tick_120ths_dy) {
  if (output_mouse_wheel_gestures_.val_) {
    return Gesture(kGestureMouseWheel, start_time, end_time, dx, dy,
                   tick_120ths_dx, tick_120ths_dy);
  } else {
    return Gesture(kGestureScroll, start_time, end_time, dx, dy);
  }
}

void MouseInterpreter::InterpretMouseButtonEvent(
    const HardwareState& prev_state, const HardwareState& hwstate) {
  const char name[] = "MouseInterpreter::InterpretMouseButtonEvent";

  const unsigned buttons[] = {
    GESTURES_BUTTON_LEFT,
    GESTURES_BUTTON_MIDDLE,
    GESTURES_BUTTON_RIGHT,
    GESTURES_BUTTON_BACK,
    GESTURES_BUTTON_FORWARD,
    GESTURES_BUTTON_SIDE,
    GESTURES_BUTTON_EXTRA,
  };
  unsigned down = 0, up = 0;

  for (unsigned i = 0; i < arraysize(buttons); i++) {
    if (!(prev_state.buttons_down & buttons[i]) &&
        (hwstate.buttons_down & buttons[i]))
      down |= buttons[i];
    if ((prev_state.buttons_down & buttons[i]) &&
        !(hwstate.buttons_down & buttons[i]))
      up |= buttons[i];
  }

  if (down || up) {
    auto button_change = Gesture(kGestureButtonsChange,
                                 prev_state.timestamp,
                                 hwstate.timestamp,
                                 down,
                                 up,
                                 false); // is_tap
    LogGestureProduce(name, button_change);
    ProduceGesture(button_change);
  }
}

void MouseInterpreter::InterpretMouseMotionEvent(
    const HardwareState& prev_state,
    const HardwareState& hwstate) {
  const char name[] = "MouseInterpreter::InterpretMouseMotionEvent";

  if (hwstate.rel_x || hwstate.rel_y) {
    auto move = Gesture(kGestureMove,
                        prev_state.timestamp,
                        hwstate.timestamp,
                        hwstate.rel_x,
                        hwstate.rel_y);
    LogGestureProduce(name, move);
    ProduceGesture(move);
  }
}

}  // namespace gestures