// Copyright 2020 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "components/exo/gamepad.h" #include "base/bind.h" #include "base/logging.h" #include "chromeos/constants/chromeos_features.h" namespace exo { Gamepad::Gamepad(const ui::GamepadDevice& gamepad_device) : device(gamepad_device), input_controller_( ui::OzonePlatform::GetInstance()->GetInputController()) {} Gamepad::~Gamepad() { for (GamepadObserver& observer : observer_list_) observer.OnGamepadDestroying(this); if (delegate_) delegate_->OnRemoved(); } void Gamepad::Vibrate(const std::vector& duration_millis, const std::vector& amplitudes, int32_t repeat) { if (!device.supports_vibration_rumble) { VLOG(2) << "Vibrate failed because gamepad does not support vibration."; return; } if (duration_millis.size() != amplitudes.size()) { VLOG(2) << "Vibrate failed because the amplitudes vector and " "duration_millis vector are not the same size."; return; } vibration_timer_.Stop(); vibration_timer_.Start( FROM_HERE, base::TimeDelta::FromMilliseconds(0), base::BindOnce(&Gamepad::HandleVibrate, base::Unretained(this), duration_millis, amplitudes, repeat, /*index=*/0, /*duration_already_vibrated=*/0)); } void Gamepad::HandleVibrate(const std::vector& duration_millis, const std::vector& amplitudes, int32_t repeat, size_t index, int64_t duration_already_vibrated) { size_t vector_size = duration_millis.size(); if (index >= vector_size) return; if (!can_vibrate_) { VLOG(2) << "Gamepad is not allowed to vibrate because it is not in focus."; return; } int64_t duration_left_to_vibrate = duration_millis[index] - duration_already_vibrated; if (duration_left_to_vibrate > kMaxDurationMillis) { // The device does not support effects this long. Issue periodic vibration // commands until the effect is complete. SendVibrate(amplitudes[index], kMaxDurationMillis); vibration_timer_.Start( FROM_HERE, base::TimeDelta::FromMilliseconds(kMaxDurationMillis), base::BindOnce(&Gamepad::HandleVibrate, base::Unretained(this), duration_millis, amplitudes, repeat, index, /*duration_already_vibrated=*/duration_already_vibrated + kMaxDurationMillis)); } else { SendVibrate(amplitudes[index], duration_left_to_vibrate); index++; bool needs_to_repeat = index >= vector_size && repeat >= 0 && repeat < static_cast(vector_size); if (needs_to_repeat) index = repeat; vibration_timer_.Start( FROM_HERE, base::TimeDelta::FromMilliseconds(duration_left_to_vibrate), base::BindOnce(&Gamepad::HandleVibrate, base::Unretained(this), duration_millis, amplitudes, repeat, index, /*duration_already_vibrated=*/0)); } } void Gamepad::SendVibrate(uint8_t amplitude, int64_t duration_millis) { // |duration_millis| is always <= |kMaxDurationMillis|, which is the max value // for uint16_t, so it is safe to cast it to uint16_t here. input_controller_->PlayVibrationEffect( device.id, amplitude, static_cast(duration_millis)); } void Gamepad::CancelVibration() { if (!device.supports_vibration_rumble) { VLOG(2) << "CancelVibration failed because gamepad does not support vibration."; return; } if (!vibration_timer_.IsRunning()) return; vibration_timer_.Stop(); SendCancelVibration(); } void Gamepad::SendCancelVibration() { input_controller_->StopVibration(device.id); } void Gamepad::SetDelegate(std::unique_ptr delegate) { DCHECK(!delegate_); delegate_ = std::move(delegate); } void Gamepad::AddObserver(GamepadObserver* observer) { observer_list_.AddObserver(observer); } bool Gamepad::HasObserver(GamepadObserver* observer) const { return observer_list_.HasObserver(observer); } void Gamepad::RemoveObserver(GamepadObserver* observer) { observer_list_.RemoveObserver(observer); } void Gamepad::OnGamepadFocused() { can_vibrate_ = base::FeatureList::IsEnabled(chromeos::features::kGamepadVibration); } void Gamepad::OnGamepadFocusLost() { can_vibrate_ = false; CancelVibration(); } void Gamepad::OnGamepadEvent(const ui::GamepadEvent& event) { DCHECK(delegate_); switch (event.type()) { case ui::GamepadEventType::BUTTON: delegate_->OnButton(event.code(), event.value(), event.timestamp()); break; case ui::GamepadEventType::AXIS: delegate_->OnAxis(event.code(), event.value(), event.timestamp()); break; case ui::GamepadEventType::FRAME: delegate_->OnFrame(event.timestamp()); break; } } } // namespace exo