// Copyright 2021 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/haptic_button_generator_filter_interpreter.h" #include #include "include/gestures.h" #include "include/interpreter.h" #include "include/logging.h" #include "include/tracer.h" #include "include/util.h" namespace gestures { HapticButtonGeneratorFilterInterpreter::HapticButtonGeneratorFilterInterpreter( PropRegistry* prop_reg, Interpreter* next, Tracer* tracer) : FilterInterpreter(NULL, next, tracer, false), release_suppress_factor_(1.0), active_gesture_(false), active_gesture_timeout_(0.1), active_gesture_deadline_(NO_DEADLINE), button_down_(false), dynamic_down_threshold_(0.0), dynamic_up_threshold_(0.0), sensitivity_(prop_reg, "Haptic Button Sensitivity", 3), use_custom_thresholds_(prop_reg, "Use Custom Haptic Button Force Thresholds", false), custom_down_threshold_(prop_reg, "Custom Haptic Button Force Threshold Down", 150.0), custom_up_threshold_(prop_reg, "Custom Haptic Button Force Threshold Up", 130.0), enabled_(prop_reg, "Enable Haptic Button Generation", false), force_scale_(prop_reg, "Force Calibration Slope", 1.0), force_translate_(prop_reg, "Force Calibration Offset", 0.0), complete_release_suppress_speed_( prop_reg, "Haptic Complete Release Suppression Speed", 200.0), use_dynamic_thresholds_(prop_reg, "Use Dynamic Haptic Thresholds", false), dynamic_down_ratio_(prop_reg, "Dynamic Haptic Down Ratio", 1.2), dynamic_up_ratio_(prop_reg, "Dynamic Haptic Up Ratio", 0.5), max_dynamic_up_force_(prop_reg, "Max Dynamic Haptic Up Force", 350.0) { InitName(); } void HapticButtonGeneratorFilterInterpreter::Initialize( const HardwareProperties* hwprops, Metrics* metrics, MetricsProperties* mprops, GestureConsumer* consumer) { is_haptic_pad_ = hwprops->is_haptic_pad; FilterInterpreter::Initialize(hwprops, NULL, mprops, consumer); } void HapticButtonGeneratorFilterInterpreter::SyncInterpretImpl( HardwareState* hwstate, stime_t* timeout) { HandleHardwareState(hwstate); stime_t next_timeout = NO_DEADLINE; next_->SyncInterpret(hwstate, &next_timeout); UpdatePalmState(hwstate); *timeout = SetNextDeadlineAndReturnTimeoutVal( hwstate->timestamp, active_gesture_deadline_, next_timeout); } void HapticButtonGeneratorFilterInterpreter::HandleHardwareState( HardwareState* hwstate) { if (!enabled_.val_ || !is_haptic_pad_) return; // Ignore the button state generated by the haptic touchpad. hwstate->buttons_down = 0; // Determine force thresholds. double down_threshold; double up_threshold; if (use_custom_thresholds_.val_) { down_threshold = custom_down_threshold_.val_; up_threshold = custom_up_threshold_.val_; } else { down_threshold = down_thresholds_[sensitivity_.val_ - 1]; up_threshold = up_thresholds_[sensitivity_.val_ - 1]; } if (use_dynamic_thresholds_.val_) { up_threshold = fmax(up_threshold, dynamic_up_threshold_); down_threshold = fmax(down_threshold, dynamic_down_threshold_); } up_threshold *= release_suppress_factor_; // Determine maximum force on touchpad in grams double force = 0.0; for (short i = 0; i < hwstate->finger_cnt; i++) { FingerState* fs = &hwstate->fingers[i]; if (!SetContainsValue(palms_, fs->tracking_id)) { force = fmax(force, fs->pressure); } } force *= force_scale_.val_; force += force_translate_.val_; // Set the button state bool prev_button_down = button_down_; if (button_down_) { if (force < up_threshold) button_down_ = false; else hwstate->buttons_down = GESTURES_BUTTON_LEFT; } else if (force > down_threshold && !active_gesture_) { button_down_ = true; hwstate->buttons_down = GESTURES_BUTTON_LEFT; } // When the user presses very hard, we want to increase the force threshold // for releasing the button. We scale the release threshold as a ratio of the // max force applied while the button is down. if (prev_button_down) { if (button_down_) { dynamic_up_threshold_ = fmax(dynamic_up_threshold_, force * dynamic_up_ratio_.val_); dynamic_up_threshold_ = fmin(dynamic_up_threshold_, max_dynamic_up_force_.val_); } else { dynamic_up_threshold_ = 0.0; } } // Because we dynamically increase the up_threshold when a user presses very // hard, we also need to increase the down_threshold for the next click. // However, if the user continues to decrease force after the button release, // event, we will keep scaling down the dynamic_down_threshold. if (prev_button_down) { if (!button_down_) { dynamic_down_threshold_ = force * dynamic_down_ratio_.val_; } } else { if (button_down_) { dynamic_down_threshold_ = 0.0; } else { dynamic_down_threshold_ = fmin(dynamic_down_threshold_, force * dynamic_down_ratio_.val_); } } release_suppress_factor_ = 1.0; } void HapticButtonGeneratorFilterInterpreter::UpdatePalmState( HardwareState* hwstate) { RemoveMissingIdsFromSet(&palms_, *hwstate); for (short i = 0; i < hwstate->finger_cnt; i++) { FingerState* fs = &hwstate->fingers[i]; if (fs->flags & GESTURES_FINGER_LARGE_PALM) { palms_.insert(fs->tracking_id); } } } void HapticButtonGeneratorFilterInterpreter::HandleTimerImpl( stime_t now, stime_t *timeout) { stime_t next_timeout; if (ShouldCallNextTimer(active_gesture_deadline_)) { next_timeout = NO_DEADLINE; next_->HandleTimer(now, &next_timeout); } else { if (active_gesture_deadline_ > now) { Err("Spurious callback. now: %f, active gesture deadline: %f", now, active_gesture_deadline_); return; } // If enough time has passed without an active gesture event assume that we // missed the gesture ending event, to prevent a state where the button is // stuck down. active_gesture_ = false; active_gesture_deadline_ = NO_DEADLINE; next_timeout = next_timer_deadline_ == NO_DEADLINE || next_timer_deadline_ <= now ? NO_DEADLINE : next_timer_deadline_ - now; } *timeout = SetNextDeadlineAndReturnTimeoutVal(now, active_gesture_deadline_, next_timeout); } void HapticButtonGeneratorFilterInterpreter::ConsumeGesture( const Gesture& gesture) { if (!enabled_.val_ || !is_haptic_pad_) { ProduceGesture(gesture); return; } // Determine if there is an active non-click multi-finger gesture. switch (gesture.type) { case kGestureTypeScroll: case kGestureTypeSwipe: case kGestureTypeFourFingerSwipe: active_gesture_ = true; break; case kGestureTypeFling: case kGestureTypeSwipeLift: case kGestureTypeFourFingerSwipeLift: active_gesture_ = false; break; case kGestureTypePinch: active_gesture_ = (gesture.details.pinch.zoom_state != GESTURES_ZOOM_END); break; default: break; } if (active_gesture_) { active_gesture_deadline_ = gesture.end_time + active_gesture_timeout_; } // When dragging while clicking, users often reduce the force applied, causing // accidental release. So we calculate a scaling factor to reduce the "up" // threshold which starts at 1.0 (normal threshold) for stationary fingers, // and goes down to 0.0 at the complete_release_suppress_speed_. if (gesture.type == kGestureTypeMove) { float distance_sq = gesture.details.move.dx * gesture.details.move.dx + gesture.details.move.dy * gesture.details.move.dy; stime_t time_delta = gesture.end_time - gesture.start_time; float complete_suppress_dist = complete_release_suppress_speed_.val_ * time_delta; float complete_suppress_dist_sq = complete_suppress_dist * complete_suppress_dist; release_suppress_factor_ = time_delta <= 0.0 ? 1.0 : 1.0 - distance_sq / complete_suppress_dist_sq; // Always allow release at very low force, to prevent a stuck button when // the user lifts their finger while moving quickly. release_suppress_factor_ = fmax(release_suppress_factor_, 0.1); } ProduceGesture(gesture); } } // namespace gestures