mirror of
https://github.com/servo/servo.git
synced 2025-07-23 07:13:52 +01:00
Implement non-XR Gamepad discovery and input (#31200)
* Create embedder event to send to constellation * Handle gamepad message in constellation, send to script thread * Handle GamepadEvent in script thread and dispatch event to document * Add missing Clones, fix event * Add gamepad task source * Adjust GamepadIndex type, remove unused imports * Add internal getter for gamepads list * Update gamepad new methods * Handle gamepad connect and disconnect events * Proto will be none, no need for HandleObject * Initialize buttons and axes to standard mapping * Adjust update type index types * Update GamepadButton update function * Adjust Gamepad mapping comments to match spec, add update logic * Amend comment * Update button and axis inputs on Updated event * Add GilRs as gamepad backend in servoshell * Add spec links, queue gamepad updates on task source * ./mach fmt * Fix comment length * Split out button init, update spec comments * Move gamepad event handling from document to global * Map and normalize axes/button values * Use std::time for gamepad timestamp * Adjust gamepad handling in event loop * Move button press/touch check into map+normalize function - Small change but is more in line with spec * ./mach fmt * Update comment spec links and warning messages * Doc comments -> regular comments * Add window event handlers for gamepad connect/disconnect * Adjust gamepad disconnect behavior * Add missing TODO's, adjust gamepad/gamepadbutton list methods and formatting * Update button handling from gilrs, add comments * Enable gamepad pref during WPT tests and update expectations * Update WPT expectations in meta-legacy-layout
This commit is contained in:
parent
1cc546c4fc
commit
c999d4546c
27 changed files with 695 additions and 208 deletions
86
Cargo.lock
generated
86
Cargo.lock
generated
|
@ -2005,6 +2005,40 @@ dependencies = [
|
|||
"weezl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gilrs"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8b2e57a9cb946b5d04ae8638c5f554abb5a9f82c4c950fd5b1fee6d119592fb"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"gilrs-core",
|
||||
"log",
|
||||
"uuid",
|
||||
"vec_map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gilrs-core"
|
||||
version = "0.5.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0af1827b7dd2f36d740ae804c1b3ea0d64c12533fb61ff91883005143a0e8c5a"
|
||||
dependencies = [
|
||||
"core-foundation",
|
||||
"inotify",
|
||||
"io-kit-sys",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"libudev-sys",
|
||||
"log",
|
||||
"nix 0.27.1",
|
||||
"uuid",
|
||||
"vec_map",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
"windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.28.1"
|
||||
|
@ -2807,6 +2841,26 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fdd168d97690d0b8c412d6b6c10360277f4d7ee495c5d0d5d5fe0854923255cc"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"inotify-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify-sys"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.12"
|
||||
|
@ -2819,6 +2873,16 @@ dependencies = [
|
|||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-kit-sys"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4769cb30e5dcf1710fc6730d3e94f78c47723a014a567de385e113c737394640"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"mach2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-surface"
|
||||
version = "0.15.1"
|
||||
|
@ -3329,6 +3393,16 @@ dependencies = [
|
|||
"webxr-api",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libudev-sys"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libz-sys"
|
||||
version = "1.1.15"
|
||||
|
@ -3879,6 +3953,17 @@ dependencies = [
|
|||
"pin-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.27.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
|
||||
dependencies = [
|
||||
"bitflags 2.4.2",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nodrop"
|
||||
version = "0.1.14"
|
||||
|
@ -5396,6 +5481,7 @@ dependencies = [
|
|||
"egui_glow",
|
||||
"euclid",
|
||||
"getopts",
|
||||
"gilrs",
|
||||
"gleam",
|
||||
"glow",
|
||||
"image",
|
||||
|
|
|
@ -13,7 +13,9 @@ use gfx::rendering_context::RenderingContext;
|
|||
use keyboard_types::KeyboardEvent;
|
||||
use libc::c_void;
|
||||
use msg::constellation_msg::{PipelineId, TopLevelBrowsingContextId, TraversalDirection};
|
||||
use script_traits::{MediaSessionActionType, MouseButton, TouchEventType, TouchId, WheelDelta};
|
||||
use script_traits::{
|
||||
GamepadEvent, MediaSessionActionType, MouseButton, TouchEventType, TouchId, WheelDelta,
|
||||
};
|
||||
use servo_geometry::DeviceIndependentPixel;
|
||||
use servo_url::ServoUrl;
|
||||
use style_traits::DevicePixel;
|
||||
|
@ -113,6 +115,8 @@ pub enum EmbedderEvent {
|
|||
/// the native widget when it is brough back to foreground. This event
|
||||
/// carries the pointer to the native widget and its new size.
|
||||
ReplaceNativeSurface(*mut c_void, DeviceIntSize),
|
||||
/// Sent when new Gamepad information is available.
|
||||
Gamepad(GamepadEvent),
|
||||
}
|
||||
|
||||
impl Debug for EmbedderEvent {
|
||||
|
@ -149,6 +153,7 @@ impl Debug for EmbedderEvent {
|
|||
EmbedderEvent::ClearCache => write!(f, "ClearCache"),
|
||||
EmbedderEvent::InvalidateNativeSurface => write!(f, "InvalidateNativeSurface"),
|
||||
EmbedderEvent::ReplaceNativeSurface(..) => write!(f, "ReplaceNativeSurface"),
|
||||
EmbedderEvent::Gamepad(..) => write!(f, "Gamepad"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -142,7 +142,7 @@ use script_traits::CompositorEvent::{MouseButtonEvent, MouseMoveEvent};
|
|||
use script_traits::{
|
||||
webdriver_msg, AnimationState, AnimationTickType, AuxiliaryBrowsingContextLoadInfo,
|
||||
BroadcastMsg, CompositorEvent, ConstellationControlMsg, DiscardBrowsingContext,
|
||||
DocumentActivity, DocumentState, HistoryEntryReplacement, IFrameLoadInfo,
|
||||
DocumentActivity, DocumentState, GamepadEvent, HistoryEntryReplacement, IFrameLoadInfo,
|
||||
IFrameLoadInfoWithData, IFrameSandboxState, IFrameSizeMsg, Job, LayoutControlMsg,
|
||||
LayoutMsg as FromLayoutMsg, LoadData, LoadOrigin, LogEntry, MediaSessionActionType,
|
||||
MessagePortMsg, MouseEventType, PortMessageTask, SWManagerMsg, SWManagerSenders,
|
||||
|
@ -1550,6 +1550,9 @@ where
|
|||
EmbedderMsg::ReadyToPresent,
|
||||
));
|
||||
},
|
||||
FromCompositorMsg::Gamepad(gamepad_event) => {
|
||||
self.handle_gamepad_msg(gamepad_event);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5466,4 +5469,40 @@ where
|
|||
error!("Got a media session action but no active media session is registered");
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle GamepadEvents from the embedder and forward them to the script thread
|
||||
fn handle_gamepad_msg(&mut self, event: GamepadEvent) {
|
||||
// Send to the focused browsing contexts' current pipeline.
|
||||
let focused_browsing_context_id = self
|
||||
.webviews
|
||||
.focused_webview()
|
||||
.map(|(_, webview)| webview.focused_browsing_context_id);
|
||||
match focused_browsing_context_id {
|
||||
Some(browsing_context_id) => {
|
||||
let event = CompositorEvent::GamepadEvent(event);
|
||||
let pipeline_id = match self.browsing_contexts.get(&browsing_context_id) {
|
||||
Some(ctx) => ctx.pipeline_id,
|
||||
None => {
|
||||
return warn!(
|
||||
"{}: Got gamepad event for nonexistent browsing context",
|
||||
browsing_context_id,
|
||||
);
|
||||
},
|
||||
};
|
||||
let msg = ConstellationControlMsg::SendEvent(pipeline_id, event);
|
||||
let result = match self.pipelines.get(&pipeline_id) {
|
||||
Some(pipeline) => pipeline.event_loop.send(msg),
|
||||
None => {
|
||||
return debug!("{}: Got gamepad event after closure", pipeline_id);
|
||||
},
|
||||
};
|
||||
if let Err(e) = result {
|
||||
self.handle_send_error(pipeline_id, e);
|
||||
}
|
||||
},
|
||||
None => {
|
||||
warn!("No focused webview to handle gamepad event");
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,9 +9,10 @@ use js::typedarray::{Float64, Float64Array};
|
|||
|
||||
use super::bindings::typedarrays::HeapTypedArray;
|
||||
use crate::dom::bindings::codegen::Bindings::GamepadBinding::{GamepadHand, GamepadMethods};
|
||||
use crate::dom::bindings::codegen::Bindings::GamepadButtonListBinding::GamepadButtonListMethods;
|
||||
use crate::dom::bindings::inheritance::Castable;
|
||||
use crate::dom::bindings::num::Finite;
|
||||
use crate::dom::bindings::reflector::{DomObject, Reflector};
|
||||
use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, DomObject, Reflector};
|
||||
use crate::dom::bindings::root::{Dom, DomRoot};
|
||||
use crate::dom::bindings::str::DOMString;
|
||||
use crate::dom::event::Event;
|
||||
|
@ -19,8 +20,13 @@ use crate::dom::eventtarget::EventTarget;
|
|||
use crate::dom::gamepadbuttonlist::GamepadButtonList;
|
||||
use crate::dom::gamepadevent::{GamepadEvent, GamepadEventType};
|
||||
use crate::dom::gamepadpose::GamepadPose;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::script_runtime::JSContext;
|
||||
|
||||
// This value is for determining when to consider a non-digital button "pressed".
|
||||
// Like Gecko and Chromium it derives from the XInput trigger threshold.
|
||||
const BUTTON_PRESS_THRESHOLD: f64 = 30.0 / 255.0;
|
||||
|
||||
#[dom_struct]
|
||||
pub struct Gamepad {
|
||||
reflector_: Reflector,
|
||||
|
@ -36,10 +42,10 @@ pub struct Gamepad {
|
|||
pose: Option<Dom<GamepadPose>>,
|
||||
#[ignore_malloc_size_of = "Defined in rust-webvr"]
|
||||
hand: GamepadHand,
|
||||
axis_bounds: (f64, f64),
|
||||
button_bounds: (f64, f64),
|
||||
}
|
||||
|
||||
// TODO: support gamepad discovery
|
||||
#[allow(dead_code)]
|
||||
impl Gamepad {
|
||||
fn new_inherited(
|
||||
gamepad_id: u32,
|
||||
|
@ -51,6 +57,8 @@ impl Gamepad {
|
|||
buttons: &GamepadButtonList,
|
||||
pose: Option<&GamepadPose>,
|
||||
hand: GamepadHand,
|
||||
axis_bounds: (f64, f64),
|
||||
button_bounds: (f64, f64),
|
||||
) -> Gamepad {
|
||||
Self {
|
||||
reflector_: Reflector::new(),
|
||||
|
@ -64,8 +72,54 @@ impl Gamepad {
|
|||
buttons: Dom::from_ref(buttons),
|
||||
pose: pose.map(Dom::from_ref),
|
||||
hand: hand,
|
||||
axis_bounds: axis_bounds,
|
||||
button_bounds: button_bounds,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
global: &GlobalScope,
|
||||
gamepad_id: u32,
|
||||
id: String,
|
||||
axis_bounds: (f64, f64),
|
||||
button_bounds: (f64, f64),
|
||||
) -> DomRoot<Gamepad> {
|
||||
Self::new_with_proto(global, gamepad_id, id, axis_bounds, button_bounds)
|
||||
}
|
||||
|
||||
/// When we construct a new gamepad, we initialize the number of buttons and
|
||||
/// axes corresponding to the "standard" gamepad mapping.
|
||||
/// The spec says UAs *may* do this for fingerprint mitigation, and it also
|
||||
/// happens to simplify implementation
|
||||
/// <https://www.w3.org/TR/gamepad/#fingerprinting-mitigation>
|
||||
fn new_with_proto(
|
||||
global: &GlobalScope,
|
||||
gamepad_id: u32,
|
||||
id: String,
|
||||
axis_bounds: (f64, f64),
|
||||
button_bounds: (f64, f64),
|
||||
) -> DomRoot<Gamepad> {
|
||||
let button_list = GamepadButtonList::init_buttons(global);
|
||||
let gamepad = reflect_dom_object_with_proto(
|
||||
Box::new(Gamepad::new_inherited(
|
||||
gamepad_id,
|
||||
id,
|
||||
0,
|
||||
false,
|
||||
0.,
|
||||
String::from("standard"),
|
||||
&button_list,
|
||||
None,
|
||||
GamepadHand::_empty,
|
||||
axis_bounds,
|
||||
button_bounds,
|
||||
)),
|
||||
global,
|
||||
None,
|
||||
);
|
||||
gamepad.init_axes();
|
||||
gamepad
|
||||
}
|
||||
}
|
||||
|
||||
impl GamepadMethods for Gamepad {
|
||||
|
@ -117,7 +171,6 @@ impl GamepadMethods for Gamepad {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: support gamepad discovery
|
||||
#[allow(dead_code)]
|
||||
impl Gamepad {
|
||||
pub fn gamepad_id(&self) -> u32 {
|
||||
|
@ -143,10 +196,73 @@ impl Gamepad {
|
|||
self.index.set(index);
|
||||
}
|
||||
|
||||
pub fn update_timestamp(&self, timestamp: f64) {
|
||||
self.timestamp.set(timestamp);
|
||||
}
|
||||
|
||||
pub fn notify_event(&self, event_type: GamepadEventType) {
|
||||
let event = GamepadEvent::new_with_type(&self.global(), event_type, &self);
|
||||
event
|
||||
.upcast::<Event>()
|
||||
.fire(self.global().as_window().upcast::<EventTarget>());
|
||||
}
|
||||
|
||||
/// Initialize the number of axes in the "standard" gamepad mapping.
|
||||
/// <https://www.w3.org/TR/gamepad/#dfn-initializing-axes>
|
||||
fn init_axes(&self) {
|
||||
let initial_axes: Vec<f64> = vec![
|
||||
0., // Horizontal axis for left stick (negative left/positive right)
|
||||
0., // Vertical axis for left stick (negative up/positive down)
|
||||
0., // Horizontal axis for right stick (negative left/positive right)
|
||||
0., // Vertical axis for right stick (negative up/positive down)
|
||||
];
|
||||
self.axes
|
||||
.set_data(GlobalScope::get_cx(), &initial_axes)
|
||||
.expect("Failed to set axes data on gamepad.")
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
/// <https://www.w3.org/TR/gamepad/#dfn-map-and-normalize-axes>
|
||||
pub fn map_and_normalize_axes(&self, axis_index: usize, value: f64) {
|
||||
// Let normalizedValue be 2 (logicalValue − logicalMinimum) / (logicalMaximum − logicalMinimum) − 1.
|
||||
let numerator = value - self.axis_bounds.0;
|
||||
let denominator = self.axis_bounds.1 - self.axis_bounds.0;
|
||||
if denominator != 0.0 && denominator.is_finite() {
|
||||
let normalized_value: f64 = 2.0 * numerator / denominator - 1.0;
|
||||
if normalized_value.is_finite() {
|
||||
let mut axis_vec = self
|
||||
.axes
|
||||
.internal_to_option()
|
||||
.expect("Axes have not been initialized!");
|
||||
unsafe {
|
||||
axis_vec.as_mut_slice()[axis_index] = normalized_value;
|
||||
}
|
||||
} else {
|
||||
warn!("Axis value is not finite!");
|
||||
}
|
||||
} else {
|
||||
warn!("Axis bounds difference is either 0 or non-finite!");
|
||||
}
|
||||
}
|
||||
|
||||
/// <https://www.w3.org/TR/gamepad/#dfn-map-and-normalize-buttons>
|
||||
pub fn map_and_normalize_buttons(&self, button_index: usize, value: f64) {
|
||||
// Let normalizedValue be (logicalValue − logicalMinimum) / (logicalMaximum − logicalMinimum).
|
||||
let numerator = value - self.button_bounds.0;
|
||||
let denominator = self.button_bounds.1 - self.button_bounds.0;
|
||||
if denominator != 0.0 && denominator.is_finite() {
|
||||
let normalized_value: f64 = numerator / denominator;
|
||||
if normalized_value.is_finite() {
|
||||
let pressed = normalized_value >= BUTTON_PRESS_THRESHOLD;
|
||||
// TODO: Determine a way of getting touch capability for button
|
||||
if let Some(button) = self.buttons.IndexedGetter(button_index as u32) {
|
||||
button.update(pressed, /*touched*/ pressed, normalized_value);
|
||||
}
|
||||
} else {
|
||||
warn!("Button value is not finite!");
|
||||
}
|
||||
} else {
|
||||
warn!("Button bounds difference is either 0 or non-finite!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,8 +20,6 @@ pub struct GamepadButton {
|
|||
value: Cell<f64>,
|
||||
}
|
||||
|
||||
// TODO: support gamepad discovery
|
||||
#[allow(dead_code)]
|
||||
impl GamepadButton {
|
||||
pub fn new_inherited(pressed: bool, touched: bool) -> GamepadButton {
|
||||
Self {
|
||||
|
@ -57,11 +55,10 @@ impl GamepadButtonMethods for GamepadButton {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: support gamepad discovery
|
||||
#[allow(dead_code)]
|
||||
impl GamepadButton {
|
||||
pub fn update(&self, pressed: bool, touched: bool) {
|
||||
pub fn update(&self, pressed: bool, touched: bool, value: f64) {
|
||||
self.pressed.set(pressed);
|
||||
self.touched.set(touched);
|
||||
self.value.set(value);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,9 +5,10 @@
|
|||
use dom_struct::dom_struct;
|
||||
|
||||
use crate::dom::bindings::codegen::Bindings::GamepadButtonListBinding::GamepadButtonListMethods;
|
||||
use crate::dom::bindings::reflector::Reflector;
|
||||
use crate::dom::bindings::root::{Dom, DomRoot};
|
||||
use crate::dom::bindings::reflector::{reflect_dom_object, Reflector};
|
||||
use crate::dom::bindings::root::{Dom, DomRoot, DomSlice};
|
||||
use crate::dom::gamepadbutton::GamepadButton;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
|
||||
// https://w3c.github.io/gamepad/#gamepadbutton-interface
|
||||
#[dom_struct]
|
||||
|
@ -16,8 +17,6 @@ pub struct GamepadButtonList {
|
|||
list: Vec<Dom<GamepadButton>>,
|
||||
}
|
||||
|
||||
// TODO: support gamepad discovery
|
||||
#[allow(dead_code)]
|
||||
impl GamepadButtonList {
|
||||
#[allow(crown::unrooted_must_root)]
|
||||
fn new_inherited(list: &[&GamepadButton]) -> GamepadButtonList {
|
||||
|
@ -26,6 +25,10 @@ impl GamepadButtonList {
|
|||
list: list.iter().map(|button| Dom::from_ref(*button)).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(global: &GlobalScope, list: &[&GamepadButton]) -> DomRoot<GamepadButtonList> {
|
||||
reflect_dom_object(Box::new(GamepadButtonList::new_inherited(list)), global)
|
||||
}
|
||||
}
|
||||
|
||||
impl GamepadButtonListMethods for GamepadButtonList {
|
||||
|
@ -46,3 +49,31 @@ impl GamepadButtonListMethods for GamepadButtonList {
|
|||
self.Item(index)
|
||||
}
|
||||
}
|
||||
|
||||
impl GamepadButtonList {
|
||||
/// Initialize the number of buttons in the "standard" gamepad mapping.
|
||||
/// <https://www.w3.org/TR/gamepad/#dfn-initializing-buttons>
|
||||
pub fn init_buttons(global: &GlobalScope) -> DomRoot<GamepadButtonList> {
|
||||
let standard_buttons = &[
|
||||
GamepadButton::new(global, false, false), // Bottom button in right cluster
|
||||
GamepadButton::new(global, false, false), // Right button in right cluster
|
||||
GamepadButton::new(global, false, false), // Left button in right cluster
|
||||
GamepadButton::new(global, false, false), // Top button in right cluster
|
||||
GamepadButton::new(global, false, false), // Top left front button
|
||||
GamepadButton::new(global, false, false), // Top right front button
|
||||
GamepadButton::new(global, false, false), // Bottom left front button
|
||||
GamepadButton::new(global, false, false), // Bottom right front button
|
||||
GamepadButton::new(global, false, false), // Left button in center cluster
|
||||
GamepadButton::new(global, false, false), // Right button in center cluster
|
||||
GamepadButton::new(global, false, false), // Left stick pressed button
|
||||
GamepadButton::new(global, false, false), // Right stick pressed button
|
||||
GamepadButton::new(global, false, false), // Top button in left cluster
|
||||
GamepadButton::new(global, false, false), // Bottom button in left cluster
|
||||
GamepadButton::new(global, false, false), // Left button in left cluster
|
||||
GamepadButton::new(global, false, false), // Right button in left cluster
|
||||
GamepadButton::new(global, false, false), // Center button in center cluster
|
||||
];
|
||||
rooted_vec!(let buttons <- standard_buttons.iter().map(|button| DomRoot::from_ref(&**button)));
|
||||
Self::new(global, buttons.r())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,8 +18,6 @@ pub struct GamepadList {
|
|||
list: DomRefCell<Vec<Dom<Gamepad>>>,
|
||||
}
|
||||
|
||||
// TODO: support gamepad discovery
|
||||
#[allow(dead_code)]
|
||||
impl GamepadList {
|
||||
fn new_inherited(list: &[&Gamepad]) -> GamepadList {
|
||||
GamepadList {
|
||||
|
@ -46,6 +44,10 @@ impl GamepadList {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_gamepad(&self, index: usize) {
|
||||
self.list.borrow_mut().remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
impl GamepadListMethods for GamepadList {
|
||||
|
|
|
@ -51,8 +51,8 @@ use profile_traits::{ipc as profile_ipc, mem as profile_mem, time as profile_tim
|
|||
use script_traits::serializable::{BlobData, BlobImpl, FileBlob};
|
||||
use script_traits::transferable::MessagePortImpl;
|
||||
use script_traits::{
|
||||
BroadcastMsg, MessagePortMsg, MsDuration, PortMessageTask, ScriptMsg,
|
||||
ScriptToConstellationChan, TimerEvent, TimerEventId, TimerSchedulerMsg, TimerSource,
|
||||
BroadcastMsg, GamepadEvent, GamepadUpdateType, MessagePortMsg, MsDuration, PortMessageTask,
|
||||
ScriptMsg, ScriptToConstellationChan, TimerEvent, TimerEventId, TimerSchedulerMsg, TimerSource,
|
||||
};
|
||||
use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
|
||||
use uuid::Uuid;
|
||||
|
@ -63,9 +63,12 @@ use super::bindings::trace::HashMapTracedValues;
|
|||
use crate::dom::bindings::cell::{DomRefCell, RefMut};
|
||||
use crate::dom::bindings::codegen::Bindings::BroadcastChannelBinding::BroadcastChannelMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::EventSourceBinding::EventSource_Binding::EventSourceMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::GamepadListBinding::GamepadList_Binding::GamepadListMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::ImageBitmapBinding::{
|
||||
ImageBitmapOptions, ImageBitmapSource,
|
||||
};
|
||||
use crate::dom::bindings::codegen::Bindings::NavigatorBinding::Navigator_Binding::NavigatorMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::PerformanceBinding::Performance_Binding::PerformanceMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::PermissionState;
|
||||
use crate::dom::bindings::codegen::Bindings::VoidFunctionBinding::VoidFunction;
|
||||
use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
|
||||
|
@ -92,6 +95,7 @@ use crate::dom::event::{Event, EventBubbles, EventCancelable, EventStatus};
|
|||
use crate::dom::eventsource::EventSource;
|
||||
use crate::dom::eventtarget::EventTarget;
|
||||
use crate::dom::file::File;
|
||||
use crate::dom::gamepad::Gamepad;
|
||||
use crate::dom::gpudevice::GPUDevice;
|
||||
use crate::dom::htmlscriptelement::{ScriptId, SourceCode};
|
||||
use crate::dom::identityhub::Identities;
|
||||
|
@ -118,6 +122,7 @@ use crate::script_thread::{MainThreadScriptChan, ScriptThread};
|
|||
use crate::task::TaskCanceller;
|
||||
use crate::task_source::dom_manipulation::DOMManipulationTaskSource;
|
||||
use crate::task_source::file_reading::FileReadingTaskSource;
|
||||
use crate::task_source::gamepad::GamepadTaskSource;
|
||||
use crate::task_source::networking::NetworkingTaskSource;
|
||||
use crate::task_source::performance_timeline::PerformanceTimelineTaskSource;
|
||||
use crate::task_source::port_message::PortMessageQueue;
|
||||
|
@ -2517,6 +2522,16 @@ impl GlobalScope {
|
|||
unreachable!();
|
||||
}
|
||||
|
||||
/// `TaskSource` to send messages to the gamepad task source of
|
||||
/// this global scope.
|
||||
/// <https://w3c.github.io/gamepad/#dfn-gamepad-task-source>
|
||||
pub fn gamepad_task_source(&self) -> GamepadTaskSource {
|
||||
if let Some(window) = self.downcast::<Window>() {
|
||||
return window.task_manager().gamepad_task_source();
|
||||
}
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
/// `TaskSource` to send messages to the networking task source of
|
||||
/// this global scope.
|
||||
pub fn networking_task_source(&self) -> NetworkingTaskSource {
|
||||
|
@ -3091,6 +3106,121 @@ impl GlobalScope {
|
|||
.handle_server_msg(scope, result);
|
||||
}
|
||||
|
||||
pub fn handle_gamepad_event(&self, gamepad_event: GamepadEvent) {
|
||||
match gamepad_event {
|
||||
GamepadEvent::Connected(index, name, bounds) => {
|
||||
self.handle_gamepad_connect(
|
||||
index.0,
|
||||
name,
|
||||
bounds.axis_bounds,
|
||||
bounds.button_bounds,
|
||||
);
|
||||
},
|
||||
GamepadEvent::Disconnected(index) => {
|
||||
self.handle_gamepad_disconnect(index.0);
|
||||
},
|
||||
GamepadEvent::Updated(index, update_type) => {
|
||||
self.receive_new_gamepad_button_or_axis(index.0, update_type);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/// <https://www.w3.org/TR/gamepad/#dfn-gamepadconnected>
|
||||
pub fn handle_gamepad_connect(
|
||||
&self,
|
||||
index: usize,
|
||||
name: String,
|
||||
axis_bounds: (f64, f64),
|
||||
button_bounds: (f64, f64),
|
||||
) {
|
||||
// TODO: 2. If document is not null and is not allowed to use the "gamepad" permission,
|
||||
// then abort these steps.
|
||||
let this = Trusted::new(&*self);
|
||||
self.gamepad_task_source().queue(
|
||||
task!(gamepad_connected: move || {
|
||||
let global = this.root();
|
||||
let gamepad = Gamepad::new(&global, index as u32, name, axis_bounds, button_bounds);
|
||||
|
||||
if let Some(window) = global.downcast::<Window>() {
|
||||
let gamepad_list = window.Navigator().GetGamepads();
|
||||
let gamepad_arr: [DomRoot<Gamepad>; 1] = [gamepad.clone()];
|
||||
gamepad_list.add_if_not_exists(&gamepad_arr);
|
||||
|
||||
// TODO: 3.4 If navigator.[[hasGamepadGesture]] is true:
|
||||
// TODO: 3.4.1 Set gamepad.[[exposed]] to true.
|
||||
|
||||
if window.Document().is_fully_active() {
|
||||
gamepad.update_connected(true);
|
||||
}
|
||||
}
|
||||
}),
|
||||
&self,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// <https://www.w3.org/TR/gamepad/#dfn-gamepaddisconnected>
|
||||
pub fn handle_gamepad_disconnect(&self, index: usize) {
|
||||
let this = Trusted::new(&*self);
|
||||
self.gamepad_task_source()
|
||||
.queue(
|
||||
task!(gamepad_disconnected: move || {
|
||||
let global = this.root();
|
||||
if let Some(window) = global.downcast::<Window>() {
|
||||
let gamepad_list = window.Navigator().GetGamepads();
|
||||
if let Some(gamepad) = gamepad_list.Item(index as u32) {
|
||||
// TODO: If gamepad.[[exposed]]
|
||||
gamepad.update_connected(false);
|
||||
gamepad_list.remove_gamepad(index);
|
||||
}
|
||||
for i in (0..gamepad_list.Length()).rev() {
|
||||
if gamepad_list.Item(i as u32).is_none() {
|
||||
gamepad_list.remove_gamepad(i as usize);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
&self,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// <https://www.w3.org/TR/gamepad/#receiving-inputs>
|
||||
pub fn receive_new_gamepad_button_or_axis(&self, index: usize, update_type: GamepadUpdateType) {
|
||||
let this = Trusted::new(&*self);
|
||||
|
||||
// <https://w3c.github.io/gamepad/#dfn-update-gamepad-state>
|
||||
self.gamepad_task_source()
|
||||
.queue(
|
||||
task!(update_gamepad_state: move || {
|
||||
let global = this.root();
|
||||
if let Some(window) = global.downcast::<Window>() {
|
||||
let gamepad_list = window.Navigator().GetGamepads();
|
||||
if let Some(gamepad) = gamepad_list.IndexedGetter(index as u32) {
|
||||
let current_time = global.performance().Now();
|
||||
gamepad.update_timestamp(*current_time);
|
||||
|
||||
match update_type {
|
||||
GamepadUpdateType::Axis(index, value) => {
|
||||
gamepad.map_and_normalize_axes(index, value);
|
||||
},
|
||||
GamepadUpdateType::Button(index, value) => {
|
||||
gamepad.map_and_normalize_buttons(index, value);
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: 6. If navigator.[[hasGamepadGesture]] is false
|
||||
// and gamepad contains a gamepad user gesture:
|
||||
}
|
||||
}
|
||||
}),
|
||||
&self,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub(crate) fn current_group_label(&self) -> Option<DOMString> {
|
||||
self.console_group_stack
|
||||
.borrow()
|
||||
|
|
|
@ -515,6 +515,8 @@ macro_rules! window_event_handlers(
|
|||
event_handler!(unhandledrejection, GetOnunhandledrejection,
|
||||
SetOnunhandledrejection);
|
||||
event_handler!(unload, GetOnunload, SetOnunload);
|
||||
event_handler!(gamepadconnected, GetOngamepadconnected, SetOngamepadconnected);
|
||||
event_handler!(gamepaddisconnected, GetOngamepaddisconnected, SetOngamepaddisconnected);
|
||||
);
|
||||
(ForwardToWindow) => (
|
||||
window_owned_event_handler!(afterprint, GetOnafterprint,
|
||||
|
@ -541,6 +543,8 @@ macro_rules! window_event_handlers(
|
|||
window_owned_event_handler!(unhandledrejection, GetOnunhandledrejection,
|
||||
SetOnunhandledrejection);
|
||||
window_owned_event_handler!(unload, GetOnunload, SetOnunload);
|
||||
window_owned_event_handler!(gamepadconnected, GetOngamepadconnected, SetOngamepadconnected);
|
||||
window_owned_event_handler!(gamepaddisconnected, GetOngamepaddisconnected, SetOngamepaddisconnected);
|
||||
);
|
||||
);
|
||||
|
||||
|
|
|
@ -27,3 +27,9 @@ enum GamepadHand {
|
|||
"left",
|
||||
"right"
|
||||
};
|
||||
|
||||
// https://www.w3.org/TR/gamepad/#extensions-to-the-windoweventhandlers-interface-mixin
|
||||
partial interface mixin WindowEventHandlers {
|
||||
attribute EventHandler ongamepadconnected;
|
||||
attribute EventHandler ongamepaddisconnected;
|
||||
};
|
||||
|
|
|
@ -75,8 +75,8 @@ use profile_traits::time::{self as profile_time, profile, ProfilerCategory};
|
|||
use script_layout_interface::message::{self, LayoutThreadInit, Msg, ReflowGoal};
|
||||
use script_traits::webdriver_msg::WebDriverScriptCommand;
|
||||
use script_traits::CompositorEvent::{
|
||||
CompositionEvent, IMEDismissedEvent, KeyboardEvent, MouseButtonEvent, MouseMoveEvent,
|
||||
ResizeEvent, TouchEvent, WheelEvent,
|
||||
CompositionEvent, GamepadEvent, IMEDismissedEvent, KeyboardEvent, MouseButtonEvent,
|
||||
MouseMoveEvent, ResizeEvent, TouchEvent, WheelEvent,
|
||||
};
|
||||
use script_traits::{
|
||||
AnimationTickType, CompositorEvent, ConstellationControlMsg, DiscardBrowsingContext,
|
||||
|
@ -152,6 +152,7 @@ use crate::task_manager::TaskManager;
|
|||
use crate::task_queue::{QueuedTask, QueuedTaskConversion, TaskQueue};
|
||||
use crate::task_source::dom_manipulation::DOMManipulationTaskSource;
|
||||
use crate::task_source::file_reading::FileReadingTaskSource;
|
||||
use crate::task_source::gamepad::GamepadTaskSource;
|
||||
use crate::task_source::history_traversal::HistoryTraversalTaskSource;
|
||||
use crate::task_source::media_element::MediaElementTaskSource;
|
||||
use crate::task_source::networking::NetworkingTaskSource;
|
||||
|
@ -568,6 +569,8 @@ pub struct ScriptThread {
|
|||
|
||||
dom_manipulation_task_sender: Box<dyn ScriptChan>,
|
||||
|
||||
gamepad_task_sender: Box<dyn ScriptChan>,
|
||||
|
||||
#[no_trace]
|
||||
media_element_task_sender: Sender<MainThreadScriptMsg>,
|
||||
|
||||
|
@ -1354,6 +1357,7 @@ impl ScriptThread {
|
|||
|
||||
chan: MainThreadScriptChan(chan.clone()),
|
||||
dom_manipulation_task_sender: boxed_script_sender.clone(),
|
||||
gamepad_task_sender: boxed_script_sender.clone(),
|
||||
media_element_task_sender: chan.clone(),
|
||||
user_interaction_task_sender: chan.clone(),
|
||||
networking_task_sender: boxed_script_sender.clone(),
|
||||
|
@ -2858,6 +2862,10 @@ impl ScriptThread {
|
|||
DOMManipulationTaskSource(self.dom_manipulation_task_sender.clone(), pipeline_id)
|
||||
}
|
||||
|
||||
pub fn gamepad_task_source(&self, pipeline_id: PipelineId) -> GamepadTaskSource {
|
||||
GamepadTaskSource(self.gamepad_task_sender.clone(), pipeline_id)
|
||||
}
|
||||
|
||||
pub fn media_element_task_source(&self, pipeline_id: PipelineId) -> MediaElementTaskSource {
|
||||
MediaElementTaskSource(self.media_element_task_sender.clone(), pipeline_id)
|
||||
}
|
||||
|
@ -3283,6 +3291,7 @@ impl ScriptThread {
|
|||
let task_manager = TaskManager::new(
|
||||
self.dom_manipulation_task_source(incomplete.pipeline_id),
|
||||
self.file_reading_task_source(incomplete.pipeline_id),
|
||||
self.gamepad_task_source(incomplete.pipeline_id),
|
||||
self.history_traversal_task_source(incomplete.pipeline_id),
|
||||
self.media_element_task_source(incomplete.pipeline_id),
|
||||
self.networking_task_source(incomplete.pipeline_id),
|
||||
|
@ -3669,6 +3678,15 @@ impl ScriptThread {
|
|||
};
|
||||
document.dispatch_composition_event(composition_event);
|
||||
},
|
||||
|
||||
GamepadEvent(gamepad_event) => {
|
||||
let window = match self.documents.borrow().find_window(pipeline_id) {
|
||||
Some(window) => window,
|
||||
None => return warn!("Message sent to closed pipeline {}.", pipeline_id),
|
||||
};
|
||||
let global = window.upcast::<GlobalScope>();
|
||||
global.handle_gamepad_event(gamepad_event);
|
||||
},
|
||||
}
|
||||
|
||||
ScriptThread::set_user_interacting(false);
|
||||
|
|
|
@ -10,6 +10,7 @@ use crate::dom::bindings::cell::DomRefCell;
|
|||
use crate::task::TaskCanceller;
|
||||
use crate::task_source::dom_manipulation::DOMManipulationTaskSource;
|
||||
use crate::task_source::file_reading::FileReadingTaskSource;
|
||||
use crate::task_source::gamepad::GamepadTaskSource;
|
||||
use crate::task_source::history_traversal::HistoryTraversalTaskSource;
|
||||
use crate::task_source::media_element::MediaElementTaskSource;
|
||||
use crate::task_source::networking::NetworkingTaskSource;
|
||||
|
@ -42,6 +43,8 @@ pub struct TaskManager {
|
|||
#[ignore_malloc_size_of = "task sources are hard"]
|
||||
file_reading_task_source: FileReadingTaskSource,
|
||||
#[ignore_malloc_size_of = "task sources are hard"]
|
||||
gamepad_task_source: GamepadTaskSource,
|
||||
#[ignore_malloc_size_of = "task sources are hard"]
|
||||
history_traversal_task_source: HistoryTraversalTaskSource,
|
||||
#[ignore_malloc_size_of = "task sources are hard"]
|
||||
media_element_task_source: MediaElementTaskSource,
|
||||
|
@ -65,6 +68,7 @@ impl TaskManager {
|
|||
pub fn new(
|
||||
dom_manipulation_task_source: DOMManipulationTaskSource,
|
||||
file_reading_task_source: FileReadingTaskSource,
|
||||
gamepad_task_source: GamepadTaskSource,
|
||||
history_traversal_task_source: HistoryTraversalTaskSource,
|
||||
media_element_task_source: MediaElementTaskSource,
|
||||
networking_task_source: NetworkingTaskSource,
|
||||
|
@ -78,6 +82,7 @@ impl TaskManager {
|
|||
TaskManager {
|
||||
dom_manipulation_task_source,
|
||||
file_reading_task_source,
|
||||
gamepad_task_source,
|
||||
history_traversal_task_source,
|
||||
media_element_task_source,
|
||||
networking_task_source,
|
||||
|
@ -99,6 +104,14 @@ impl TaskManager {
|
|||
DOMManipulation
|
||||
);
|
||||
|
||||
task_source_functions!(
|
||||
self,
|
||||
gamepad_task_source_with_canceller,
|
||||
gamepad_task_source,
|
||||
GamepadTaskSource,
|
||||
Gamepad
|
||||
);
|
||||
|
||||
task_source_functions!(
|
||||
self,
|
||||
media_element_task_source_with_canceller,
|
||||
|
|
47
components/script/task_source/gamepad.rs
Normal file
47
components/script/task_source/gamepad.rs
Normal file
|
@ -0,0 +1,47 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::fmt;
|
||||
use std::result::Result;
|
||||
|
||||
use msg::constellation_msg::PipelineId;
|
||||
|
||||
use crate::script_runtime::{CommonScriptMsg, ScriptChan, ScriptThreadEventCategory};
|
||||
use crate::task::{TaskCanceller, TaskOnce};
|
||||
use crate::task_source::{TaskSource, TaskSourceName};
|
||||
|
||||
#[derive(JSTraceable)]
|
||||
pub struct GamepadTaskSource(
|
||||
pub Box<dyn ScriptChan + Send + 'static>,
|
||||
#[no_trace] pub PipelineId,
|
||||
);
|
||||
|
||||
impl Clone for GamepadTaskSource {
|
||||
fn clone(&self) -> GamepadTaskSource {
|
||||
GamepadTaskSource(self.0.clone(), self.1.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for GamepadTaskSource {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "GamepadTaskSource(...)")
|
||||
}
|
||||
}
|
||||
|
||||
impl TaskSource for GamepadTaskSource {
|
||||
const NAME: TaskSourceName = TaskSourceName::Gamepad;
|
||||
|
||||
fn queue_with_canceller<T>(&self, task: T, canceller: &TaskCanceller) -> Result<(), ()>
|
||||
where
|
||||
T: TaskOnce + 'static,
|
||||
{
|
||||
let msg = CommonScriptMsg::Task(
|
||||
ScriptThreadEventCategory::InputEvent,
|
||||
Box::new(canceller.wrap_task(task)),
|
||||
Some(self.1),
|
||||
GamepadTaskSource::NAME,
|
||||
);
|
||||
self.0.send(msg).map_err(|_| ())
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
pub mod dom_manipulation;
|
||||
pub mod file_reading;
|
||||
pub mod gamepad;
|
||||
pub mod history_traversal;
|
||||
pub mod media_element;
|
||||
pub mod networking;
|
||||
|
@ -38,6 +39,8 @@ pub enum TaskSourceName {
|
|||
MediaElement,
|
||||
Websocket,
|
||||
Timer,
|
||||
/// <https://www.w3.org/TR/gamepad/#dfn-gamepad-task-source>
|
||||
Gamepad,
|
||||
}
|
||||
|
||||
impl TaskSourceName {
|
||||
|
|
|
@ -791,6 +791,13 @@ where
|
|||
);
|
||||
}
|
||||
},
|
||||
|
||||
EmbedderEvent::Gamepad(gamepad_event) => {
|
||||
let msg = ConstellationMsg::Gamepad(gamepad_event);
|
||||
if let Err(e) = self.constellation_chan.send(msg) {
|
||||
warn!("Sending Gamepad event to constellation failed ({:?}).", e);
|
||||
}
|
||||
},
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -14,8 +14,8 @@ use msg::constellation_msg::{
|
|||
BrowsingContextId, PipelineId, TopLevelBrowsingContextId, TraversalDirection,
|
||||
};
|
||||
use script_traits::{
|
||||
AnimationTickType, CompositorEvent, LogEntry, MediaSessionActionType, WebDriverCommandMsg,
|
||||
WindowSizeData, WindowSizeType,
|
||||
AnimationTickType, CompositorEvent, GamepadEvent, LogEntry, MediaSessionActionType,
|
||||
WebDriverCommandMsg, WindowSizeData, WindowSizeType,
|
||||
};
|
||||
use servo_url::ServoUrl;
|
||||
|
||||
|
@ -82,6 +82,8 @@ pub enum ConstellationMsg {
|
|||
IMEDismissed,
|
||||
/// Compositing done, but external code needs to present.
|
||||
ReadyToPresent(TopLevelBrowsingContextId),
|
||||
/// Gamepad state has changed
|
||||
Gamepad(GamepadEvent),
|
||||
}
|
||||
|
||||
impl fmt::Debug for ConstellationMsg {
|
||||
|
@ -117,6 +119,7 @@ impl fmt::Debug for ConstellationMsg {
|
|||
IMEDismissed => "IMEDismissed",
|
||||
ClearCache => "ClearCache",
|
||||
ReadyToPresent(..) => "ReadyToPresent",
|
||||
Gamepad(..) => "Gamepad",
|
||||
};
|
||||
write!(formatter, "ConstellationMsg::{}", variant)
|
||||
}
|
||||
|
|
|
@ -227,6 +227,7 @@ pub enum CompositorEventVariant {
|
|||
KeyboardEvent,
|
||||
CompositionEvent,
|
||||
IMEDismissedEvent,
|
||||
GamepadEvent,
|
||||
}
|
||||
|
||||
impl Debug for EmbedderMsg {
|
||||
|
|
|
@ -569,6 +569,8 @@ pub enum CompositorEvent {
|
|||
CompositionEvent(CompositionEvent),
|
||||
/// Virtual keyboard was dismissed
|
||||
IMEDismissedEvent,
|
||||
/// Connected gamepad state updated
|
||||
GamepadEvent(GamepadEvent),
|
||||
}
|
||||
|
||||
impl From<&CompositorEvent> for CompositorEventVariant {
|
||||
|
@ -582,6 +584,7 @@ impl From<&CompositorEvent> for CompositorEventVariant {
|
|||
CompositorEvent::KeyboardEvent(..) => CompositorEventVariant::KeyboardEvent,
|
||||
CompositorEvent::CompositionEvent(..) => CompositorEventVariant::CompositionEvent,
|
||||
CompositorEvent::IMEDismissedEvent => CompositorEventVariant::IMEDismissedEvent,
|
||||
CompositorEvent::GamepadEvent(..) => CompositorEventVariant::GamepadEvent,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1327,3 +1330,43 @@ impl SerializedImageData {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, Ord, PartialEq, PartialOrd, Serialize,
|
||||
)]
|
||||
/// Index of gamepad in list of system's connected gamepads
|
||||
pub struct GamepadIndex(pub usize);
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
/// The minimum and maximum values that can be reported for axis or button input from this gamepad
|
||||
pub struct GamepadInputBounds {
|
||||
/// Minimum and maximum axis values
|
||||
pub axis_bounds: (f64, f64),
|
||||
/// Minimum and maximum button values
|
||||
pub button_bounds: (f64, f64),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
/// The type of Gamepad event
|
||||
pub enum GamepadEvent {
|
||||
/// A new gamepad has been connected
|
||||
/// <https://www.w3.org/TR/gamepad/#event-gamepadconnected>
|
||||
Connected(GamepadIndex, String, GamepadInputBounds),
|
||||
/// An existing gamepad has been disconnected
|
||||
/// <https://www.w3.org/TR/gamepad/#event-gamepaddisconnected>
|
||||
Disconnected(GamepadIndex),
|
||||
/// An existing gamepad has been updated
|
||||
/// <https://www.w3.org/TR/gamepad/#receiving-inputs>
|
||||
Updated(GamepadIndex, GamepadUpdateType),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
/// The type of Gamepad input being updated
|
||||
pub enum GamepadUpdateType {
|
||||
/// Axis index and input value
|
||||
/// <https://www.w3.org/TR/gamepad/#dfn-represents-a-standard-gamepad-axis>
|
||||
Axis(usize, f64),
|
||||
/// Button index and input value
|
||||
/// <https://www.w3.org/TR/gamepad/#dfn-represents-a-standard-gamepad-button
|
||||
Button(usize, f64),
|
||||
}
|
||||
|
|
|
@ -52,6 +52,7 @@ egui_glow = { version = "0.22.0", features = ["winit"] }
|
|||
egui-winit = { version = "0.22.0", default-features = false, features = ["clipboard", "wayland"] }
|
||||
euclid = { workspace = true }
|
||||
getopts = { workspace = true }
|
||||
gilrs = "0.10.4"
|
||||
gleam = { workspace = true }
|
||||
glow = "0.12.2"
|
||||
keyboard-types = { workspace = true }
|
||||
|
|
|
@ -438,6 +438,9 @@ impl App {
|
|||
|
||||
// Catch some keyboard events, and push the rest onto the WebViewManager event queue.
|
||||
webviews.handle_window_events(embedder_events);
|
||||
if webviews.webview_id().is_some() {
|
||||
webviews.handle_gamepad_events();
|
||||
}
|
||||
|
||||
// Take any new embedder messages from Servo itself.
|
||||
let mut embedder_messages = self.servo.as_mut().unwrap().get_events();
|
||||
|
|
|
@ -12,6 +12,7 @@ use std::{env, thread};
|
|||
|
||||
use arboard::Clipboard;
|
||||
use euclid::{Point2D, Vector2D};
|
||||
use gilrs::{EventType, Gilrs};
|
||||
use keyboard_types::{Key, KeyboardEvent, Modifiers, ShortcutMatcher};
|
||||
use log::{debug, error, info, trace, warn};
|
||||
use servo::compositing::windowing::{EmbedderEvent, WebRenderDebugOption};
|
||||
|
@ -20,7 +21,9 @@ use servo::embedder_traits::{
|
|||
PermissionRequest, PromptDefinition, PromptOrigin, PromptResult,
|
||||
};
|
||||
use servo::msg::constellation_msg::{TopLevelBrowsingContextId as WebViewId, TraversalDirection};
|
||||
use servo::script_traits::TouchEventType;
|
||||
use servo::script_traits::{
|
||||
GamepadEvent, GamepadIndex, GamepadInputBounds, GamepadUpdateType, TouchEventType,
|
||||
};
|
||||
use servo::servo_config::opts;
|
||||
use servo::servo_url::ServoUrl;
|
||||
use servo::webrender_api::ScrollLocation;
|
||||
|
@ -51,6 +54,7 @@ pub struct WebViewManager<Window: WindowPortsMethods + ?Sized> {
|
|||
window: Rc<Window>,
|
||||
event_queue: Vec<EmbedderEvent>,
|
||||
clipboard: Option<Clipboard>,
|
||||
gamepad: Option<Gilrs>,
|
||||
shutdown_requested: bool,
|
||||
}
|
||||
|
||||
|
@ -82,6 +86,13 @@ where
|
|||
None
|
||||
},
|
||||
},
|
||||
gamepad: match Gilrs::new() {
|
||||
Ok(g) => Some(g),
|
||||
Err(e) => {
|
||||
warn!("Error creating gamepad input connection ({})", e);
|
||||
None
|
||||
},
|
||||
},
|
||||
event_queue: Vec::new(),
|
||||
shutdown_requested: false,
|
||||
}
|
||||
|
@ -113,6 +124,109 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// Handle updates to connected gamepads from GilRs
|
||||
pub fn handle_gamepad_events(&mut self) {
|
||||
if let Some(ref mut gilrs) = self.gamepad {
|
||||
while let Some(event) = gilrs.next_event() {
|
||||
let gamepad = gilrs.gamepad(event.id);
|
||||
let name = gamepad.name();
|
||||
let index = GamepadIndex(event.id.into());
|
||||
match event.event {
|
||||
EventType::ButtonPressed(button, _) => {
|
||||
let mapped_index = Self::map_gamepad_button(button);
|
||||
// We only want to send this for a valid digital button, aka on/off only
|
||||
if !matches!(mapped_index, 6 | 7 | 17) {
|
||||
let update_type = GamepadUpdateType::Button(mapped_index, 1.0);
|
||||
let event = GamepadEvent::Updated(index, update_type);
|
||||
self.event_queue.push(EmbedderEvent::Gamepad(event));
|
||||
}
|
||||
},
|
||||
EventType::ButtonReleased(button, _) => {
|
||||
let mapped_index = Self::map_gamepad_button(button);
|
||||
// We only want to send this for a valid digital button, aka on/off only
|
||||
if !matches!(mapped_index, 6 | 7 | 17) {
|
||||
let update_type = GamepadUpdateType::Button(mapped_index, 0.0);
|
||||
let event = GamepadEvent::Updated(index, update_type);
|
||||
self.event_queue.push(EmbedderEvent::Gamepad(event));
|
||||
}
|
||||
},
|
||||
EventType::ButtonChanged(button, value, _) => {
|
||||
let mapped_index = Self::map_gamepad_button(button);
|
||||
// We only want to send this for a valid non-digital button, aka the triggers
|
||||
if matches!(mapped_index, 6 | 7) {
|
||||
let update_type = GamepadUpdateType::Button(mapped_index, value as f64);
|
||||
let event = GamepadEvent::Updated(index, update_type);
|
||||
self.event_queue.push(EmbedderEvent::Gamepad(event));
|
||||
}
|
||||
},
|
||||
EventType::AxisChanged(axis, value, _) => {
|
||||
// Map axis index and value to represent Standard Gamepad axis
|
||||
// <https://www.w3.org/TR/gamepad/#dfn-represents-a-standard-gamepad-axis>
|
||||
let mapped_axis: usize = match axis {
|
||||
gilrs::Axis::LeftStickX => 0,
|
||||
gilrs::Axis::LeftStickY => 1,
|
||||
gilrs::Axis::RightStickX => 2,
|
||||
gilrs::Axis::RightStickY => 3,
|
||||
_ => 4, // Other axes do not map to "standard" gamepad mapping and are ignored
|
||||
};
|
||||
if mapped_axis < 4 {
|
||||
// The Gamepad spec designates down as positive and up as negative.
|
||||
// GilRs does the inverse of this, so correct for it here.
|
||||
let axis_value = match mapped_axis {
|
||||
0 | 2 => value,
|
||||
1 | 3 => -value,
|
||||
_ => 0., // Should not reach here
|
||||
};
|
||||
let update_type =
|
||||
GamepadUpdateType::Axis(mapped_axis, axis_value as f64);
|
||||
let event = GamepadEvent::Updated(index, update_type);
|
||||
self.event_queue.push(EmbedderEvent::Gamepad(event));
|
||||
}
|
||||
},
|
||||
EventType::Connected => {
|
||||
let name = String::from(name);
|
||||
let bounds = GamepadInputBounds {
|
||||
axis_bounds: (-1.0, 1.0),
|
||||
button_bounds: (0.0, 1.0),
|
||||
};
|
||||
let event = GamepadEvent::Connected(index, name, bounds);
|
||||
self.event_queue.push(EmbedderEvent::Gamepad(event));
|
||||
},
|
||||
EventType::Disconnected => {
|
||||
let event = GamepadEvent::Disconnected(index);
|
||||
self.event_queue.push(EmbedderEvent::Gamepad(event));
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Map button index and value to represent Standard Gamepad button
|
||||
// <https://www.w3.org/TR/gamepad/#dfn-represents-a-standard-gamepad-button>
|
||||
fn map_gamepad_button(button: gilrs::Button) -> usize {
|
||||
match button {
|
||||
gilrs::Button::South => 0,
|
||||
gilrs::Button::East => 1,
|
||||
gilrs::Button::West => 2,
|
||||
gilrs::Button::North => 3,
|
||||
gilrs::Button::LeftTrigger => 4,
|
||||
gilrs::Button::RightTrigger => 5,
|
||||
gilrs::Button::LeftTrigger2 => 6,
|
||||
gilrs::Button::RightTrigger2 => 7,
|
||||
gilrs::Button::Select => 8,
|
||||
gilrs::Button::Start => 9,
|
||||
gilrs::Button::LeftThumb => 10,
|
||||
gilrs::Button::RightThumb => 11,
|
||||
gilrs::Button::DPadUp => 12,
|
||||
gilrs::Button::DPadDown => 13,
|
||||
gilrs::Button::DPadLeft => 14,
|
||||
gilrs::Button::DPadRight => 15,
|
||||
gilrs::Button::Mode => 16,
|
||||
_ => 17, // Other buttons do not map to "standard" gamepad mapping and are ignored
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shutdown_requested(&self) -> bool {
|
||||
self.shutdown_requested
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
prefs: ["dom.gamepad.enabled:true"]
|
||||
prefs: [dom.gamepad.enabled:true]
|
||||
|
|
|
@ -8,24 +8,6 @@
|
|||
[Stringification of new GamepadEvent("gamepad")]
|
||||
expected: FAIL
|
||||
|
||||
[HTMLBodyElement interface: attribute ongamepadconnected]
|
||||
expected: FAIL
|
||||
|
||||
[HTMLBodyElement interface: attribute ongamepaddisconnected]
|
||||
expected: FAIL
|
||||
|
||||
[Window interface: attribute ongamepadconnected]
|
||||
expected: FAIL
|
||||
|
||||
[Window interface: attribute ongamepaddisconnected]
|
||||
expected: FAIL
|
||||
|
||||
[HTMLFrameSetElement interface: attribute ongamepadconnected]
|
||||
expected: FAIL
|
||||
|
||||
[HTMLFrameSetElement interface: attribute ongamepaddisconnected]
|
||||
expected: FAIL
|
||||
|
||||
[Gamepad interface: attribute vibrationActuator]
|
||||
expected: FAIL
|
||||
|
||||
|
|
1
tests/wpt/meta/gamepad/__dir__.ini
Normal file
1
tests/wpt/meta/gamepad/__dir__.ini
Normal file
|
@ -0,0 +1 @@
|
|||
prefs: [dom.gamepad.enabled: true]
|
|
@ -4,9 +4,3 @@
|
|||
|
||||
[Feature-Policy allow="gamepad" disallows cross-origin by default.]
|
||||
expected: FAIL
|
||||
|
||||
[Feature-Policy allow="gamepad" allows same-origin by default.]
|
||||
expected: FAIL
|
||||
|
||||
[Feature-Policy allow="gamepad" allows cross-origin by default.]
|
||||
expected: FAIL
|
||||
|
|
|
@ -70,51 +70,3 @@
|
|||
|
||||
[Gamepad interface: attribute vibrationActuator]
|
||||
expected: FAIL
|
||||
|
||||
[GamepadPose interface: existence and properties of interface object]
|
||||
expected: FAIL
|
||||
|
||||
[GamepadPose interface object length]
|
||||
expected: FAIL
|
||||
|
||||
[GamepadPose interface object name]
|
||||
expected: FAIL
|
||||
|
||||
[GamepadPose interface: existence and properties of interface prototype object]
|
||||
expected: FAIL
|
||||
|
||||
[GamepadPose interface: existence and properties of interface prototype object's "constructor" property]
|
||||
expected: FAIL
|
||||
|
||||
[GamepadPose interface: existence and properties of interface prototype object's @@unscopables property]
|
||||
expected: FAIL
|
||||
|
||||
[GamepadPose interface: attribute hasOrientation]
|
||||
expected: FAIL
|
||||
|
||||
[GamepadPose interface: attribute hasPosition]
|
||||
expected: FAIL
|
||||
|
||||
[GamepadPose interface: attribute position]
|
||||
expected: FAIL
|
||||
|
||||
[GamepadPose interface: attribute linearVelocity]
|
||||
expected: FAIL
|
||||
|
||||
[GamepadPose interface: attribute linearAcceleration]
|
||||
expected: FAIL
|
||||
|
||||
[GamepadPose interface: attribute orientation]
|
||||
expected: FAIL
|
||||
|
||||
[GamepadPose interface: attribute angularVelocity]
|
||||
expected: FAIL
|
||||
|
||||
[GamepadPose interface: attribute angularAcceleration]
|
||||
expected: FAIL
|
||||
|
||||
[Gamepad interface: attribute hand]
|
||||
expected: FAIL
|
||||
|
||||
[Gamepad interface: attribute pose]
|
||||
expected: FAIL
|
||||
|
|
|
@ -8,117 +8,6 @@
|
|||
[Stringification of new GamepadEvent("gamepad")]
|
||||
expected: FAIL
|
||||
|
||||
[HTMLBodyElement interface: attribute ongamepadconnected]
|
||||
expected: FAIL
|
||||
|
||||
[HTMLBodyElement interface: attribute ongamepaddisconnected]
|
||||
expected: FAIL
|
||||
|
||||
[Window interface: attribute ongamepadconnected]
|
||||
expected: FAIL
|
||||
|
||||
[Window interface: attribute ongamepaddisconnected]
|
||||
expected: FAIL
|
||||
|
||||
[HTMLFrameSetElement interface: attribute ongamepadconnected]
|
||||
expected: FAIL
|
||||
|
||||
[HTMLFrameSetElement interface: attribute ongamepaddisconnected]
|
||||
expected: FAIL
|
||||
|
||||
[Gamepad interface: existence and properties of interface object]
|
||||
expected: FAIL
|
||||
|
||||
[Gamepad interface object length]
|
||||
expected: FAIL
|
||||
|
||||
[Gamepad interface object name]
|
||||
expected: FAIL
|
||||
|
||||
[Gamepad interface: existence and properties of interface prototype object]
|
||||
expected: FAIL
|
||||
|
||||
[Gamepad interface: existence and properties of interface prototype object's "constructor" property]
|
||||
expected: FAIL
|
||||
|
||||
[Gamepad interface: existence and properties of interface prototype object's @@unscopables property]
|
||||
expected: FAIL
|
||||
|
||||
[Gamepad interface: attribute id]
|
||||
expected: FAIL
|
||||
|
||||
[Gamepad interface: attribute index]
|
||||
expected: FAIL
|
||||
|
||||
[Gamepad interface: attribute connected]
|
||||
expected: FAIL
|
||||
|
||||
[Gamepad interface: attribute timestamp]
|
||||
expected: FAIL
|
||||
|
||||
[Gamepad interface: attribute mapping]
|
||||
expected: FAIL
|
||||
|
||||
[Gamepad interface: attribute axes]
|
||||
expected: FAIL
|
||||
|
||||
[Gamepad interface: attribute buttons]
|
||||
expected: FAIL
|
||||
|
||||
[GamepadButton interface: existence and properties of interface object]
|
||||
expected: FAIL
|
||||
|
||||
[GamepadButton interface object length]
|
||||
expected: FAIL
|
||||
|
||||
[GamepadButton interface object name]
|
||||
expected: FAIL
|
||||
|
||||
[GamepadButton interface: existence and properties of interface prototype object]
|
||||
expected: FAIL
|
||||
|
||||
[GamepadButton interface: existence and properties of interface prototype object's "constructor" property]
|
||||
expected: FAIL
|
||||
|
||||
[GamepadButton interface: existence and properties of interface prototype object's @@unscopables property]
|
||||
expected: FAIL
|
||||
|
||||
[GamepadButton interface: attribute pressed]
|
||||
expected: FAIL
|
||||
|
||||
[GamepadButton interface: attribute touched]
|
||||
expected: FAIL
|
||||
|
||||
[GamepadButton interface: attribute value]
|
||||
expected: FAIL
|
||||
|
||||
[GamepadEvent interface: existence and properties of interface object]
|
||||
expected: FAIL
|
||||
|
||||
[GamepadEvent interface object length]
|
||||
expected: FAIL
|
||||
|
||||
[GamepadEvent interface object name]
|
||||
expected: FAIL
|
||||
|
||||
[GamepadEvent interface: existence and properties of interface prototype object]
|
||||
expected: FAIL
|
||||
|
||||
[GamepadEvent interface: existence and properties of interface prototype object's "constructor" property]
|
||||
expected: FAIL
|
||||
|
||||
[GamepadEvent interface: existence and properties of interface prototype object's @@unscopables property]
|
||||
expected: FAIL
|
||||
|
||||
[GamepadEvent interface: attribute gamepad]
|
||||
expected: FAIL
|
||||
|
||||
[Navigator interface: operation getGamepads()]
|
||||
expected: FAIL
|
||||
|
||||
[Navigator interface: navigator must inherit property "getGamepads()" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[Gamepad interface: attribute vibrationActuator]
|
||||
expected: FAIL
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue