blob: 6a6a0549b98d8f5d544798c71e9bbb7f07572819 [file] [log] [blame]
// Copyright 2016 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/gaming_seat.h"
#include <cmath>
#include "base/bind.h"
#include "components/exo/gamepad_delegate.h"
#include "components/exo/gaming_seat_delegate.h"
#include "components/exo/shell_surface.h"
#include "components/exo/surface.h"
#include "device/gamepad/gamepad_data_fetcher.h"
#include "device/gamepad/gamepad_pad_state_provider.h"
#include "device/gamepad/gamepad_platform_data_fetcher_linux.h"
#include "ui/aura/window.h"
namespace exo {
namespace {
constexpr double kGamepadButtonValueEpsilon = 0.001;
bool GamepadButtonValuesAreEqual(double a, double b) {
return fabs(a - b) < kGamepadButtonValueEpsilon;
}
std::unique_ptr<device::GamepadDataFetcher> CreateGamepadPlatformDataFetcher() {
return std::unique_ptr<device::GamepadDataFetcher>(
new device::GamepadPlatformDataFetcherLinux());
}
// Time between gamepad polls in milliseconds.
constexpr unsigned kPollingTimeIntervalMs = 16;
} // namespace
////////////////////////////////////////////////////////////////////////////////
// GamingSeat::ThreadSafeGamepadChangeFetcher
// Implements all methods and resources running on the polling thread.
// This class is reference counted to allow it to shut down safely on the
// polling thread even if the Gamepad has been destroyed on the origin thread.
class GamingSeat::ThreadSafeGamepadChangeFetcher
: public device::GamepadPadStateProvider,
public base::RefCountedThreadSafe<
GamingSeat::ThreadSafeGamepadChangeFetcher> {
public:
using ProcessGamepadChangesCallback =
base::Callback<void(int index, const device::Gamepad)>;
ThreadSafeGamepadChangeFetcher(
const ProcessGamepadChangesCallback& post_gamepad_changes,
const CreateGamepadDataFetcherCallback& create_fetcher_callback,
base::SingleThreadTaskRunner* task_runner)
: process_gamepad_changes_(post_gamepad_changes),
create_fetcher_callback_(create_fetcher_callback),
polling_task_runner_(task_runner),
origin_task_runner_(base::ThreadTaskRunnerHandle::Get()) {
DETACH_FROM_THREAD(thread_checker_);
}
// Enable or disable gamepad polling. Can be called from any thread.
void EnablePolling(bool enabled) {
polling_task_runner_->PostTask(
FROM_HERE,
base::Bind(
&ThreadSafeGamepadChangeFetcher::EnablePollingOnPollingThread,
make_scoped_refptr(this), enabled));
}
private:
friend class base::RefCountedThreadSafe<ThreadSafeGamepadChangeFetcher>;
~ThreadSafeGamepadChangeFetcher() override {}
// Enables or disables polling.
void EnablePollingOnPollingThread(bool enabled) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
is_enabled_ = enabled;
if (is_enabled_) {
if (!fetcher_) {
fetcher_ = create_fetcher_callback_.Run();
InitializeDataFetcher(fetcher_.get());
DCHECK(fetcher_);
}
SchedulePollOnPollingThread();
} else {
fetcher_.reset();
}
}
// Schedules the next poll on the polling thread.
void SchedulePollOnPollingThread() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(fetcher_);
if (!is_enabled_ || has_poll_scheduled_)
return;
has_poll_scheduled_ = true;
polling_task_runner_->PostDelayedTask(
FROM_HERE,
base::Bind(&ThreadSafeGamepadChangeFetcher::PollOnPollingThread,
make_scoped_refptr(this)),
base::TimeDelta::FromMilliseconds(kPollingTimeIntervalMs));
}
// Polls devices for new data and posts gamepad changes back to origin thread.
void PollOnPollingThread() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
has_poll_scheduled_ = false;
if (!is_enabled_)
return;
DCHECK(fetcher_);
device::Gamepads new_state = state_;
fetcher_->GetGamepadData(
false /* No hardware changed notification from the system */);
for (size_t i = 0; i < device::Gamepads::kItemsLengthCap; ++i) {
device::PadState& pad_state = pad_states_.get()[i];
// After querying the gamepad clear the state if it did not have it's
// active
// state updated but is still listed as being associated with a specific
// source. This indicates the gamepad is disconnected.
if (!pad_state.active_state &&
pad_state.source != device::GAMEPAD_SOURCE_NONE) {
ClearPadState(pad_state);
}
MapAndSanitizeGamepadData(&pad_state, &new_state.items[i],
false /* Don't sanitize gamepad data */);
// If the gamepad is still actively reporting the next call to
// GetGamepadData will set the active state to active again.
if (pad_state.active_state)
pad_state.active_state = device::GAMEPAD_INACTIVE;
if (new_state.items[i].connected != state_.items[i].connected ||
new_state.items[i].timestamp > state_.items[i].timestamp) {
origin_task_runner_->PostTask(
FROM_HERE,
base::Bind(process_gamepad_changes_, i, new_state.items[i]));
}
}
state_ = new_state;
SchedulePollOnPollingThread();
}
// Callback to ProcessGamepadChanges using weak reference to Gamepad.
ProcessGamepadChangesCallback process_gamepad_changes_;
// Callback method to create a gamepad data fetcher.
CreateGamepadDataFetcherCallback create_fetcher_callback_;
// Reference to task runner on polling thread.
scoped_refptr<base::SingleThreadTaskRunner> polling_task_runner_;
// Reference to task runner on origin thread.
scoped_refptr<base::SingleThreadTaskRunner> origin_task_runner_;
// Gamepad data fetcher used for querying gamepad devices.
std::unique_ptr<device::GamepadDataFetcher> fetcher_;
// The current state of all gamepads.
device::Gamepads state_;
// True if a poll has been scheduled.
bool has_poll_scheduled_ = false;
// True if the polling thread is paused.
bool is_enabled_ = false;
// ThreadChecker for the polling thread.
THREAD_CHECKER(thread_checker_);
DISALLOW_COPY_AND_ASSIGN(ThreadSafeGamepadChangeFetcher);
};
////////////////////////////////////////////////////////////////////////////////
// GamingSeat, public:
GamingSeat::GamingSeat(GamingSeatDelegate* gaming_seat_delegate,
base::SingleThreadTaskRunner* polling_task_runner)
: delegate_(gaming_seat_delegate),
gamepad_delegates_{nullptr},
weak_ptr_factory_(this) {
gamepad_change_fetcher_ = new ThreadSafeGamepadChangeFetcher(
base::Bind(&GamingSeat::ProcessGamepadChanges,
weak_ptr_factory_.GetWeakPtr()),
base::Bind(CreateGamepadPlatformDataFetcher), polling_task_runner);
auto* helper = WMHelper::GetInstance();
helper->AddFocusObserver(this);
OnWindowFocused(helper->GetFocusedWindow(), nullptr);
}
GamingSeat::~GamingSeat() {
// Disable polling. Since ThreadSafeGamepadChangeFetcher are reference
// counted, we can safely have it shut down after Gamepad has been destroyed.
gamepad_change_fetcher_->EnablePolling(false);
delegate_->OnGamingSeatDestroying(this);
for (size_t i = 0; i < device::Gamepads::kItemsLengthCap; ++i) {
if (gamepad_delegates_[i]) {
gamepad_delegates_[i]->OnRemoved();
}
}
WMHelper::GetInstance()->RemoveFocusObserver(this);
}
////////////////////////////////////////////////////////////////////////////////
// aura::client::FocusChangeObserver overrides:
void GamingSeat::OnWindowFocused(aura::Window* gained_focus,
aura::Window* lost_focus) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
Surface* target = nullptr;
if (gained_focus) {
target = Surface::AsSurface(gained_focus);
if (!target) {
aura::Window* top_level_window = gained_focus->GetToplevelWindow();
if (top_level_window)
target = ShellSurface::GetMainSurface(top_level_window);
}
}
bool focused = target && delegate_->CanAcceptGamepadEventsForSurface(target);
gamepad_change_fetcher_->EnablePolling(focused);
}
////////////////////////////////////////////////////////////////////////////////
// GamingSeat, private:
void GamingSeat::ProcessGamepadChanges(int index,
const device::Gamepad new_pad) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
bool send_frame = false;
device::Gamepad& pad_state = pad_state_.items[index];
// Update connection state.
GamepadDelegate* delegate = gamepad_delegates_[index];
if (new_pad.connected != pad_state.connected) {
// New pad is disconnected.
if (!new_pad.connected) {
// If gamepad is disconnected now, it should be connected before, then
// gamepad_delegate should not be null.
DCHECK(delegate);
delegate->OnRemoved();
gamepad_delegates_[index] = nullptr;
pad_state = new_pad;
return;
} else if (new_pad.connected) {
gamepad_delegates_[index] = delegate_->GamepadAdded();
}
}
if (!delegate || !new_pad.connected ||
new_pad.timestamp <= pad_state.timestamp) {
pad_state = new_pad;
return;
}
// Notify delegate of updated axes.
for (size_t axis = 0;
axis < std::max(pad_state.axes_length, new_pad.axes_length); ++axis) {
if (!GamepadButtonValuesAreEqual(new_pad.axes[axis],
pad_state.axes[axis])) {
send_frame = true;
delegate->OnAxis(axis, new_pad.axes[axis]);
}
}
// Notify delegate of updated buttons.
for (size_t button_id = 0;
button_id < std::max(pad_state.buttons_length, new_pad.buttons_length);
++button_id) {
auto button = pad_state.buttons[button_id];
auto new_button = new_pad.buttons[button_id];
if (button.pressed != new_button.pressed ||
!GamepadButtonValuesAreEqual(button.value, new_button.value)) {
send_frame = true;
delegate->OnButton(button_id, new_button.pressed, new_button.value);
}
}
if (send_frame)
delegate->OnFrame();
pad_state = new_pad;
}
} // namespace exo