// Copyright 2020 The Chromium Authors
// 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 "ash/constants/ash_features.h"
#include "base/functional/bind.h"
#include "base/logging.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<int64_t>& duration_millis,
const std::vector<uint8_t>& 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::Milliseconds(0),
base::BindOnce(&Gamepad::HandleVibrate, base::Unretained(this),
duration_millis, amplitudes, repeat, /*index=*/0,
/*duration_already_vibrated=*/0));
}
void Gamepad::HandleVibrate(const std::vector<int64_t>& duration_millis,
const std::vector<uint8_t>& 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::Milliseconds(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<int32_t>(vector_size);
if (needs_to_repeat)
index = repeat;
vibration_timer_.Start(
FROM_HERE, base::Milliseconds(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<uint16_t>(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<GamepadDelegate> 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(ash::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