Implement Gamepad API

This commit is contained in:
Imanol Fernandez 2017-03-29 15:41:40 +02:00
parent 69eda6a60c
commit 0158b5b2af
27 changed files with 1193 additions and 96 deletions

7
Cargo.lock generated
View file

@ -4,7 +4,7 @@ version = "0.0.1"
dependencies = [
"ipc-channel 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"msg 0.0.1",
"rust-webvr 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rust-webvr 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -2193,9 +2193,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "rust-webvr"
version = "0.2.0"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"gl_generator 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libloading 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)",
@ -3495,7 +3496,7 @@ dependencies = [
"checksum ref_slice 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "825740057197b7d43025e7faf6477eaabc03434e153233da02d1f44602f71527"
"checksum regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4278c17d0f6d62dfef0ab00028feb45bd7d2102843f80763474eeb1be8a10c01"
"checksum regex-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9191b1f57603095f105d317e375d19b1c9c5c3185ea9633a99a6dcbed04457"
"checksum rust-webvr 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ae0560bf176cd49f08d3df2784f9bfe74df6f6346b71b98ca3358160316e271"
"checksum rust-webvr 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "454fc4c3a786029ab82c5528c14f01bf965f60f61b3f9b1ed51b4646223eab59"
"checksum rustc-demangle 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3058a43ada2c2d0b92b3ae38007a2d0fa5e9db971be260e0171408a4ff471c95"
"checksum rustc-serialize 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)" = "684ce48436d6465300c9ea783b6b14c4361d6b8dcbb1375b486a69cc19e2dfb0"
"checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084"

View file

@ -102,7 +102,6 @@ use script_traits::{LayoutMsg as FromLayoutMsg, ScriptMsg as FromScriptMsg, Scri
use script_traits::{LogEntry, ServiceWorkerMsg, webdriver_msg};
use script_traits::{MozBrowserErrorType, MozBrowserEvent, WebDriverCommandMsg, WindowSizeData};
use script_traits::{SWManagerMsg, ScopeThings, WindowSizeType};
use script_traits::WebVREventMsg;
use serde::{Deserialize, Serialize};
use servo_config::opts;
use servo_config::prefs::PREFS;
@ -124,7 +123,7 @@ use style_traits::cursor::Cursor;
use style_traits::viewport::ViewportConstraints;
use timer_scheduler::TimerScheduler;
use webrender_traits;
use webvr_traits::WebVRMsg;
use webvr_traits::{WebVREvent, WebVRMsg};
/// The `Constellation` itself. In the servo browser, there is one
/// constellation, which maintains all of the browser global data.
@ -897,9 +896,9 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
assert!(self.webvr_thread.is_none());
self.webvr_thread = Some(webvr_thread)
}
FromCompositorMsg::WebVREvent(pipeline_ids, event) => {
debug!("constellation got WebVR event");
self.handle_webvr_event(pipeline_ids, event);
FromCompositorMsg::WebVREvents(pipeline_ids, events) => {
debug!("constellation got {:?} WebVR events", events.len());
self.handle_webvr_events(pipeline_ids, events);
}
}
}
@ -1326,12 +1325,12 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
}
}
fn handle_webvr_event(&mut self, ids: Vec<PipelineId>, event: WebVREventMsg) {
fn handle_webvr_events(&mut self, ids: Vec<PipelineId>, events: Vec<WebVREvent>) {
for id in ids {
match self.pipelines.get_mut(&id) {
Some(ref pipeline) => {
// Notify script thread
let _ = pipeline.event_loop.send(ConstellationControlMsg::WebVREvent(id, event.clone()));
let _ = pipeline.event_loop.send(ConstellationControlMsg::WebVREvents(id, events.clone()));
},
None => warn!("constellation got webvr event for dead pipeline")
}

View file

@ -107,6 +107,7 @@ use time::Duration;
use uuid::Uuid;
use webrender_traits::{WebGLBufferId, WebGLError, WebGLFramebufferId, WebGLProgramId};
use webrender_traits::{WebGLRenderbufferId, WebGLShaderId, WebGLTextureId};
use webvr_traits::WebVRGamepadHand;
/// A trait to allow tracing (only) DOM objects.
pub unsafe trait JSTraceable {
@ -381,6 +382,7 @@ unsafe_no_jsmanaged_fields!(WebGLRenderbufferId);
unsafe_no_jsmanaged_fields!(WebGLShaderId);
unsafe_no_jsmanaged_fields!(WebGLTextureId);
unsafe_no_jsmanaged_fields!(MediaList);
unsafe_no_jsmanaged_fields!(WebVRGamepadHand);
unsafe impl<'a> JSTraceable for &'a str {
#[inline]

View file

@ -0,0 +1,206 @@
/* 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 core::nonzero::NonZero;
use dom::bindings::codegen::Bindings::GamepadBinding;
use dom::bindings::codegen::Bindings::GamepadBinding::GamepadMethods;
use dom::bindings::inheritance::Castable;
use dom::bindings::js::{JS, Root};
use dom::bindings::num::Finite;
use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object};
use dom::bindings::str::DOMString;
use dom::event::Event;
use dom::eventtarget::EventTarget;
use dom::gamepadbuttonlist::GamepadButtonList;
use dom::gamepadevent::{GamepadEvent, GamepadEventType};
use dom::globalscope::GlobalScope;
use dom::vrpose::VRPose;
use dom_struct::dom_struct;
use js::jsapi::{Heap, JSContext, JSObject};
use js::typedarray::{Float64Array, CreateWith};
use std::cell::Cell;
use std::ptr;
use webvr_traits::{WebVRGamepadData, WebVRGamepadHand, WebVRGamepadState};
#[dom_struct]
pub struct Gamepad {
reflector_: Reflector,
gamepad_id: u32,
id: String,
index: Cell<i32>,
connected: Cell<bool>,
timestamp: Cell<f64>,
mapping_type: String,
axes: Heap<*mut JSObject>,
buttons: JS<GamepadButtonList>,
pose: Option<JS<VRPose>>,
#[ignore_heap_size_of = "Defined in rust-webvr"]
hand: WebVRGamepadHand,
display_id: u32
}
impl Gamepad {
fn new_inherited(gamepad_id: u32,
id: String,
index: i32,
connected: bool,
timestamp: f64,
mapping_type: String,
axes: *mut JSObject,
buttons: &GamepadButtonList,
pose: Option<&VRPose>,
hand: WebVRGamepadHand,
display_id: u32) -> Gamepad {
Self {
reflector_: Reflector::new(),
gamepad_id: gamepad_id,
id: id,
index: Cell::new(index),
connected: Cell::new(connected),
timestamp: Cell::new(timestamp),
mapping_type: mapping_type,
axes: Heap::new(axes),
buttons: JS::from_ref(buttons),
pose: pose.map(JS::from_ref),
hand: hand,
display_id: display_id
}
}
#[allow(unsafe_code)]
pub fn new_from_vr(global: &GlobalScope,
index: i32,
data: &WebVRGamepadData,
state: &WebVRGamepadState) -> Root<Gamepad> {
let buttons = GamepadButtonList::new_from_vr(&global, &state.buttons);
let pose = VRPose::new(&global, &state.pose);
let cx = global.get_cx();
rooted!(in (cx) let mut axes = ptr::null_mut());
unsafe {
let _ = Float64Array::create(cx,
CreateWith::Slice(&state.axes),
axes.handle_mut());
}
reflect_dom_object(box Gamepad::new_inherited(state.gamepad_id,
data.name.clone(),
index,
state.connected,
state.timestamp,
"".into(),
axes.get(),
&buttons,
Some(&pose),
data.hand.clone(),
data.display_id),
global,
GamepadBinding::Wrap)
}
}
impl GamepadMethods for Gamepad {
// https://w3c.github.io/gamepad/#dom-gamepad-id
fn Id(&self) -> DOMString {
DOMString::from(self.id.clone())
}
// https://w3c.github.io/gamepad/#dom-gamepad-index
fn Index(&self) -> i32 {
self.index.get()
}
// https://w3c.github.io/gamepad/#dom-gamepad-connected
fn Connected(&self) -> bool {
self.connected.get()
}
// https://w3c.github.io/gamepad/#dom-gamepad-timestamp
fn Timestamp(&self) -> Finite<f64> {
Finite::wrap(self.timestamp.get())
}
// https://w3c.github.io/gamepad/#dom-gamepad-mapping
fn Mapping(&self) -> DOMString {
DOMString::from(self.mapping_type.clone())
}
#[allow(unsafe_code)]
// https://w3c.github.io/gamepad/#dom-gamepad-axes
unsafe fn Axes(&self, _cx: *mut JSContext) -> NonZero<*mut JSObject> {
NonZero::new(self.axes.get())
}
// https://w3c.github.io/gamepad/#dom-gamepad-buttons
fn Buttons(&self) -> Root<GamepadButtonList> {
Root::from_ref(&*self.buttons)
}
// https://w3c.github.io/gamepad/extensions.html#gamepadhand-enum
fn Hand(&self) -> DOMString {
let value = match self.hand {
WebVRGamepadHand::Unknown => "",
WebVRGamepadHand::Left => "left",
WebVRGamepadHand::Right => "right"
};
value.into()
}
// https://w3c.github.io/gamepad/extensions.html#dom-gamepad-pose
fn GetPose(&self) -> Option<Root<VRPose>> {
self.pose.as_ref().map(|p| Root::from_ref(&**p))
}
// https://w3c.github.io/webvr/spec/1.1/#gamepad-getvrdisplays-attribute
fn DisplayId(&self) -> u32 {
self.display_id
}
}
impl Gamepad {
#[allow(unsafe_code)]
pub fn update_from_vr(&self, state: &WebVRGamepadState) {
self.timestamp.set(state.timestamp);
unsafe {
let cx = self.global().get_cx();
typedarray!(in(cx) let axes: Float64Array = self.axes.get());
if let Ok(mut array) = axes {
array.update(&state.axes);
}
}
self.buttons.sync_from_vr(&state.buttons);
if let Some(ref pose) = self.pose {
pose.update(&state.pose);
}
self.update_connected(state.connected);
}
pub fn gamepad_id(&self) -> u32 {
self.gamepad_id
}
pub fn update_connected(&self, connected: bool) {
if self.connected.get() == connected {
return;
}
self.connected.set(connected);
let event_type = if connected {
GamepadEventType::Connected
} else {
GamepadEventType::Disconnected
};
self.notify_event(event_type);
}
pub fn update_index(&self, index: i32) {
self.index.set(index);
}
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>());
}
}

View file

@ -0,0 +1,61 @@
/* 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 dom::bindings::codegen::Bindings::GamepadButtonBinding;
use dom::bindings::codegen::Bindings::GamepadButtonBinding::GamepadButtonMethods;
use dom::bindings::js::Root;
use dom::bindings::num::Finite;
use dom::bindings::reflector::{Reflector, reflect_dom_object};
use dom::globalscope::GlobalScope;
use dom_struct::dom_struct;
use std::cell::Cell;
#[dom_struct]
pub struct GamepadButton {
reflector_: Reflector,
pressed: Cell<bool>,
touched: Cell<bool>,
value: Cell<f64>,
}
impl GamepadButton {
pub fn new_inherited(pressed: bool, touched: bool) -> GamepadButton {
Self {
reflector_: Reflector::new(),
pressed: Cell::new(pressed),
touched: Cell::new(touched),
value: Cell::new(0.0),
}
}
pub fn new(global: &GlobalScope, pressed: bool, touched: bool) -> Root<GamepadButton> {
reflect_dom_object(box GamepadButton::new_inherited(pressed, touched),
global,
GamepadButtonBinding::Wrap)
}
}
impl GamepadButtonMethods for GamepadButton {
// https://www.w3.org/TR/gamepad/#widl-GamepadButton-pressed
fn Pressed(&self) -> bool {
self.pressed.get()
}
// https://www.w3.org/TR/gamepad/#widl-GamepadButton-touched
fn Touched(&self) -> bool {
self.touched.get()
}
// https://www.w3.org/TR/gamepad/#widl-GamepadButton-value
fn Value(&self) -> Finite<f64> {
Finite::wrap(self.value.get())
}
}
impl GamepadButton {
pub fn update(&self, pressed: bool, touched: bool) {
self.pressed.set(pressed);
self.touched.set(touched);
}
}

View file

@ -0,0 +1,63 @@
/* 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 dom::bindings::codegen::Bindings::GamepadButtonListBinding;
use dom::bindings::codegen::Bindings::GamepadButtonListBinding::GamepadButtonListMethods;
use dom::bindings::js::{JS, Root, RootedReference};
use dom::bindings::reflector::{Reflector, reflect_dom_object};
use dom::gamepadbutton::GamepadButton;
use dom::globalscope::GlobalScope;
use dom_struct::dom_struct;
use webvr_traits::WebVRGamepadButton;
// https://w3c.github.io/gamepad/#gamepadbutton-interface
#[dom_struct]
pub struct GamepadButtonList {
reflector_: Reflector,
list: Vec<JS<GamepadButton>>
}
impl GamepadButtonList {
#[allow(unrooted_must_root)]
fn new_inherited(list: &[&GamepadButton]) -> GamepadButtonList {
GamepadButtonList {
reflector_: Reflector::new(),
list: list.iter().map(|button| JS::from_ref(*button)).collect(),
}
}
pub fn new_from_vr(global: &GlobalScope, buttons: &[WebVRGamepadButton]) -> Root<GamepadButtonList> {
rooted_vec!(let list <- buttons.iter()
.map(|btn| GamepadButton::new(&global, btn.pressed, btn.touched)));
reflect_dom_object(box GamepadButtonList::new_inherited(list.r()),
global,
GamepadButtonListBinding::Wrap)
}
pub fn sync_from_vr(&self, vr_buttons: &[WebVRGamepadButton]) {
let mut index = 0;
for btn in vr_buttons {
self.list.get(index).as_ref().unwrap().update(btn.pressed, btn.touched);
index += 1;
}
}
}
impl GamepadButtonListMethods for GamepadButtonList {
// https://w3c.github.io/gamepad/#dom-gamepad-buttons
fn Length(&self) -> u32 {
self.list.len() as u32
}
// https://w3c.github.io/gamepad/#dom-gamepad-buttons
fn Item(&self, index: u32) -> Option<Root<GamepadButton>> {
self.list.get(index as usize).map(|button| Root::from_ref(&**button))
}
// https://w3c.github.io/gamepad/#dom-gamepad-buttons
fn IndexedGetter(&self, index: u32) -> Option<Root<GamepadButton>> {
self.Item(index)
}
}

View file

@ -0,0 +1,92 @@
/* 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 dom::bindings::codegen::Bindings::EventBinding::EventBinding::EventMethods;
use dom::bindings::codegen::Bindings::GamepadEventBinding;
use dom::bindings::codegen::Bindings::GamepadEventBinding::GamepadEventMethods;
use dom::bindings::error::Fallible;
use dom::bindings::inheritance::Castable;
use dom::bindings::js::{JS, Root};
use dom::bindings::reflector::{DomObject, reflect_dom_object};
use dom::bindings::str::DOMString;
use dom::event::Event;
use dom::gamepad::Gamepad;
use dom::globalscope::GlobalScope;
use dom::window::Window;
use dom_struct::dom_struct;
use servo_atoms::Atom;
#[dom_struct]
pub struct GamepadEvent {
event: Event,
gamepad: JS<Gamepad>,
}
pub enum GamepadEventType {
Connected,
Disconnected
}
impl GamepadEvent {
fn new_inherited(gamepad: &Gamepad) -> GamepadEvent {
GamepadEvent {
event: Event::new_inherited(),
gamepad: JS::from_ref(gamepad),
}
}
pub fn new(global: &GlobalScope,
type_: Atom,
bubbles: bool,
cancelable: bool,
gamepad: &Gamepad)
-> Root<GamepadEvent> {
let ev = reflect_dom_object(box GamepadEvent::new_inherited(&gamepad),
global,
GamepadEventBinding::Wrap);
{
let event = ev.upcast::<Event>();
event.init_event(type_, bubbles, cancelable);
}
ev
}
pub fn new_with_type(global: &GlobalScope, event_type: GamepadEventType, gamepad: &Gamepad)
-> Root<GamepadEvent> {
let name = match event_type {
GamepadEventType::Connected => "gamepadconnected",
GamepadEventType::Disconnected => "gamepaddisconnected"
};
GamepadEvent::new(&global,
name.into(),
false,
false,
&gamepad)
}
// https://w3c.github.io/gamepad/#gamepadevent-interface
pub fn Constructor(window: &Window,
type_: DOMString,
init: &GamepadEventBinding::GamepadEventInit)
-> Fallible<Root<GamepadEvent>> {
Ok(GamepadEvent::new(&window.global(),
Atom::from(type_),
init.parent.bubbles,
init.parent.cancelable,
&init.gamepad))
}
}
impl GamepadEventMethods for GamepadEvent {
// https://w3c.github.io/gamepad/#gamepadevent-interface
fn Gamepad(&self) -> Root<Gamepad> {
Root::from_ref(&*self.gamepad)
}
// https://dom.spec.whatwg.org/#dom-event-istrusted
fn IsTrusted(&self) -> bool {
self.event.IsTrusted()
}
}

View file

@ -0,0 +1,61 @@
/* 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 dom::bindings::cell::DOMRefCell;
use dom::bindings::codegen::Bindings::GamepadListBinding;
use dom::bindings::codegen::Bindings::GamepadListBinding::GamepadListMethods;
use dom::bindings::js::{JS, Root};
use dom::bindings::reflector::{Reflector, reflect_dom_object};
use dom::gamepad::Gamepad;
use dom::globalscope::GlobalScope;
use dom_struct::dom_struct;
// https://www.w3.org/TR/gamepad/
#[dom_struct]
pub struct GamepadList {
reflector_: Reflector,
list: DOMRefCell<Vec<JS<Gamepad>>>
}
impl GamepadList {
fn new_inherited(list: &[&Gamepad]) -> GamepadList {
GamepadList {
reflector_: Reflector::new(),
list: DOMRefCell::new(list.iter().map(|g| JS::from_ref(&**g)).collect())
}
}
pub fn new(global: &GlobalScope, list: &[&Gamepad]) -> Root<GamepadList> {
reflect_dom_object(box GamepadList::new_inherited(list),
global,
GamepadListBinding::Wrap)
}
pub fn add_if_not_exists(&self, gamepads: &[Root<Gamepad>]) {
for gamepad in gamepads {
if !self.list.borrow().iter().any(|g| g.gamepad_id() == gamepad.gamepad_id()) {
self.list.borrow_mut().push(JS::from_ref(&*gamepad));
// Ensure that the gamepad has the correct index
gamepad.update_index(self.list.borrow().len() as i32 - 1);
}
}
}
}
impl GamepadListMethods for GamepadList {
// https://w3c.github.io/gamepad/#dom-navigator-getgamepads
fn Length(&self) -> u32 {
self.list.borrow().len() as u32
}
// https://w3c.github.io/gamepad/#dom-navigator-getgamepads
fn Item(&self, index: u32) -> Option<Root<Gamepad>> {
self.list.borrow().get(index as usize).map(|gamepad| Root::from_ref(&**gamepad))
}
// https://w3c.github.io/gamepad/#dom-navigator-getgamepads
fn IndexedGetter(&self, index: u32) -> Option<Root<Gamepad>> {
self.Item(index)
}
}

View file

@ -290,6 +290,11 @@ pub mod filereadersync;
pub mod focusevent;
pub mod forcetouchevent;
pub mod formdata;
pub mod gamepad;
pub mod gamepadbutton;
pub mod gamepadbuttonlist;
pub mod gamepadevent;
pub mod gamepadlist;
pub mod globalscope;
pub mod hashchangeevent;
pub mod headers;

View file

@ -8,6 +8,7 @@ use dom::bindings::js::{MutNullableJS, Root};
use dom::bindings::reflector::{Reflector, DomObject, reflect_dom_object};
use dom::bindings::str::DOMString;
use dom::bluetooth::Bluetooth;
use dom::gamepadlist::GamepadList;
use dom::mimetypearray::MimeTypeArray;
use dom::navigatorinfo;
use dom::permissions::Permissions;
@ -16,7 +17,6 @@ use dom::serviceworkercontainer::ServiceWorkerContainer;
use dom::vr::VR;
use dom::window::Window;
use dom_struct::dom_struct;
use script_traits::WebVREventMsg;
#[dom_struct]
pub struct Navigator {
@ -26,6 +26,7 @@ pub struct Navigator {
mime_types: MutNullableJS<MimeTypeArray>,
service_worker: MutNullableJS<ServiceWorkerContainer>,
vr: MutNullableJS<VR>,
gamepads: MutNullableJS<GamepadList>,
permissions: MutNullableJS<Permissions>,
}
@ -38,6 +39,7 @@ impl Navigator {
mime_types: Default::default(),
service_worker: Default::default(),
vr: Default::default(),
gamepads: Default::default(),
permissions: Default::default(),
}
}
@ -128,15 +130,19 @@ impl NavigatorMethods for Navigator {
self.vr.or_init(|| VR::new(&self.global()))
}
// https://www.w3.org/TR/gamepad/#navigator-interface-extension
fn GetGamepads(&self) -> Root<GamepadList> {
let root = self.gamepads.or_init(|| {
GamepadList::new(&self.global(), &[])
});
let vr_gamepads = self.Vr().get_gamepads();
root.add_if_not_exists(&vr_gamepads);
// TODO: Add not VR related gamepads
root
}
// https://w3c.github.io/permissions/#navigator-and-workernavigator-extension
fn Permissions(&self) -> Root<Permissions> {
self.permissions.or_init(|| Permissions::new(&self.global()))
}
}
impl Navigator {
pub fn handle_webvr_event(&self, event: WebVREventMsg) {
self.vr.get().expect("Shouldn't arrive here with an empty VR instance")
.handle_webvr_event(event);
}
}

View file

@ -5,12 +5,15 @@
use dom::bindings::cell::DOMRefCell;
use dom::bindings::codegen::Bindings::VRBinding;
use dom::bindings::codegen::Bindings::VRBinding::VRMethods;
use dom::bindings::codegen::Bindings::VRDisplayBinding::VRDisplayMethods;
use dom::bindings::error::Error;
use dom::bindings::inheritance::Castable;
use dom::bindings::js::{JS, Root};
use dom::bindings::reflector::{DomObject, reflect_dom_object};
use dom::event::Event;
use dom::eventtarget::EventTarget;
use dom::gamepad::Gamepad;
use dom::gamepadevent::GamepadEventType;
use dom::globalscope::GlobalScope;
use dom::promise::Promise;
use dom::vrdisplay::VRDisplay;
@ -18,22 +21,23 @@ use dom::vrdisplayevent::VRDisplayEvent;
use dom_struct::dom_struct;
use ipc_channel::ipc;
use ipc_channel::ipc::IpcSender;
use script_traits::WebVREventMsg;
use std::rc::Rc;
use webvr_traits::WebVRMsg;
use webvr_traits::webvr;
use webvr_traits::{WebVRDisplayData, WebVRDisplayEvent, WebVREvent, WebVRMsg};
use webvr_traits::{WebVRGamepadData, WebVRGamepadEvent, WebVRGamepadState};
#[dom_struct]
pub struct VR {
eventtarget: EventTarget,
displays: DOMRefCell<Vec<JS<VRDisplay>>>
displays: DOMRefCell<Vec<JS<VRDisplay>>>,
gamepads: DOMRefCell<Vec<JS<Gamepad>>>
}
impl VR {
fn new_inherited() -> VR {
VR {
eventtarget: EventTarget::new_inherited(),
displays: DOMRefCell::new(Vec::new())
displays: DOMRefCell::new(Vec::new()),
gamepads: DOMRefCell::new(Vec::new()),
}
}
@ -95,10 +99,10 @@ impl VR {
self.global().as_window().webvr_thread()
}
fn find_display(&self, display_id: u64) -> Option<Root<VRDisplay>> {
fn find_display(&self, display_id: u32) -> Option<Root<VRDisplay>> {
self.displays.borrow()
.iter()
.find(|d| d.get_display_id() == display_id)
.find(|d| d.DisplayId() == display_id)
.map(|d| Root::from_ref(&**d))
}
@ -116,7 +120,7 @@ impl VR {
}
}
fn sync_display(&self, display: &webvr::VRDisplayData) -> Root<VRDisplay> {
fn sync_display(&self, display: &WebVRDisplayData) -> Root<VRDisplay> {
if let Some(existing) = self.find_display(display.display_id) {
existing.update_display(&display);
existing
@ -127,35 +131,121 @@ impl VR {
}
}
pub fn handle_webvr_event(&self, event: WebVREventMsg) {
let WebVREventMsg::DisplayEvent(event) = event;
match &event {
&webvr::VRDisplayEvent::Connect(ref display) => {
fn handle_display_event(&self, event: WebVRDisplayEvent) {
match event {
WebVRDisplayEvent::Connect(ref display) => {
let display = self.sync_display(&display);
display.handle_webvr_event(&event);
self.notify_event(&display, &event);
self.notify_display_event(&display, &event);
},
&webvr::VRDisplayEvent::Disconnect(id) => {
WebVRDisplayEvent::Disconnect(id) => {
if let Some(display) = self.find_display(id) {
display.handle_webvr_event(&event);
self.notify_event(&display, &event);
self.notify_display_event(&display, &event);
}
},
&webvr::VRDisplayEvent::Activate(ref display, _) |
&webvr::VRDisplayEvent::Deactivate(ref display, _) |
&webvr::VRDisplayEvent::Blur(ref display) |
&webvr::VRDisplayEvent::Focus(ref display) |
&webvr::VRDisplayEvent::PresentChange(ref display, _) |
&webvr::VRDisplayEvent::Change(ref display) => {
WebVRDisplayEvent::Activate(ref display, _) |
WebVRDisplayEvent::Deactivate(ref display, _) |
WebVRDisplayEvent::Blur(ref display) |
WebVRDisplayEvent::Focus(ref display) |
WebVRDisplayEvent::PresentChange(ref display, _) |
WebVRDisplayEvent::Change(ref display) => {
let display = self.sync_display(&display);
display.handle_webvr_event(&event);
}
};
}
fn notify_event(&self, display: &VRDisplay, event: &webvr::VRDisplayEvent) {
fn handle_gamepad_event(&self, event: WebVRGamepadEvent) {
match event {
WebVRGamepadEvent::Connect(data, state) => {
if let Some(gamepad) = self.find_gamepad(state.gamepad_id) {
gamepad.update_from_vr(&state);
} else {
// new gamepad
self.sync_gamepad(Some(data), &state);
}
},
WebVRGamepadEvent::Disconnect(id) => {
if let Some(gamepad) = self.find_gamepad(id) {
gamepad.update_connected(false);
}
}
};
}
pub fn handle_webvr_event(&self, event: WebVREvent) {
match event {
WebVREvent::Display(event) => {
self.handle_display_event(event);
},
WebVREvent::Gamepad(event) => {
self.handle_gamepad_event(event);
}
};
}
pub fn handle_webvr_events(&self, events: Vec<WebVREvent>) {
for event in events {
self.handle_webvr_event(event);
}
}
fn notify_display_event(&self, display: &VRDisplay, event: &WebVRDisplayEvent) {
let event = VRDisplayEvent::new_from_webvr(&self.global(), &display, &event);
event.upcast::<Event>().fire(self.upcast());
}
}
// Gamepad
impl VR {
fn find_gamepad(&self, gamepad_id: u32) -> Option<Root<Gamepad>> {
self.gamepads.borrow()
.iter()
.find(|g| g.gamepad_id() == gamepad_id)
.map(|g| Root::from_ref(&**g))
}
fn sync_gamepad(&self, data: Option<WebVRGamepadData>, state: &WebVRGamepadState) {
if let Some(existing) = self.find_gamepad(state.gamepad_id) {
existing.update_from_vr(&state);
} else {
let index = self.gamepads.borrow().len();
let data = data.unwrap_or_default();
let root = Gamepad::new_from_vr(&self.global(),
index as i32,
&data,
&state);
self.gamepads.borrow_mut().push(JS::from_ref(&*root));
if state.connected {
root.notify_event(GamepadEventType::Connected);
}
}
}
// Gamepads are synced immediately in response to the API call.
// The current approach allows the to sample gamepad state multiple times per frame. This
// guarantees that the gamepads always have a valid state and can be very useful for
// motion capture or drawing applications.
pub fn get_gamepads(&self) -> Vec<Root<Gamepad>> {
if let Some(wevbr_sender) = self.webvr_thread() {
let (sender, receiver) = ipc::channel().unwrap();
let synced_ids = self.gamepads.borrow().iter().map(|g| g.gamepad_id()).collect();
wevbr_sender.send(WebVRMsg::GetGamepads(synced_ids, sender)).unwrap();
match receiver.recv().unwrap() {
Ok(gamepads) => {
// Sync displays
for gamepad in gamepads {
self.sync_gamepad(gamepad.0, &gamepad.1);
}
},
Err(_) => {}
}
}
// We can add other not VR related gamepad providers here
self.gamepads.borrow().iter()
.map(|g| Root::from_ref(&**g))
.collect()
}
}

View file

@ -161,7 +161,7 @@ impl VRDisplayMethods for VRDisplay {
// https://w3c.github.io/webvr/#dom-vrdisplay-displayid
fn DisplayId(&self) -> u32 {
self.display.borrow().display_id as u32
self.display.borrow().display_id
}
// https://w3c.github.io/webvr/#dom-vrdisplay-displayname
@ -188,7 +188,7 @@ impl VRDisplayMethods for VRDisplay {
// If not presenting we fetch inmediante VRFrameData
let (sender, receiver) = ipc::channel().unwrap();
self.webvr_thread().send(WebVRMsg::GetFrameData(self.global().pipeline_id(),
self.get_display_id(),
self.DisplayId(),
self.depth_near.get(),
self.depth_far.get(),
sender)).unwrap();
@ -213,7 +213,7 @@ impl VRDisplayMethods for VRDisplay {
fn ResetPose(&self) {
let (sender, receiver) = ipc::channel().unwrap();
self.webvr_thread().send(WebVRMsg::ResetPose(self.global().pipeline_id(),
self.get_display_id(),
self.DisplayId(),
sender)).unwrap();
if let Ok(data) = receiver.recv().unwrap() {
// Some VRDisplay data might change after calling ResetPose()
@ -378,7 +378,7 @@ impl VRDisplayMethods for VRDisplay {
}
let api_sender = self.layer_ctx.get().unwrap().ipc_renderer();
let display_id = self.display.borrow().display_id;
let display_id = self.display.borrow().display_id as u64;
let layer = self.layer.borrow();
let msg = VRCompositorCommand::SubmitFrame(display_id, layer.left_bounds, layer.right_bounds);
api_sender.send(CanvasMsg::WebVR(msg)).unwrap();
@ -390,10 +390,6 @@ impl VRDisplay {
self.global().as_window().webvr_thread().expect("Shouldn't arrive here with WebVR disabled")
}
pub fn get_display_id(&self) -> u64 {
self.display.borrow().display_id
}
pub fn update_display(&self, display: &WebVRDisplayData) {
*self.display.borrow_mut() = display.clone();
if let Some(ref stage) = display.stage_parameters {
@ -447,7 +443,7 @@ impl VRDisplay {
let (sync_sender, sync_receiver) = ipc::channel().unwrap();
*self.frame_data_receiver.borrow_mut() = Some(sync_receiver);
let display_id = self.display.borrow().display_id;
let display_id = self.display.borrow().display_id as u64;
let api_sender = self.layer_ctx.get().unwrap().ipc_renderer();
let js_sender = self.global().script_chan();
let address = Trusted::new(&*self);
@ -497,7 +493,7 @@ impl VRDisplay {
*self.frame_data_receiver.borrow_mut() = None;
let api_sender = self.layer_ctx.get().unwrap().ipc_renderer();
let display_id = self.display.borrow().display_id;
let display_id = self.display.borrow().display_id as u64;
let msg = VRCompositorCommand::Release(display_id);
api_sender.send(CanvasMsg::WebVR(msg)).unwrap();
}

View file

@ -32,7 +32,9 @@ unsafe fn update_or_create_typed_array(cx: *mut JSContext,
match src {
Some(data) => {
if dst.get().is_null() {
let _ = Float32Array::create(cx, CreateWith::Slice(data), dst.handle_mut());
rooted!(in (cx) let mut array = ptr::null_mut());
let _ = Float32Array::create(cx, CreateWith::Slice(data), array.handle_mut());
(*dst).set(array.get());
} else {
typedarray!(in(cx) let array: Float32Array = dst.get());
if let Ok(mut array) = array {

View file

@ -0,0 +1,26 @@
/* 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/. */
// https://w3c.github.io/gamepad/#gamepad-interface
[Pref="dom.gamepad.enabled"]
interface Gamepad {
readonly attribute DOMString id;
readonly attribute long index;
readonly attribute boolean connected;
readonly attribute DOMHighResTimeStamp timestamp;
readonly attribute DOMString mapping;
readonly attribute Float64Array axes;
[SameObject] readonly attribute GamepadButtonList buttons;
};
// https://w3c.github.io/gamepad/extensions.html#dom-gamepad
partial interface Gamepad {
readonly attribute DOMString hand;
readonly attribute VRPose? pose;
};
// https://w3c.github.io/webvr/spec/1.1/#interface-gamepad
partial interface Gamepad {
readonly attribute unsigned long displayId;
};

View file

@ -0,0 +1,11 @@
/* 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/. */
// https://w3c.github.io/gamepad/#gamepadbutton-interface
[Pref="dom.gamepad.enabled"]
interface GamepadButton {
readonly attribute boolean pressed;
readonly attribute boolean touched;
readonly attribute double value;
};

View file

@ -0,0 +1,10 @@
/* 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/. */
// https://w3c.github.io/gamepad/#dom-gamepad-buttons
[Pref="dom.gamepad.enabled"]
interface GamepadButtonList {
getter GamepadButton? item(unsigned long index);
readonly attribute unsigned long length;
};

View file

@ -0,0 +1,13 @@
/* 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/. */
// https://w3c.github.io/gamepad/#gamepadevent-interface
[Pref="dom.gamepad.enabled", Constructor(DOMString type, GamepadEventInit eventInitDict)]
interface GamepadEvent : Event {
readonly attribute Gamepad gamepad;
};
dictionary GamepadEventInit : EventInit {
required Gamepad gamepad;
};

View file

@ -0,0 +1,10 @@
/* 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/. */
// https://w3c.github.io/gamepad/#navigator-interface-extension
[Pref="dom.gamepad.enabled"]
interface GamepadList {
getter Gamepad? item(unsigned long index);
readonly attribute unsigned long length;
};

View file

@ -68,3 +68,8 @@ partial interface Navigator {
partial interface Navigator {
[Pref="dom.permissions.enabled"] readonly attribute Permissions permissions;
};
// https://w3c.github.io/gamepad/#navigator-interface-extension
partial interface Navigator {
[Pref="dom.gamepad.enabled"] GamepadList getGamepads();
};

View file

@ -27,6 +27,7 @@ use dom::bindings::cell::DOMRefCell;
use dom::bindings::codegen::Bindings::CSSStyleDeclarationBinding::CSSStyleDeclarationMethods;
use dom::bindings::codegen::Bindings::DocumentBinding::{DocumentMethods, DocumentReadyState};
use dom::bindings::codegen::Bindings::EventBinding::EventInit;
use dom::bindings::codegen::Bindings::NavigatorBinding::NavigatorMethods;
use dom::bindings::codegen::Bindings::TransitionEventBinding::TransitionEventInit;
use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
use dom::bindings::conversions::{ConversionResult, FromJSValConvertible, StringificationBehavior};
@ -91,7 +92,6 @@ use script_traits::{ScriptThreadFactory, TimerEvent, TimerSchedulerMsg, TimerSou
use script_traits::{TouchEventType, TouchId, UntrustedNodeAddress, WindowSizeData, WindowSizeType};
use script_traits::CompositorEvent::{KeyEvent, MouseButtonEvent, MouseMoveEvent, ResizeEvent};
use script_traits::CompositorEvent::{TouchEvent, TouchpadPressureEvent};
use script_traits::WebVREventMsg;
use script_traits::webdriver_msg::WebDriverScriptCommand;
use serviceworkerjob::{Job, JobQueue, AsyncJobHandler};
use servo_config::opts;
@ -119,7 +119,7 @@ use task_source::user_interaction::{UserInteractionTask, UserInteractionTaskSour
use time::Tm;
use url::Position;
use webdriver_handlers;
use webvr_traits::WebVRMsg;
use webvr_traits::{WebVREvent, WebVRMsg};
pub type ImageCacheMsg = (PipelineId, PendingImageResponse);
@ -1070,8 +1070,8 @@ impl ScriptThread {
self.handle_reload(pipeline_id),
ConstellationControlMsg::ExitPipeline(pipeline_id, discard_browsing_context) =>
self.handle_exit_pipeline_msg(pipeline_id, discard_browsing_context),
ConstellationControlMsg::WebVREvent(pipeline_id, event) =>
self.handle_webvr_event(pipeline_id, event),
ConstellationControlMsg::WebVREvents(pipeline_id, events) =>
self.handle_webvr_events(pipeline_id, events),
msg @ ConstellationControlMsg::AttachLayout(..) |
msg @ ConstellationControlMsg::Viewport(..) |
msg @ ConstellationControlMsg::SetScrollState(..) |
@ -2186,11 +2186,11 @@ impl ScriptThread {
}
}
fn handle_webvr_event(&self, pipeline_id: PipelineId, event: WebVREventMsg) {
fn handle_webvr_events(&self, pipeline_id: PipelineId, events: Vec<WebVREvent>) {
let window = self.documents.borrow().find_window(pipeline_id);
if let Some(window) = window {
let navigator = window.Navigator();
navigator.handle_webvr_event(event);
let vr = window.Navigator().Vr();
vr.handle_webvr_events(events);
}
}

View file

@ -71,7 +71,7 @@ use std::sync::Arc;
use std::sync::mpsc::{Receiver, Sender};
use style_traits::{CSSPixel, UnsafeNode};
use webdriver_msg::{LoadStatus, WebDriverScriptCommand};
use webvr_traits::{WebVRDisplayEvent, WebVRMsg};
use webvr_traits::{WebVREvent, WebVRMsg};
pub use script_msg::{LayoutMsg, ScriptMsg, EventResult, LogEntry};
pub use script_msg::{ServiceWorkerMsg, ScopeThings, SWManagerMsg, SWManagerSenders, DOMMessage};
@ -280,8 +280,8 @@ pub enum ConstellationControlMsg {
ReportCSSError(PipelineId, String, usize, usize, String),
/// Reload the given page.
Reload(PipelineId),
/// Notifies the script thread of a WebVR device event
WebVREvent(PipelineId, WebVREventMsg)
/// Notifies the script thread of WebVR events.
WebVREvents(PipelineId, Vec<WebVREvent>)
}
impl fmt::Debug for ConstellationControlMsg {
@ -314,7 +314,7 @@ impl fmt::Debug for ConstellationControlMsg {
FramedContentChanged(..) => "FramedContentChanged",
ReportCSSError(..) => "ReportCSSError",
Reload(..) => "Reload",
WebVREvent(..) => "WebVREvent",
WebVREvents(..) => "WebVREvents",
};
write!(formatter, "ConstellationMsg::{}", variant)
}
@ -751,16 +751,8 @@ pub enum ConstellationMsg {
LogEntry(Option<FrameId>, Option<String>, LogEntry),
/// Set the WebVR thread channel.
SetWebVRThread(IpcSender<WebVRMsg>),
/// Dispatch a WebVR event to the subscribed script threads.
WebVREvent(Vec<PipelineId>, WebVREventMsg),
}
/// Messages to the constellation originating from the WebVR thread.
/// Used to dispatch VR Headset state events: connected, unconnected, and more.
#[derive(Deserialize, Serialize, Clone)]
pub enum WebVREventMsg {
/// Inform the constellation of a VR display event.
DisplayEvent(WebVRDisplayEvent)
/// Dispatch WebVR events to the subscribed script threads.
WebVREvents(Vec<PipelineId>, Vec<WebVREvent>),
}
/// Resources required by workerglobalscopes

View file

@ -5,7 +5,7 @@
use ipc_channel::ipc;
use ipc_channel::ipc::{IpcReceiver, IpcSender};
use msg::constellation_msg::PipelineId;
use script_traits::{ConstellationMsg, WebVREventMsg};
use script_traits::ConstellationMsg;
use servo_config::prefs::PREFS;
use std::{thread, time};
use std::collections::{HashMap, HashSet};
@ -42,7 +42,7 @@ pub struct WebVRThread {
constellation_chan: Sender<ConstellationMsg>,
vr_compositor_chan: WebVRCompositorSender,
polling_events: bool,
presenting: HashMap<u64, PipelineId>
presenting: HashMap<u32, PipelineId>
}
impl WebVRThread {
@ -108,6 +108,9 @@ impl WebVRThread {
WebVRMsg::CreateCompositor(display_id) => {
self.handle_create_compositor(display_id);
},
WebVRMsg::GetGamepads(synced_ids, sender) => {
self.handle_get_gamepads(synced_ids, sender);
}
WebVRMsg::Exit => {
break
},
@ -134,7 +137,7 @@ impl WebVRThread {
fn handle_framedata(&mut self,
pipeline: PipelineId,
display_id: u64,
display_id: u32,
near: f64,
far: f64,
sender: IpcSender<WebVRResult<VRFrameData>>) {
@ -148,7 +151,7 @@ impl WebVRThread {
fn handle_reset_pose(&mut self,
pipeline: PipelineId,
display_id: u64,
display_id: u32,
sender: IpcSender<WebVRResult<VRDisplayData>>) {
match self.access_check(pipeline, display_id) {
Ok(display) => {
@ -166,7 +169,7 @@ impl WebVRThread {
// while the user is having a VR experience in the current tab.
// These security rules also avoid multithreading race conditions between WebVRThread and
// Webrender thread. See WebVRCompositorHandler implementation notes for more details about this.
fn access_check(&self, pipeline: PipelineId, display_id: u64) -> Result<&VRDisplayPtr, &'static str> {
fn access_check(&self, pipeline: PipelineId, display_id: u32) -> Result<&VRDisplayPtr, &'static str> {
if *self.presenting.get(&display_id).unwrap_or(&pipeline) != pipeline {
return Err("No access granted to this Display because it's presenting on other JavaScript Tab");
}
@ -175,14 +178,14 @@ impl WebVRThread {
fn handle_request_present(&mut self,
pipeline: PipelineId,
display_id: u64,
display_id: u32,
sender: IpcSender<WebVRResult<()>>) {
match self.access_check(pipeline, display_id).map(|d| d.clone()) {
Ok(display) => {
self.presenting.insert(display_id, pipeline);
let data = display.borrow().data();
sender.send(Ok(())).unwrap();
self.notify_event(VRDisplayEvent::PresentChange(data, true));
self.notify_event(VRDisplayEvent::PresentChange(data, true).into());
},
Err(msg) => {
sender.send(Err(msg.into())).unwrap();
@ -192,7 +195,7 @@ impl WebVRThread {
fn handle_exit_present(&mut self,
pipeline: PipelineId,
display_id: u64,
display_id: u32,
sender: Option<IpcSender<WebVRResult<()>>>) {
match self.access_check(pipeline, display_id).map(|d| d.clone()) {
Ok(display) => {
@ -201,7 +204,7 @@ impl WebVRThread {
sender.send(Ok(())).unwrap();
}
let data = display.borrow().data();
self.notify_event(VRDisplayEvent::PresentChange(data, false));
self.notify_event(VRDisplayEvent::PresentChange(data, false).into());
},
Err(msg) => {
if let Some(sender) = sender {
@ -211,11 +214,28 @@ impl WebVRThread {
}
}
fn handle_create_compositor(&mut self, display_id: u64) {
fn handle_create_compositor(&mut self, display_id: u32) {
let compositor = self.service.get_display(display_id).map(|d| WebVRCompositor(d.as_ptr()));
self.vr_compositor_chan.send(compositor).unwrap();
}
fn handle_get_gamepads(&mut self,
synced_ids: Vec<u32>,
sender: IpcSender<WebVRResult<Vec<(Option<VRGamepadData>, VRGamepadState)>>>) {
let gamepads = self.service.get_gamepads();
let data = gamepads.iter().map(|g| {
let g = g.borrow();
// Optimization, don't fetch and send gamepad static data when the gamepad is already synced.
let data = if synced_ids.iter().any(|v| *v == g.id()) {
None
} else {
Some(g.data())
};
(data, g.state())
}).collect();
sender.send(Ok(data)).unwrap();
}
fn poll_events(&mut self, sender: IpcSender<bool>) {
loop {
let events = self.service.poll_events();
@ -230,16 +250,13 @@ impl WebVRThread {
sender.send(self.polling_events).unwrap();
}
fn notify_events(&self, events: Vec<VRDisplayEvent>) {
fn notify_events(&self, events: Vec<VREvent>) {
let pipeline_ids: Vec<PipelineId> = self.contexts.iter().map(|c| *c).collect();
for event in events {
let event = WebVREventMsg::DisplayEvent(event);
self.constellation_chan.send(ConstellationMsg::WebVREvent(pipeline_ids.clone(), event)).unwrap();
}
self.constellation_chan.send(ConstellationMsg::WebVREvents(pipeline_ids.clone(), events)).unwrap();
}
#[inline]
fn notify_event(&self, event: VRDisplayEvent) {
fn notify_event(&self, event: VREvent) {
self.notify_events(vec![event]);
}
@ -334,7 +351,8 @@ impl webrender_traits::VRCompositorHandler for WebVRCompositorHandler {
let layer = VRLayer {
texture_id: texture_id,
left_bounds: left_bounds,
right_bounds: right_bounds
right_bounds: right_bounds,
texture_size: None
};
unsafe {
(*compositor.0).submit_frame(&layer);
@ -357,7 +375,7 @@ impl WebVRCompositorHandler {
None => return,
};
sender.send(WebVRMsg::CreateCompositor(display_id)).unwrap();
sender.send(WebVRMsg::CreateCompositor(display_id as u32)).unwrap();
let display = self.webvr_thread_receiver.recv().unwrap();
match display {

View file

@ -12,6 +12,6 @@ path = "lib.rs"
[dependencies]
ipc-channel = "0.7"
msg = {path = "../msg"}
rust-webvr = {version = "0.2", features = ["serde-serialization"]}
rust-webvr = {version = "0.3", features = ["serde-serialization"]}
serde = "0.9"
serde_derive = "0.9"

View file

@ -16,9 +16,15 @@ pub use webvr::VRDisplayData as WebVRDisplayData;
pub use webvr::VRDisplayCapabilities as WebVRDisplayCapabilities;
pub use webvr::VRDisplayEvent as WebVRDisplayEvent;
pub use webvr::VRDisplayEventReason as WebVRDisplayEventReason;
pub use webvr::VREvent as WebVREvent;
pub use webvr::VREye as WebVREye;
pub use webvr::VREyeParameters as WebVREyeParameters;
pub use webvr::VRFieldOfView as WebVRFieldOfView;
pub use webvr::VRGamepadButton as WebVRGamepadButton;
pub use webvr::VRGamepadData as WebVRGamepadData;
pub use webvr::VRGamepadEvent as WebVRGamepadEvent;
pub use webvr::VRGamepadHand as WebVRGamepadHand;
pub use webvr::VRGamepadState as WebVRGamepadState;
pub use webvr::VRFrameData as WebVRFrameData;
pub use webvr::VRLayer as WebVRLayer;
pub use webvr::VRPose as WebVRPose;

View file

@ -15,10 +15,11 @@ pub enum WebVRMsg {
UnregisterContext(PipelineId),
PollEvents(IpcSender<bool>),
GetDisplays(IpcSender<WebVRResult<Vec<VRDisplayData>>>),
GetFrameData(PipelineId, u64, f64, f64, IpcSender<WebVRResult<VRFrameData>>),
ResetPose(PipelineId, u64, IpcSender<WebVRResult<VRDisplayData>>),
RequestPresent(PipelineId, u64, IpcSender<WebVRResult<()>>),
ExitPresent(PipelineId, u64, Option<IpcSender<WebVRResult<()>>>),
CreateCompositor(u64),
GetFrameData(PipelineId, u32, f64, f64, IpcSender<WebVRResult<VRFrameData>>),
ResetPose(PipelineId, u32, IpcSender<WebVRResult<VRDisplayData>>),
RequestPresent(PipelineId, u32, IpcSender<WebVRResult<()>>),
ExitPresent(PipelineId, u32, Option<IpcSender<WebVRResult<()>>>),
CreateCompositor(u32),
GetGamepads(Vec<u32>, IpcSender<WebVRResult<Vec<(Option<VRGamepadData>, VRGamepadState)>>>),
Exit,
}

View file

@ -2,6 +2,7 @@
"dom.bluetooth.enabled": false,
"dom.bluetooth.testing.enabled": false,
"dom.forcetouch.enabled": false,
"dom.gamepad.enabled": false,
"dom.mouseevent.which.enabled": false,
"dom.mozbrowser.enabled": false,
"dom.mutation_observer.enabled": false,

View file

@ -0,0 +1,420 @@
<!doctype html>
<!--
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.
-->
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<!--Chrome origin trial header-->
<meta http-equiv="origin-trial" data-feature="WebVR" data-expires="2017-04-13" content="AtpEVUJTjLpU/IMdKA/u8TRWqVUKfA6aJQsonwi01IPxqA16zX7L4BMa9E4g4DdJW80v3N6jqde4pXeqd2GYCg4AAABJeyJvcmlnaW4iOiJodHRwczovL3dlYnZyLmluZm86NDQzIiwiZmVhdHVyZSI6IldlYlZSIiwiZXhwaXJ5IjoxNDkyMTAzODUyfQ==">
<title>XX - VR Controllers</title>
<!--
This sample demonstrates how to handle gamepads with 6DoF support, such as
the Vive controllers or Oculus touch. PLEASE NOTE: The additions to the
gamepad API used here are not yet part of the standard, and subject to
change at any time!
-->
<style>
#webgl-canvas {
box-sizing: border-box;
height: 100%;
left: 0;
margin: 0;
position: absolute;
top: 0;
width: 100%;
}
</style>
<!-- This entire block in only to facilitate dynamically enabling and
disabling the WebVR polyfill, and is not necessary for most WebVR apps.
If you want to use the polyfill in your app, just include the js file and
everything will work the way you want it to by default. -->
<script>
var WebVRConfig = {
// Prevents the polyfill from initializing automatically.
DEFER_INITIALIZATION: true,
// Ensures the polyfill is always active when initialized, even if the
// native API is available. This is probably NOT what most pages want.
POLYFILL_MODE: "ALWAYS",
// Polyfill optimizations
DIRTY_SUBMIT_FRAME_BINDINGS: true,
BUFFER_SCALE: 0.75,
};
</script>
<script src="js/third-party/webvr-polyfill.js"></script>
<script src="js/third-party/wglu/wglu-url.js"></script>
<script>
// Dynamically turn the polyfill on if requested by the query args.
if (WGLUUrl.getBool('polyfill', false)) {
InitializeWebVRPolyfill();
} else {
// Shim for migration from older version of WebVR. Shouldn't be necessary for very long.
InitializeSpecShim();
}
</script>
<!-- End sample polyfill enabling logic -->
<script src="js/third-party/gl-matrix-min.js"></script>
<script src="js/third-party/wglu/wglu-debug-geometry.js"></script>
<script src="js/third-party/wglu/wglu-program.js"></script>
<script src="js/third-party/wglu/wglu-stats.js"></script>
<script src="js/third-party/wglu/wglu-texture.js"></script>
<script src="js/vr-cube-island.js"></script>
<script src="js/vr-samples-util.js"></script>
</head>
<body>
<canvas id="webgl-canvas"></canvas>
<script>
/* global mat4, vec3, VRCubeIsland, WGLUDebugGeometry, WGLUStats, WGLUTextureLoader, VRSamplesUtil */
(function () {
"use strict";
var PLAYER_HEIGHT = 1.65;
var vrDisplay = null;
var frameData = null;
var projectionMat = mat4.create();
var viewMat = mat4.create();
var poseMat = mat4.create();
var gamepadMat = mat4.create();
var gamepadMat2 = mat4.create();
var gamepadColor = vec4.create();
var standingPosition = vec3.create();
var vrPresentButton = null;
var orientation = [0, 0, 0, 1];
var position = [0, 0, 0];
window.addEventListener("gamepadconnected", function(ev) {
var gamepad = ev.gamepad;
console.log("Gamepad connected", gamepad.index, gamepad.id, gamepad.displayId);
});
window.addEventListener("gamepaddisconnected", function(ev) {
var gamepad = ev.gamepad;
console.log("Gamepad disconnected", gamepad.index, gamepad.id, gamepad.displayId);
});
// ===================================================
// WebGL scene setup. This code is not WebVR specific.
// ===================================================
// WebGL setup.
var webglCanvas = document.getElementById("webgl-canvas");
var gl = null;
var cubeIsland = null;
var stats = null;
var debugGeom = null;
function initWebGL (preserveDrawingBuffer) {
var glAttribs = {
alpha: false,
antialias: false,
preserveDrawingBuffer: false
};
gl = webglCanvas.getContext("webgl", glAttribs);
if (!gl) {
gl = webglCanvas.getContext("experimental-webgl", glAttribs);
if (!gl) {
VRSamplesUtil.addError("Your browser does not support WebGL.");
return;
}
}
//gl.clearColor(0.1, 0.2, 0.3, 1.0);
gl.clearColor(0.0, 119.0/255.0, 51.0/255.0, 1.0);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
var textureLoader = new WGLUTextureLoader(gl);
var texture = textureLoader.loadTexture("media/textures/cube-sea.png");
cubeIsland = new VRCubeIsland(gl, texture, 2, 2);
stats = new WGLUStats(gl);
debugGeom = new WGLUDebugGeometry(gl);
// Wait until we have a WebGL context to resize and start rendering.
window.addEventListener("resize", onResize, false);
onResize();
window.requestAnimationFrame(onAnimationFrame);
}
// ================================
// WebVR-specific code begins here.
// ================================
function onVRRequestPresent () {
vrDisplay.requestPresent([{ source: webglCanvas }]).then(function () {
}, function (err) {
var errMsg = "requestPresent failed.";
if (err && err.message) {
errMsg += "<br/>" + err.message
}
VRSamplesUtil.addError(errMsg, 2000);
});
}
function onVRExitPresent () {
if (!vrDisplay.isPresenting)
return;
vrDisplay.exitPresent().then(function () {
}, function () {
VRSamplesUtil.addError("exitPresent failed.", 2000);
});
}
function onVRPresentChange () {
onResize();
if (vrDisplay.isPresenting) {
if (vrDisplay.capabilities.hasExternalDisplay) {
VRSamplesUtil.removeButton(vrPresentButton);
vrPresentButton = VRSamplesUtil.addButton("Exit VR", "E", "media/icons/cardboard64.png", onVRExitPresent);
}
} else {
if (vrDisplay.capabilities.hasExternalDisplay) {
VRSamplesUtil.removeButton(vrPresentButton);
vrPresentButton = VRSamplesUtil.addButton("Enter VR", "E", "media/icons/cardboard64.png", onVRRequestPresent);
}
}
}
if (navigator.vr || navigator.getVRDisplays) {
frameData = new VRFrameData();
navigator.vr.getDisplays().then(function (displays) {
// navigator.getVRDisplays().then(function (displays) {
if (displays.length > 0) {
vrDisplay = displays[displays.length - 1];
vrDisplay.depthNear = 0.1;
vrDisplay.depthFar = 1024.0;
initWebGL(true);
if (vrDisplay.stageParameters &&
vrDisplay.stageParameters.sizeX > 0 &&
vrDisplay.stageParameters.sizeZ > 0) {
cubeIsland.resize(vrDisplay.stageParameters.sizeX, vrDisplay.stageParameters.sizeZ);
}
VRSamplesUtil.addButton("Reset Pose", "R", null, function () { vrDisplay.resetPose(); });
if (vrDisplay.capabilities.canPresent)
vrPresentButton = VRSamplesUtil.addButton("Enter VR", "E", "media/icons/cardboard64.png", onVRRequestPresent);
vrDisplay.addEventListener('presentchange', onVRPresentChange, false);
//vrDisplay.addEventListener('vrdisplayactivate', onVRRequestPresent, false);
//vrDisplay.addEventListener('vrdisplaydeactivate', onVRExitPresent, false);
console.log(navigator.getGamepads());
setTimeout(function(){
onVRRequestPresent();
}, 5);
} else {
initWebGL(false);
VRSamplesUtil.addInfo("WebVR supported, but no VRDisplays found.", 3000);
}
});
} else if (navigator.getVRDevices) {
initWebGL(false);
VRSamplesUtil.addError("Your browser supports WebVR but not the latest version. See <a href='http://webvr.info'>webvr.info</a> for more info.");
} else {
initWebGL(false);
VRSamplesUtil.addError("Your browser does not support WebVR. See <a href='http://webvr.info'>webvr.info</a> for assistance.");
}
function onResize () {
if (vrDisplay && vrDisplay.isPresenting) {
var leftEye = vrDisplay.getEyeParameters("left");
var rightEye = vrDisplay.getEyeParameters("right");
webglCanvas.width = Math.max(leftEye.renderWidth, rightEye.renderWidth) * 2;
webglCanvas.height = Math.max(leftEye.renderHeight, rightEye.renderHeight);
} else {
webglCanvas.width = window.innerWidth * 2.0 * window.devicePixelRatio;
webglCanvas.height = window.innerHeight * 2.0 * window.devicePixelRatio;
}
}
function onClick() {
//onVRRequestPresent();
}
webglCanvas.addEventListener("click", onClick, false);
function getStandingViewMatrix (out, view) {
if (vrDisplay.stageParameters) {
mat4.invert(out, vrDisplay.stageParameters.sittingToStandingTransform);
mat4.multiply(out, view, out);
} else {
mat4.identity(out);
mat4.translate(out, out, [0, PLAYER_HEIGHT, 0]);
mat4.invert(out, out);
mat4.multiply(out, view, out);
}
}
function getPoseMatrix (out, pose, isGamepad) {
orientation = pose.orientation;
position = pose.position;
if (!orientation) { orientation = [0, 0, 0, 1]; }
if (!position) {
// If this is a gamepad without a pose set it out in front of us so
// we can see it.
position = isGamepad ? [0.1, -0.1, -0.5] : [0, 0, 0];
}
if (vrDisplay.stageParameters) {
mat4.fromRotationTranslation(out, orientation, position);
mat4.multiply(out, vrDisplay.stageParameters.sittingToStandingTransform, out);
} else {
vec3.add(standingPosition, position, [0, PLAYER_HEIGHT, 0]);
mat4.fromRotationTranslation(out, orientation, standingPosition);
}
}
function renderSceneView (projection, view, gamepads) {
cubeIsland.render(projection, view, stats);
debugGeom.bind(projection, view);
// Render every gamepad with a pose we found
for (var i = 0; i < gamepads.length; ++i) {
var gamepad = gamepads[i];
// Because this sample is done in standing space we need to apply
// the same transformation to the gamepad pose as we did the
// VRDisplay's pose.
getPoseMatrix(gamepadMat, gamepad.pose, true);
// Loop through all the gamepad's axes and scale the gamepad geometry
// by their value.
var scale = [0.1, 0.1, 0.1];
for (var j = 0; j < gamepad.axes.length; ++j) {
switch (j%3) {
case 0:
scale[0] *= 1.0 + gamepad.axes[j];
break;
case 1:
scale[1] *= 1.0 + gamepad.axes[j];
break;
case 2:
scale[2] *= 1.0 + gamepad.axes[j];
break;
}
}
// Scaled down to from 1 meter to be something closer to the size of
// a hand.
mat4.scale(gamepadMat, gamepadMat, scale);
// Rotate -90 deg so the point of the cone faces "forward"
mat4.rotateX(gamepadMat, gamepadMat, -Math.PI * 0.5);
// Show the gamepad's cube as red if any buttons are pressed, blue
// otherwise.
vec4.set(gamepadColor, 0, 0, 1, 1);
var buttons = gamepad.buttons;
for (var j = 0; j < buttons.length; ++j) {
if (buttons[j].pressed) {
vec4.set(gamepadColor, buttons[j].value || 1.0, 0, 0, 1);
break;
}
}
debugGeom.drawConeWithMatrix(gamepadMat, gamepadColor);
// Draw a "handle" for the gamepad
mat4.identity(gamepadMat2);
mat4.translate(gamepadMat2, gamepadMat2, [0, -0.5, -0.3]);
mat4.rotateX(gamepadMat2, gamepadMat2, -Math.PI * 0.2);
mat4.scale(gamepadMat2, gamepadMat2, [0.25, 0.25, 0.5]);
mat4.multiply(gamepadMat, gamepadMat, gamepadMat2);
debugGeom.drawBoxWithMatrix(gamepadMat, gamepadColor);
}
}
function onAnimationFrame (t) {
stats.begin();
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
if (vrDisplay) {
vrDisplay.requestAnimationFrame(onAnimationFrame);
vrDisplay.getFrameData(frameData);
// Loop over every gamepad and if we find any that have a pose use it.
var vrGamepads = [];
var gamepads = navigator.getGamepads();
for (var i = 0; i < gamepads.length; ++i) {
var gamepad = gamepads[i];
// The array may contain undefined gamepads, so check for that as
// well as a non-null pose.
if (gamepad) {
if (gamepad.pose)
vrGamepads.push(gamepad);
if ("hapticActuators" in gamepad && gamepad.hapticActuators.length > 0) {
for (var j = 0; j < gamepad.buttons.length; ++j) {
if (gamepad.buttons[j].pressed) {
// Vibrate the gamepad using to the value of the button as
// the vibration intensity.
gamepad.hapticActuators[0].pulse(gamepad.buttons[j].value, 100);
break;
}
}
}
}
}
if (vrDisplay.isPresenting) {
gl.viewport(0, 0, webglCanvas.width * 0.5, webglCanvas.height);
getStandingViewMatrix(viewMat, frameData.leftViewMatrix);
renderSceneView(frameData.leftProjectionMatrix, viewMat, vrGamepads);
gl.viewport(webglCanvas.width * 0.5, 0, webglCanvas.width * 0.5, webglCanvas.height);
getStandingViewMatrix(viewMat, frameData.rightViewMatrix);
renderSceneView(frameData.rightProjectionMatrix, viewMat, vrGamepads);
vrDisplay.submitFrame();
} else {
gl.viewport(0, 0, webglCanvas.width, webglCanvas.height);
mat4.perspective(projectionMat, Math.PI*0.4, webglCanvas.width / webglCanvas.height, 0.1, 1024.0);
getStandingViewMatrix(viewMat, frameData.leftViewMatrix);
renderSceneView(projectionMat, viewMat, vrGamepads);
stats.renderOrtho();
}
} else {
window.requestAnimationFrame(onAnimationFrame);
// No VRDisplay found.
gl.viewport(0, 0, webglCanvas.width, webglCanvas.height);
mat4.perspective(projectionMat, Math.PI*0.4, webglCanvas.width / webglCanvas.height, 0.1, 1024.0);
mat4.identity(viewMat);
mat4.translate(viewMat, viewMat, [0, -PLAYER_HEIGHT, 0]);
cubeIsland.render(projectionMat, viewMat, stats);
stats.renderOrtho();
}
stats.end();
}
})();
</script>
</body>
</html>