ladybird/Libraries/LibWeb/Gamepad/Gamepad.cpp
ayeteadoe 454e6a6f7f LibWeb/Gamepad: Forward declare SDL components to fix Windows build
We have to prevent from including any SDL headers in LibWeb headers.
Otherwise there will be transitive Windows.h includes that will
re-declare some of our existing forward decls/defines in
LibCore/SocketAddressWindows.h
2025-09-02 11:11:12 +01:00

628 lines
27 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 2025, Jelle Raaijmakers <jelle@ladybird.org>
* Copyright (c) 2025, Luke Wilde <luke@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Gamepad/EventNames.h>
#include <LibWeb/Gamepad/Gamepad.h>
#include <LibWeb/Gamepad/GamepadButton.h>
#include <LibWeb/Gamepad/GamepadEvent.h>
#include <LibWeb/Gamepad/GamepadHapticActuator.h>
#include <LibWeb/HTML/Navigator.h>
#include <LibWeb/HTML/Scripting/Environments.h>
#include <LibWeb/HTML/Window.h>
#include <LibWeb/HighResolutionTime/TimeOrigin.h>
#include <SDL3/SDL_gamepad.h>
namespace Web::Gamepad {
GC_DEFINE_ALLOCATOR(Gamepad);
// https://w3c.github.io/gamepad/#dfn-standard-gamepad
// Type Index Location
// Button 0 Bottom button in right cluster
// 1 Right button in right cluster
// 2 Left button in right cluster
// 3 Top button in right cluster
// 4 Top left front button
// 5 Top right front button
// 6 Bottom left front button
// 7 Bottom right front button
// 8 Left button in center cluster
// 9 Right button in center cluster
// 10 Left stick pressed button
// 11 Right stick pressed button
// 12 Top button in left cluster
// 13 Bottom button in left cluster
// 14 Left button in left cluster
// 15 Right button in left cluster
// 16 Center button in center cluster
static Array<Variant<SDL_GamepadButton, SDL_GamepadAxis, Empty>, 17> standard_gamepad_button_layout {
SDL_GAMEPAD_BUTTON_SOUTH,
SDL_GAMEPAD_BUTTON_EAST,
SDL_GAMEPAD_BUTTON_WEST,
SDL_GAMEPAD_BUTTON_NORTH,
SDL_GAMEPAD_BUTTON_LEFT_SHOULDER,
SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER,
SDL_GAMEPAD_AXIS_LEFT_TRIGGER,
SDL_GAMEPAD_AXIS_RIGHT_TRIGGER,
SDL_GAMEPAD_BUTTON_BACK,
SDL_GAMEPAD_BUTTON_START,
SDL_GAMEPAD_BUTTON_LEFT_STICK,
SDL_GAMEPAD_BUTTON_RIGHT_STICK,
SDL_GAMEPAD_BUTTON_DPAD_UP,
SDL_GAMEPAD_BUTTON_DPAD_DOWN,
SDL_GAMEPAD_BUTTON_DPAD_LEFT,
SDL_GAMEPAD_BUTTON_DPAD_RIGHT,
SDL_GAMEPAD_BUTTON_GUIDE,
};
static Array<SDL_GamepadButton, 11> non_standard_gamepad_button_layout {
SDL_GAMEPAD_BUTTON_MISC1,
SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1,
SDL_GAMEPAD_BUTTON_LEFT_PADDLE1,
SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2,
SDL_GAMEPAD_BUTTON_LEFT_PADDLE2,
SDL_GAMEPAD_BUTTON_TOUCHPAD,
SDL_GAMEPAD_BUTTON_MISC2,
SDL_GAMEPAD_BUTTON_MISC3,
SDL_GAMEPAD_BUTTON_MISC4,
SDL_GAMEPAD_BUTTON_MISC5,
SDL_GAMEPAD_BUTTON_MISC6,
};
// axes 0 Horizontal axis for left stick (negative left/positive right)
// 1 Vertical axis for left stick (negative up/positive down)
// 2 Horizontal axis for right stick (negative left/positive right)
// 3 Vertical axis for right stick (negative up/positive down)
static Array<SDL_GamepadAxis, 4> standard_gamepad_axes_layout {
SDL_GAMEPAD_AXIS_LEFTX,
SDL_GAMEPAD_AXIS_LEFTY,
SDL_GAMEPAD_AXIS_RIGHTX,
SDL_GAMEPAD_AXIS_RIGHTY,
};
// https://w3c.github.io/gamepad/#dfn-button-press-threshold
// For buttons which do not have a digital switch to indicate a pure pressed or released state, the user
// agent MUST choose a button press threshold to indicate the button as pressed when its value is above a
// certain amount. If the platform API gives a recommended value, the user agent SHOULD use that. In other
// cases, the user agent SHOULD choose some other reasonable value.
static constexpr double ANALOG_BUTTON_PRESS_THRESHOLD = 0.1;
static constexpr double GAMEPAD_EXPOSURE_AXIS_THRESHOLD = 0.5;
// https://w3c.github.io/gamepad/#dfn-a-new-gamepad
GC::Ref<Gamepad> Gamepad::create(JS::Realm& realm, SDL_JoystickID sdl_joystick_id)
{
// 1. Let gamepad be a newly created Gamepad instance:
auto gamepad = realm.create<Gamepad>(realm, sdl_joystick_id);
// 1. Initialize gamepad's id attribute to an identification string for the gamepad.
// FIXME: What is the encoding used by SDL?
auto const* name = SDL_GetGamepadNameForID(sdl_joystick_id);
if (name) {
gamepad->m_id = Utf16String::from_utf8(StringView { name, strlen(name) });
}
// 2. Initialize gamepad's index attribute to the result of selecting an unused gamepad index for gamepad.
// https://w3c.github.io/gamepad/#dfn-selecting-an-unused-gamepad-index
// 1. Let navigator be gamepad's relevant global object's Navigator object.
// The rest of the steps are implemented in NavigatorGamepad.
// NOTE: Gamepad is only exposed on Window.
auto& window = as<HTML::Window>(HTML::relevant_global_object(gamepad));
gamepad->m_index = window.navigator()->select_an_unused_gamepad_index({});
// 3. Initialize gamepad's mapping attribute to the result of selecting a mapping for the gamepad device.
gamepad->select_a_mapping();
// 4. Set gamepad.[[connected]] to true.
gamepad->m_connected = true;
// 5. Set gamepad.[[timestamp]] to the current high resolution time given gamepad's relevant global object.
gamepad->m_timestamp = HighResolutionTime::current_high_resolution_time(HTML::relevant_global_object(gamepad));
// 6. Set gamepad.[[axes]] to the result of initializing axes for gamepad.
gamepad->initialize_axes();
// 7. Set gamepad.[[buttons]] to the result of initializing buttons for gamepad.
gamepad->initialize_buttons();
// 8. Set gamepad.[[vibrationActuator]] to the result of constructing a GamepadHapticActuator for gamepad.
gamepad->m_vibration_actuator = GamepadHapticActuator::create(realm, gamepad);
// 2. Return gamepad.
return gamepad;
}
Gamepad::Gamepad(JS::Realm& realm, SDL_JoystickID sdl_joystick_id)
: PlatformObject(realm)
, m_sdl_joystick_id(sdl_joystick_id)
{
m_sdl_gamepad = SDL_OpenGamepad(m_sdl_joystick_id);
}
void Gamepad::initialize(JS::Realm& realm)
{
WEB_SET_PROTOTYPE_FOR_INTERFACE(Gamepad);
Base::initialize(realm);
}
void Gamepad::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_buttons);
visitor.visit(m_vibration_actuator);
}
void Gamepad::finalize()
{
SDL_CloseGamepad(m_sdl_gamepad);
}
// https://w3c.github.io/gamepad/#dfn-initializing-axes
void Gamepad::initialize_axes()
{
// 1. Let inputCount be the number of axis inputs exposed by the device represented by gamepad.
Vector<SDL_GamepadAxis> inputs;
// 2. Set gamepad.[[axisMinimums]] to a list of unsigned long values with size equal to inputCount containing minimum logical values for each of the axis inputs.
// 3. Set gamepad.[[axisMaximums]] to a list of unsigned long values with size equal to inputCount containing maximum logical values for each of the axis inputs.
for (auto const standard_gamepad_axis : standard_gamepad_axes_layout) {
if (SDL_GamepadHasAxis(m_sdl_gamepad, standard_gamepad_axis)) {
inputs.append(standard_gamepad_axis);
m_axis_minimums.append(SDL_JOYSTICK_AXIS_MIN);
m_axis_maximums.append(SDL_JOYSTICK_AXIS_MAX);
}
}
// 4. Let unmappedInputList be an empty list.
Vector<size_t> unmapped_input_list;
// 5. Let mappedIndexList be an empty list.
Vector<size_t> mapped_index_list;
// 6. Let axesSize be 0.
size_t axes_size = 0;
// 7. For each rawInputIndex of the range from 0 to inputCount 1:
for (size_t raw_input_index = 0; raw_input_index < inputs.size(); ++raw_input_index) {
// 1. If the gamepad axis at index rawInputIndex represents a Standard Gamepad axis:
auto const axis = inputs[raw_input_index];
if (auto maybe_index = standard_gamepad_axes_layout.first_index_of(axis); maybe_index.has_value()) {
// 1. Let canonicalIndex be the canonical index for the axis.
auto canonical_index = maybe_index.value();
// 2. If mappedIndexList contains canonicalIndex, then append rawInputIndex to unmappedInputList.
if (mapped_index_list.contains_slow(canonical_index)) {
unmapped_input_list.append(raw_input_index);
} else {
// Otherwise:
// 1. Set gamepad.[[axisMapping]][rawInputIndex] to canonicalIndex.
m_axis_mapping.set(raw_input_index, canonical_index);
// 2. Append canonicalIndex to mappedIndexList.
mapped_index_list.append(canonical_index);
// 3. If canonicalIndex + 1 is greater than axesSize, then set axesSize to canonicalIndex + 1.
if (canonical_index + 1 > axes_size)
axes_size = canonical_index + 1;
}
} else {
// Otherwise, append rawInputIndex to unmappedInputList.
unmapped_input_list.append(raw_input_index);
}
}
// 8. Let axisIndex be 0.
size_t axis_index = 0;
// 9. For each rawInputIndex of unmappedInputList:
for (size_t raw_input_index : unmapped_input_list) {
// 1. While mappedIndexList contains axisIndex:
while (mapped_index_list.contains_slow(axis_index)) {
// 1. Increment axisIndex.
++axis_index;
}
// 2. Set gamepad.[[axisMapping]][rawInputIndex] to axisIndex.
m_axis_mapping.set(raw_input_index, axis_index);
// 3. Append axisIndex to mappedIndexList.
mapped_index_list.append(axis_index);
// 4. If axisIndex + 1 is greater than axesSize, then set axesSize to axisIndex + 1.
if (axis_index + 1 > axes_size)
axes_size = axis_index + 1;
}
// NOTE: Instead of returning a list, we can just directly update m_buttons.
// 10. Let axes be an empty list.
// 11. For each axisIndex of the range from 0 to axesSize 1, append 0 to axes.
// 12. Return axes.
for (size_t final_axis_index = 0; final_axis_index < axes_size; ++final_axis_index)
m_axes.append(0.0);
}
// https://w3c.github.io/gamepad/#dfn-initializing-buttons
void Gamepad::initialize_buttons()
{
auto& realm = this->realm();
// 1. Let inputCount be the number of button inputs exposed by the device represented by gamepad.
Vector<Variant<SDL_GamepadButton, SDL_GamepadAxis>> inputs;
// 2. Set gamepad.[[buttonMinimums]] to be a list of unsigned long values with size equal to inputCount containing minimum logical values for each of the button inputs.
// 3. Set gamepad.[[buttonMaximums]] to be a list of unsigned long values with size equal to inputCount containing maximum logical values for each of the button inputs.
for (auto const& standard_gamepad_button : standard_gamepad_button_layout) {
standard_gamepad_button.visit(
[this, &inputs](SDL_GamepadButton button) {
if (SDL_GamepadHasButton(m_sdl_gamepad, button)) {
inputs.append(button);
// Buttons are binary inputs with SDL.
m_button_minimums.append(0);
m_button_maximums.append(1);
}
},
[this, &inputs](SDL_GamepadAxis axis) {
if (SDL_GamepadHasAxis(m_sdl_gamepad, axis)) {
inputs.append(axis);
// "Trigger axis values range from 0 (released) to SDL_JOYSTICK_AXIS_MAX (fully
// pressed) when reported by SDL_GetGamepadAxis(). Note that this is not the
// same range that will be reported by the lower-level SDL_GetJoystickAxis()."
m_button_minimums.append(0);
m_button_maximums.append(SDL_JOYSTICK_AXIS_MAX);
}
},
[](Empty) {
VERIFY_NOT_REACHED();
});
}
for (auto const non_standard_gamepad_button : non_standard_gamepad_button_layout) {
if (SDL_GamepadHasButton(m_sdl_gamepad, non_standard_gamepad_button)) {
inputs.append(non_standard_gamepad_button);
// Buttons are binary inputs with SDL.
m_button_minimums.append(0);
m_button_maximums.append(1);
}
}
// 4. Let unmappedInputList be an empty list.
Vector<size_t> unmapped_input_list;
// 5. Let mappedIndexList be an empty list.
Vector<size_t> mapped_index_list;
// 6. Let buttonsSize be 0.
size_t buttons_size = 0;
// 7. For each rawInputIndex of the range from 0 to inputCount 1:
for (size_t raw_input_index = 0; raw_input_index < inputs.size(); ++raw_input_index) {
auto const& input = inputs[raw_input_index];
// 1. If the gamepad button at index rawInputIndex represents a Standard Gamepad button:
if (auto maybe_index = standard_gamepad_button_layout.first_index_of(input); maybe_index.has_value()) {
// 1. Let canonicalIndex be the canonical index for the button.
auto canonical_index = maybe_index.value();
// 2. If mappedIndexList contains canonicalIndex, then append rawInputIndex to unmappedInputList.
if (mapped_index_list.contains_slow(canonical_index)) {
unmapped_input_list.append(raw_input_index);
} else {
// Otherwise:
// 1. Set gamepad.[[buttonMapping]][rawInputIndex] to canonicalIndex.
m_button_mapping.set(raw_input_index, canonical_index);
// 2. Append canonicalIndex to mappedIndexList.
mapped_index_list.append(canonical_index);
// 3. If canonicalIndex + 1 is greater than buttonsSize, then set buttonsSize to canonicalIndex + 1.
if (canonical_index + 1 > buttons_size)
buttons_size = canonical_index + 1;
}
} else {
// Otherwise, append rawInputIndex to unmappedInputList.
unmapped_input_list.append(raw_input_index);
}
// 2. Increment rawInputIndex.
}
// 8. Let buttonIndex be 0.
size_t button_index = 0;
// 9. For each rawInputIndex of unmappedInputList:
for (size_t raw_input_index : unmapped_input_list) {
// 1. While mappedIndexList contains buttonIndex:
while (mapped_index_list.contains_slow(button_index)) {
// 1. Increment buttonIndex.
++button_index;
}
// 2. Set gamepad.[[buttonMapping]][rawInputIndex] to buttonIndex.
m_button_mapping.set(raw_input_index, button_index);
// 3. Append buttonIndex to mappedIndexList.
mapped_index_list.append(button_index);
// 4. If buttonIndex + 1 is greater than buttonsSize, then set buttonsSize to buttonIndex + 1.
if (button_index + 1 > buttons_size)
buttons_size = button_index + 1;
}
// NOTE: Instead of returning a list (and thus needing to use RootVector), we can just directly update m_buttons.
// 10. Let buttons be an empty list.
// 11. For each buttonIndex of the range from 0 to buttonsSize 1, append a new GamepadButton to buttons.
// 12. Return buttons.
for (size_t final_button_index = 0; final_button_index < buttons_size; ++final_button_index) {
auto gamepad_button = realm.create<GamepadButton>(realm);
m_buttons.append(gamepad_button);
}
}
GC::Ref<GamepadHapticActuator> Gamepad::vibration_actuator() const
{
VERIFY(m_vibration_actuator);
return *m_vibration_actuator;
}
void Gamepad::set_connected(Badge<NavigatorGamepadPartial>, bool value)
{
m_connected = value;
}
void Gamepad::set_exposed(Badge<NavigatorGamepadPartial>, bool value)
{
m_exposed = value;
}
void Gamepad::set_timestamp(Badge<NavigatorGamepadPartial>, HighResolutionTime::DOMHighResTimeStamp value)
{
m_timestamp = value;
}
// https://w3c.github.io/gamepad/#dfn-selecting-a-mapping
void Gamepad::select_a_mapping()
{
// 1. If the button and axis layout of the gamepad device corresponds with the Standard Gamepad layout, then
// return "standard".
// 2. Return "".
for (auto const& standard_gamepad_button : standard_gamepad_button_layout) {
bool has_standard_button = standard_gamepad_button.visit(
[this](SDL_GamepadButton button) -> bool {
return SDL_GamepadHasButton(m_sdl_gamepad, button);
},
[this](SDL_GamepadAxis axis) -> bool {
return SDL_GamepadHasAxis(m_sdl_gamepad, axis);
},
[](Empty) -> bool {
VERIFY_NOT_REACHED();
});
if (!has_standard_button) {
m_mapping = Bindings::GamepadMappingType::Empty;
return;
}
}
for (auto const standard_gamepad_axis : standard_gamepad_axes_layout) {
if (!SDL_GamepadHasAxis(m_sdl_gamepad, standard_gamepad_axis)) {
m_mapping = Bindings::GamepadMappingType::Empty;
return;
}
}
m_mapping = Bindings::GamepadMappingType::Standard;
}
// https://w3c.github.io/gamepad/#dfn-map-and-normalize-axes
void Gamepad::map_and_normalize_axes()
{
// 1. Let axisValues be a list of unsigned long values representing the most recent logical axis input values for
// each axis input of the device represented by gamepad.
// NOTE: While the Gamepad API internally uses u32 to represent raw axis values, SDL uses i16 for axes.
Vector<i16> axis_values;
for (auto const standard_gamepad_axis : standard_gamepad_axes_layout) {
if (SDL_GamepadHasAxis(m_sdl_gamepad, standard_gamepad_axis))
axis_values.append(SDL_GetGamepadAxis(m_sdl_gamepad, standard_gamepad_axis));
}
// 2. Let maxRawAxisIndex be the size of axisValues 1.
// 3. For each rawAxisIndex of the range from 0 to maxRawAxisIndex:
for (size_t raw_axis_index = 0; raw_axis_index < axis_values.size(); ++raw_axis_index) {
// 1. Let mappedIndex be gamepad.[[axisMapping]][rawAxisIndex].
auto mapped_index = m_axis_mapping.get(raw_axis_index).value();
// 2. Let logicalValue be axisValues[rawAxisIndex].
auto logical_value = axis_values[raw_axis_index];
// 3. Let logicalMinimum be gamepad.[[axisMinimums]][rawAxisIndex].
auto logical_minimum = m_axis_minimums[raw_axis_index];
// 4. Let logicalMaximum be gamepad.[[axisMaximums]][rawAxisIndex].
auto logical_maximum = m_axis_maximums[raw_axis_index];
// 5. Let normalizedValue be 2 (logicalValue logicalMinimum) / (logicalMaximum logicalMinimum) 1.
double normalized_value = 2.0 * static_cast<double>(logical_value - logical_minimum) / static_cast<double>(logical_maximum - logical_minimum) - 1.0;
// 6. Set gamepad.[[axes]][axisIndex] to be normalizedValue.
// FIXME: axisIndex should be mappedIndex.
m_axes[mapped_index] = normalized_value;
}
}
// https://w3c.github.io/gamepad/#dfn-map-and-normalize-buttons
void Gamepad::map_and_normalize_buttons()
{
// 1. Let buttonValues be a list of unsigned long values representing the most recent logical button input values
// for each button input of the device represented by gamepad.
// NOTE: While the Gamepad API internally uses u32 to represent raw button values, SDL uses bool for buttons and
// i16 for axes. The left and right triggers are buttons in the Gamepad API.
Vector<i16> button_values;
for (auto const& standard_gamepad_button : standard_gamepad_button_layout) {
standard_gamepad_button.visit(
[this, &button_values](SDL_GamepadButton button) {
if (SDL_GamepadHasButton(m_sdl_gamepad, button)) {
bool button_pressed = SDL_GetGamepadButton(m_sdl_gamepad, button);
button_values.append(button_pressed ? 1 : 0);
}
},
[this, &button_values](SDL_GamepadAxis axis) {
if (SDL_GamepadHasAxis(m_sdl_gamepad, axis))
button_values.append(SDL_GetGamepadAxis(m_sdl_gamepad, axis));
},
[](Empty) {
VERIFY_NOT_REACHED();
});
}
for (auto const non_standard_gamepad_button : non_standard_gamepad_button_layout) {
if (SDL_GamepadHasButton(m_sdl_gamepad, non_standard_gamepad_button)) {
bool button_pressed = SDL_GetGamepadButton(m_sdl_gamepad, non_standard_gamepad_button);
button_values.append(button_pressed ? 1 : 0);
}
}
// 2. Let maxRawButtonIndex be the size of buttonValues 1.
// 3. For each rawButtonIndex of the range from 0 to maxRawButtonIndex:
for (size_t raw_button_index = 0; raw_button_index < button_values.size(); ++raw_button_index) {
// 1. Let mappedIndex be gamepad.[[buttonMapping]][rawButtonIndex].
auto mapped_index = m_button_mapping.get(raw_button_index).value();
// 2. Let logicalValue be buttonValues[rawButtonIndex].
auto logical_value = button_values[raw_button_index];
// 3. Let logicalMinimum be gamepad.[[buttonMinimums]][rawButtonIndex].
auto logical_minimum = m_button_minimums[raw_button_index];
// 4. Let logicalMaximum be gamepad.[[buttonMaximums]][rawButtonIndex].
auto logical_maximum = m_button_maximums[raw_button_index];
// 5. Let normalizedValue be (logicalValue logicalMinimum) / (logicalMaximum logicalMinimum).
double value = static_cast<double>(logical_value - logical_minimum) / static_cast<double>(logical_maximum - logical_minimum);
// 6. Let button be gamepad.[[buttons]][mappedIndex].
auto button = m_buttons[mapped_index];
// 7. Set button.[[value]] to normalizedValue.
button->set_value({}, value);
// 8. If the button has a digital switch to indicate a pure pressed or released state, set button.[[pressed]]
// to true if the button is pressed or false if it is not pressed.
// Otherwise, set button.[[pressed]] to true if the value is above the button press threshold or false if
// it is not above the threshold.
if (logical_maximum == 1) {
button->set_pressed({}, logical_value == 1);
} else {
button->set_pressed({}, value > ANALOG_BUTTON_PRESS_THRESHOLD);
}
// 9. If the button is capable of detecting touch, set button.[[touched]] to true if the button is currently being touched.
// Otherwise, set button.[[touched]] to button.[[pressed]].
// FIXME: Support the PS4/PS5 controller which has a touchpad, which is a button that can be touched and not pressed in at the same time.
button->set_touched({}, button->pressed());
}
}
// https://w3c.github.io/gamepad/#dfn-update-gamepad-state
void Gamepad::update_gamepad_state(Badge<NavigatorGamepadPartial>)
{
auto& realm = this->realm();
// 1. Let now be the current high resolution time given gamepad's relevant global object.
auto& window = as<HTML::Window>(HTML::relevant_global_object(*this));
auto now = HighResolutionTime::current_high_resolution_time(window);
// 2. Set gamepad.[[timestamp]] to now.
m_timestamp = now;
// 3. Run the steps to map and normalize axes for gamepad.
map_and_normalize_axes();
// 4. Run the steps to map and normalize buttons for gamepad.
map_and_normalize_buttons();
// FIXME: 5. Run the steps to record touches for gamepad.
// 6. Let navigator be gamepad's relevant global object's Navigator object.
auto navigator = window.navigator();
// 7. If navigator.[[hasGamepadGesture]] is false and gamepad contains a gamepad user gesture:
if (!navigator->has_gamepad_gesture() && contains_gamepad_user_gesture()) {
// 1. Set navigator.[[hasGamepadGesture]] to true.
navigator->set_has_gamepad_gesture({}, true);
// 2. For each connectedGamepad of navigator.[[gamepads]]:
for (auto connected_gamepad : navigator->gamepads({})) {
// 1. If connectedGamepad is not equal to null:
if (connected_gamepad) {
// 1. Set connectedGamepad.[[exposed]] to true.
connected_gamepad->m_exposed = true;
// 2. Set connectedGamepad.[[timestamp]] to now.
connected_gamepad->m_timestamp = now;
// 3. Let document be gamepad's relevant global object's associated Document; otherwise null.
auto& document = window.associated_document();
// 4. If document is not null and is fully active, then queue a global task on the gamepad task source
// to fire an event named gamepadconnected at gamepad's relevant global object using GamepadEvent
// with its gamepad attribute initialized to connectedGamepad.
if (document.is_fully_active()) {
auto gamepad_connected_event_init = GamepadEventInit {
{
.bubbles = false,
.cancelable = false,
.composed = false,
},
connected_gamepad,
};
auto gamepad_connected_event = MUST(GamepadEvent::construct_impl(realm, EventNames::gamepadconnected, gamepad_connected_event_init));
window.dispatch_event(gamepad_connected_event);
}
}
}
}
}
// https://w3c.github.io/gamepad/#dfn-gamepad-user-gesture
bool Gamepad::contains_gamepad_user_gesture()
{
// A gamepad contains a gamepad user gesture if the current input state indicates that the user is currently
// interacting with the gamepad. The user agent MUST provide an algorithm to check if the input state contains a
// gamepad user gesture. For buttons that support a neutral default value and have reported a pressed value of
// false at least once, a pressed value of true SHOULD be considered interaction. If a button does not support a
// neutral default value (for example, a toggle switch), then a pressed value of true SHOULD NOT be considered
// interaction. If a button has never reported a pressed value of false then it SHOULD NOT be considered
// interaction. Axis movements SHOULD be considered interaction if the axis supports a neutral default value, the
// current displacement from neutral is greater than a threshold chosen by the user agent, and the axis has
// reported a value below the threshold at least once. If an axis does not support a neutral default value (for
// example, an axis for a joystick that does not self-center), or an axis has never reported a value below the axis
// gesture threshold, then the axis SHOULD NOT be considered when checking for interaction. The axis gesture
// threshold SHOULD be large enough that random jitter is not considered interaction.
// NOTE: This roughly follows Chrome, where it exposes gamepads if a button is pressed (even if it's held across
// a refresh) or an absolute axis is above 0.5.
auto pressed_button = m_buttons.find_if([](GC::Ref<GamepadButton> gamepad_button) {
return gamepad_button->pressed();
});
if (!pressed_button.is_end())
return true;
auto axis_above_threshold = m_axes.find_if([](double value) {
return abs(value) > GAMEPAD_EXPOSURE_AXIS_THRESHOLD;
});
return !axis_above_threshold.is_end();
}
}