From e6cc1fabe0d97006024e38de4a498b1f364fa860 Mon Sep 17 00:00:00 2001
From: howard-stearns <howard.stearns@gmail.com>
Date: Thu, 2 Jun 2016 12:38:54 -0700
Subject: [PATCH 1/3] Do not send stale vive data as valid after deactivating.

---
 plugins/openvr/src/OpenVrDisplayPlugin.cpp | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp
index dba8fca208..a74e5458af 100644
--- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp
+++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp
@@ -96,6 +96,11 @@ void OpenVrDisplayPlugin::internalDeactivate() {
     Parent::internalDeactivate();
     _container->setIsOptionChecked(StandingHMDSensorMode, false);
     if (_system) {
+        // Invalidate poses. It's fine if someone else sets these shared values, but we're about to stop updating them, and
+        // we don't want ViveControllerManager to consider old values to be valid.
+        for (int i = 0; i < vr::k_unMaxTrackedDeviceCount; i++) {
+            _trackedDevicePose[i].bPoseIsValid = false;
+        }
         releaseOpenVrSystem();
         _system = nullptr;
     }

From 3e919cb7ac4d78b3661b020e9f577a0978c680eb Mon Sep 17 00:00:00 2001
From: howard-stearns <howard.stearns@gmail.com>
Date: Fri, 3 Jun 2016 10:42:02 -0700
Subject: [PATCH 2/3] cleanup

---
 .../controllers/handControllerPointer.js      | 59 +++++++++----------
 1 file changed, 28 insertions(+), 31 deletions(-)

diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js
index 5cbde82654..e70f704ce7 100644
--- a/scripts/system/controllers/handControllerPointer.js
+++ b/scripts/system/controllers/handControllerPointer.js
@@ -27,6 +27,7 @@
 
 // UTILITIES -------------
 //
+function ignore() { }
 
 // Utility to make it easier to setup and disconnect cleanly.
 function setupHandler(event, handler) {
@@ -106,7 +107,7 @@ function Trigger() {
     };
     that.full = function () {
         return (that.state === 'full') ? 1.0 : 0.0;
-    }
+    };
 }
 
 // VERTICAL FIELD OF VIEW ---------
@@ -229,10 +230,9 @@ function updateSeeking() {
     }
     averageMouseVelocity = lastIntegration = 0;
     var lookAt2D = HMD.getHUDLookAtPosition2D();
-    if (!lookAt2D) {
-        // FIXME - determine if this message is useful but make it so it doesn't spam the
-        // log in the case that it is happening
-        //print('Cannot seek without lookAt position');
+    if (!lookAt2D) { // If this happens, something has gone terribly wrong.
+        print('Cannot seek without lookAt position');
+        isSeeking = false;
         return;
     } // E.g., if parallel to location in HUD
     var copy = Reticle.position;
@@ -305,7 +305,7 @@ var leftTrigger = new Trigger();
 var rightTrigger = new Trigger();
 var activeTrigger = rightTrigger;
 var activeHand = Controller.Standard.RightHand;
-function toggleHand() {
+function toggleHand() { // unequivocally switch which hand controls mouse position
     if (activeHand === Controller.Standard.RightHand) {
         activeHand = Controller.Standard.LeftHand;
         activeTrigger = leftTrigger;
@@ -314,6 +314,13 @@ function toggleHand() {
         activeTrigger = rightTrigger;
     }
 }
+function makeToggleAction(hand) { // return a function(0|1) that makes the specified hand control mouse when 1
+    return function (on) {
+        if (on && (activeHand !== hand)) {
+            toggleHand();
+        }
+    };
+}
 
 var clickMapping = Controller.newMapping(Script.resolvePath('') + '-click');
 Script.scriptEnding.connect(clickMapping.disable);
@@ -321,25 +328,14 @@ Script.scriptEnding.connect(clickMapping.disable);
 // Gather the trigger data for smoothing.
 clickMapping.from(Controller.Standard.RT).peek().to(rightTrigger.triggerPress);
 clickMapping.from(Controller.Standard.LT).peek().to(leftTrigger.triggerPress);
-// The next two lines will be removed soon. Right now I want both trigger and button so we can compare.
-clickMapping.from(Controller.Standard.RightPrimaryThumb).peek().to(Controller.Actions.ReticleClick);
-clickMapping.from(Controller.Standard.LeftPrimaryThumb).peek().to(Controller.Actions.ReticleClick);
-// Full somoothed trigger is a click.
+// Full smoothed trigger is a click.
 clickMapping.from(rightTrigger.full).to(Controller.Actions.ReticleClick);
 clickMapping.from(leftTrigger.full).to(Controller.Actions.ReticleClick);
 clickMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(Controller.Actions.ContextMenu);
 clickMapping.from(Controller.Standard.LeftSecondaryThumb).peek().to(Controller.Actions.ContextMenu);
 // Partial smoothed trigger is activation.
-clickMapping.from(rightTrigger.partial).to(function (on) {
-    if (on && (activeHand !== Controller.Standard.RightHand)) {
-        toggleHand();
-    }
-});
-clickMapping.from(leftTrigger.partial).to(function (on) {
-    if (on && (activeHand !== Controller.Standard.LeftHand)) {
-        toggleHand();
-    }
-});
+clickMapping.from(rightTrigger.partial).to(makeToggleAction(Controller.Standard.RightHand));
+clickMapping.from(leftTrigger.partial).to(makeToggleAction(Controller.Standard.LeftHand));
 clickMapping.enable();
 
 // VISUAL AID -----------
@@ -374,6 +370,7 @@ function turnOffVisualization(optionalEnableClicks) { // because we're showing c
 }
 var MAX_RAY_SCALE = 32000; // Anything large. It's a scale, not a distance.
 function updateVisualization(controllerPosition, controllerDirection, hudPosition3d, hudPosition2d) {
+    ignore(controllerPosition, controllerDirection, hudPosition2d);
     // Show an indication of where the cursor will appear when crossing a HUD element,
     // and where in-world clicking will occur.
     //
@@ -408,21 +405,21 @@ function updateVisualization(controllerPosition, controllerDirection, hudPositio
 function update() {
     var now = Date.now();
     if (!handControllerLockOut.expired(now)) {
-        return turnOffVisualization();
-    } // Let them use mouse it in peace.
+        return turnOffVisualization(); // Let them use mouse it in peace.
+    }
     if (!Menu.isOptionChecked("First Person")) {
-        return turnOffVisualization();
-    }  // What to do? menus can be behind hand!
-    if (!Window.hasFocus()) { // Don't mess with other apps
-        return turnOffVisualization();
+        return turnOffVisualization(); // What to do? menus can be behind hand!
+    }
+    if (!Window.hasFocus()) {
+        return turnOffVisualization(); // Don't mess with other apps
     }
     leftTrigger.update();
     rightTrigger.update();
     var controllerPose = Controller.getPoseValue(activeHand);
     // Valid if any plugged-in hand controller is "on". (uncradled Hydra, green-lighted Vive...)
     if (!controllerPose.valid) {
-        return turnOffVisualization();
-    } // Controller is cradled.
+        return turnOffVisualization(); // Controller is cradled.
+    }
     var controllerPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, controllerPose.translation),
                                       MyAvatar.position);
     // This gets point direction right, but if you want general quaternion it would be more complicated:
@@ -430,9 +427,9 @@ function update() {
 
     var hudPoint3d = calculateRayUICollisionPoint(controllerPosition, controllerDirection);
     if (!hudPoint3d) {
-        // FIXME - determine if this message is useful but make it so it doesn't spam the
-        // log in the case that it is happening
-        //print('Controller is parallel to HUD');
+        if (Menu.isOptionChecked("Overlays")) { // With our hud resetting strategy, hudPoint3d should be valid here
+            print('Controller is parallel to HUD');  // so let us know that our assumptions are wrong.
+        }
         return turnOffVisualization();
     }
     var hudPoint2d = overlayFromWorldPoint(hudPoint3d);

From 68819561175d8495be22975dafd39b784ced661e Mon Sep 17 00:00:00 2001
From: howard-stearns <howard.stearns@gmail.com>
Date: Fri, 3 Jun 2016 14:57:46 -0700
Subject: [PATCH 3/3] Basic HMD toggle button.

---
 scripts/defaultScripts.js                     |   1 +
 .../assets/images/tools/hmd-switch-01.svg     | 102 ++++++++++++++++++
 scripts/system/hmd.js                         |  57 ++++++++++
 3 files changed, 160 insertions(+)
 create mode 100644 scripts/system/assets/images/tools/hmd-switch-01.svg
 create mode 100644 scripts/system/hmd.js

diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js
index 2a050d183e..b10d1c6f28 100644
--- a/scripts/defaultScripts.js
+++ b/scripts/defaultScripts.js
@@ -13,6 +13,7 @@ Script.load("system/progress.js");
 Script.load("system/away.js");
 Script.load("system/users.js");
 Script.load("system/examples.js");
+Script.load("system/hmd.js");
 Script.load("system/edit.js");
 Script.load("system/selectAudioDevice.js");
 Script.load("system/notifications.js");
diff --git a/scripts/system/assets/images/tools/hmd-switch-01.svg b/scripts/system/assets/images/tools/hmd-switch-01.svg
new file mode 100644
index 0000000000..15ecb02b6b
--- /dev/null
+++ b/scripts/system/assets/images/tools/hmd-switch-01.svg
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 50 150" style="enable-background:new 0 0 50 150;" xml:space="preserve">
+<style type="text/css">
+	.st0{opacity:0.49;}
+	.st1{fill:#1E1E1E;}
+	.st2{opacity:0.9;}
+	.st3{fill:#FFFFFF;}
+	.st4{fill:#333333;}
+	.st5{fill:#CBCBCB;}
+	.st6{enable-background:new    ;}
+	.st7{fill:#CBCBCB;stroke:#CBCBCB;stroke-width:0.2978;stroke-linecap:square;stroke-miterlimit:10;}
+	.st8{fill:#CBCBCB;stroke:#CBCBCB;stroke-width:9.574610e-03;stroke-linejoin:round;stroke-miterlimit:10;}
+	.st9{fill:#EAEAEA;}
+	.st10{opacity:0.47;}
+	.st11{opacity:0.49;fill:#EAEAEA;}
+	.st12{opacity:0.47;enable-background:new    ;}
+	.st13{opacity:0.5;}
+</style>
+<g>
+	<g class="st2">
+		<path class="st3" d="M50,46.3c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4v-42c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V46.3z"/>
+	</g>
+</g>
+<g>
+	<g class="st2">
+		<path class="st1" d="M50,96.3c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4v-42c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V96.3z"/>
+	</g>
+</g>
+<path d="M21.6,21.5c-1.4,1.4-2.8,2.8-4.2,4.2c-0.3-0.3-0.7-0.7-1-1c1.4-1.4,2.8-2.8,4.2-4.2l-1.1-1.1c-1.6,1.6-3.2,3.2-4.9,4.8
+	c-0.3,0.3-0.5,0.7-0.6,1.2c-0.2,1.3-0.4,2.6-0.6,4c0.1,0,0.2,0,0.3,0c1.3-0.3,2.5-0.5,3.8-0.8c0.2-0.1,0.5-0.2,0.7-0.4
+	c1.7-1.7,3.4-3.4,5.1-5.1L21.6,21.5z"/>
+<path d="M28.2,10.6c0.5-0.5,1.1-1.1,1.7-1.7c0.2-0.2,0.5-0.2,0.7,0c1,1,2,2,3,3c0.2,0.2,0.2,0.5,0,0.8c-0.6,0.6-1.2,1.2-1.7,1.7
+	C30.7,13.2,29.4,11.9,28.2,10.6z"/>
+<path d="M29.4,19.1c-0.3,0.1-0.4,0-0.6-0.2c-0.4-0.4-0.8-0.8-1.2-1.2c1-1,1.9-1.9,2.9-2.9c0.1-0.1,0.2-0.2,0.2-0.2
+	c-1.3-1.3-2.5-2.5-3.8-3.9c-0.1,0.1-0.1,0.2-0.2,0.3c-1,1-1.9,1.9-2.9,2.9c-1.8-1.8-3.5-3.5-5.3-5.3c-0.1-0.1-0.3-0.3-0.4-0.4
+	c-1-0.7-2.2-0.6-3,0.3c-0.8,0.9-0.7,2.2,0.1,3c2.6,2.6,5.3,5.3,7.9,7.9c0.9,0.9,1.7,1.7,2.6,2.6c0.1,0.1,0.2,0.2,0.1,0.4
+	c-0.1,0.4-0.2,0.8-0.2,1.2c-0.2,2.5,1.4,4.7,3.9,5.4c1,0.3,2,0.2,3.1-0.2c-0.1-0.1-0.2-0.2-0.3-0.2c-0.7-0.7-1.5-1.5-2.2-2.2
+	c-0.5-0.5-0.6-1.4-0.1-1.9c0.4-0.4,0.8-0.8,1.2-1.2c0.6-0.5,1.3-0.5,1.8,0c0.1,0.1,0.2,0.2,0.3,0.3c0.7,0.7,1.5,1.5,2.2,2.2
+	c0,0,0.1,0,0.1,0c0.1-0.4,0.2-0.8,0.2-1.3C36.3,20.9,32.9,18.1,29.4,19.1z M16.7,10.6c-0.4,0-0.7-0.3-0.7-0.7c0-0.4,0.3-0.8,0.7-0.8
+	c0.4,0,0.8,0.4,0.8,0.7C17.4,10.3,17.1,10.6,16.7,10.6z M27.1,13c0.3,0.3,0.6,0.7,1,1c-0.7,0.7-1.4,1.4-2.1,2.1
+	c-0.3-0.3-0.7-0.7-1-1C25.7,14.4,26.4,13.7,27.1,13z"/>
+<g>
+	<path d="M18.2,41.3v1.1h-4.4V36h4.4v1.1H15v1.5h2.7v1H15v1.7H18.2z"/>
+	<path d="M19.4,42.4V36h2.3c0.5,0,1,0.1,1.4,0.3c0.4,0.2,0.7,0.4,1,0.7c0.3,0.3,0.5,0.6,0.6,1c0.1,0.4,0.2,0.8,0.2,1.2
+		c0,0.5-0.1,0.9-0.2,1.3c-0.1,0.4-0.4,0.7-0.6,1c-0.3,0.3-0.6,0.5-1,0.6c-0.4,0.2-0.8,0.2-1.3,0.2H19.4z M23.6,39.2
+		c0-0.3,0-0.6-0.1-0.8c-0.1-0.3-0.2-0.5-0.4-0.7c-0.2-0.2-0.4-0.3-0.6-0.4c-0.2-0.1-0.5-0.2-0.8-0.2h-1.1v4.2h1.1
+		c0.3,0,0.6-0.1,0.8-0.2c0.2-0.1,0.4-0.3,0.6-0.4c0.2-0.2,0.3-0.4,0.4-0.7C23.6,39.8,23.6,39.5,23.6,39.2z"/>
+	<path d="M26.1,42.4V36h1.2v6.4H26.1z"/>
+	<path d="M33.6,37.1h-2v5.3h-1.2v-5.3h-2V36h5.3V37.1z"/>
+</g>
+<path class="st9" d="M21.9,71.5c-1.4,1.4-2.8,2.8-4.2,4.2c-0.3-0.3-0.7-0.7-1-1c1.4-1.4,2.8-2.8,4.2-4.2l-1.1-1.1
+	c-1.6,1.6-3.2,3.2-4.9,4.8c-0.3,0.3-0.5,0.7-0.6,1.2c-0.2,1.3-0.4,2.6-0.6,4c0.1,0,0.2,0,0.3,0c1.3-0.3,2.5-0.5,3.8-0.8
+	c0.2-0.1,0.5-0.2,0.7-0.4c1.7-1.7,3.4-3.4,5.1-5.1L21.9,71.5z"/>
+<path class="st9" d="M28.5,60.6c0.5-0.5,1.1-1.1,1.7-1.7c0.2-0.2,0.5-0.2,0.7,0c1,1,2,2,3,3c0.2,0.2,0.2,0.5,0,0.8
+	c-0.6,0.6-1.2,1.2-1.7,1.7C31,63.2,29.8,61.9,28.5,60.6z"/>
+<path class="st9" d="M29.7,69.1c-0.3,0.1-0.4,0-0.6-0.2c-0.4-0.4-0.8-0.8-1.2-1.2c1-1,1.9-1.9,2.9-2.9c0.1-0.1,0.2-0.2,0.2-0.2
+	c-1.3-1.3-2.5-2.5-3.8-3.9c-0.1,0.1-0.1,0.2-0.2,0.3c-1,1-1.9,1.9-2.9,2.9c-1.8-1.8-3.5-3.5-5.3-5.3c-0.1-0.1-0.3-0.3-0.4-0.4
+	c-1-0.7-2.2-0.6-3,0.3c-0.8,0.9-0.7,2.2,0.1,3c2.6,2.6,5.3,5.3,7.9,7.9c0.9,0.9,1.7,1.7,2.6,2.6c0.1,0.1,0.2,0.2,0.1,0.4
+	c-0.1,0.4-0.2,0.8-0.2,1.2c-0.2,2.5,1.4,4.7,3.9,5.4c1,0.3,2,0.2,3.1-0.2c-0.1-0.1-0.2-0.2-0.3-0.2c-0.7-0.7-1.5-1.5-2.2-2.2
+	c-0.5-0.5-0.6-1.4-0.1-1.9c0.4-0.4,0.8-0.8,1.2-1.2c0.6-0.5,1.3-0.5,1.8,0c0.1,0.1,0.2,0.2,0.3,0.3c0.7,0.7,1.5,1.5,2.2,2.2
+	c0,0,0.1,0,0.1,0c0.1-0.4,0.2-0.8,0.2-1.3C36.6,70.9,33.2,68.1,29.7,69.1z M17,60.6c-0.4,0-0.7-0.3-0.7-0.7c0-0.4,0.3-0.8,0.7-0.8
+	c0.4,0,0.8,0.4,0.8,0.7C17.7,60.3,17.4,60.6,17,60.6z M27.4,63c0.3,0.3,0.6,0.7,1,1c-0.7,0.7-1.4,1.4-2.1,2.1c-0.3-0.3-0.7-0.7-1-1
+	C26,64.4,26.7,63.7,27.4,63z"/>
+<g>
+	<path class="st9" d="M18.5,91.4v1.1H14v-6.4h4.4v1.1h-3.1v1.5H18v1h-2.7v1.7H18.5z"/>
+	<path class="st9" d="M19.7,92.4v-6.4H22c0.5,0,1,0.1,1.4,0.3s0.7,0.4,1,0.7c0.3,0.3,0.5,0.6,0.6,1c0.1,0.4,0.2,0.8,0.2,1.2
+		c0,0.5-0.1,0.9-0.2,1.3c-0.1,0.4-0.4,0.7-0.6,1c-0.3,0.3-0.6,0.5-1,0.6c-0.4,0.2-0.8,0.2-1.3,0.2H19.7z M23.9,89.2
+		c0-0.3,0-0.6-0.1-0.8s-0.2-0.5-0.4-0.7c-0.2-0.2-0.4-0.3-0.6-0.4c-0.2-0.1-0.5-0.2-0.8-0.2H21v4.2H22c0.3,0,0.6-0.1,0.8-0.2
+		c0.2-0.1,0.4-0.3,0.6-0.4s0.3-0.4,0.4-0.7C23.9,89.8,23.9,89.5,23.9,89.2z"/>
+	<path class="st9" d="M26.4,92.4v-6.4h1.2v6.4H26.4z"/>
+	<path class="st9" d="M34,87.1h-2v5.3h-1.2v-5.3h-2v-1.1H34V87.1z"/>
+</g>
+<g class="st0">
+	<g class="st2">
+		<path class="st1" d="M50,146.3c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4v-42c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V146.3z"/>
+	</g>
+</g>
+<path class="st11" d="M21.9,121.5c-1.4,1.4-2.8,2.8-4.2,4.2c-0.3-0.3-0.7-0.7-1-1c1.4-1.4,2.8-2.8,4.2-4.2l-1.1-1.1
+	c-1.6,1.6-3.2,3.2-4.9,4.8c-0.3,0.3-0.5,0.7-0.6,1.2c-0.2,1.3-0.4,2.6-0.6,4c0.1,0,0.2,0,0.3,0c1.3-0.3,2.5-0.5,3.8-0.8
+	c0.2-0.1,0.5-0.2,0.7-0.4c1.7-1.7,3.4-3.4,5.1-5.1L21.9,121.5z"/>
+<path class="st11" d="M28.5,110.6c0.5-0.5,1.1-1.1,1.7-1.7c0.2-0.2,0.5-0.2,0.7,0c1,1,2,2,3,3c0.2,0.2,0.2,0.5,0,0.8
+	c-0.6,0.6-1.2,1.2-1.7,1.7C31,113.2,29.8,111.9,28.5,110.6z"/>
+<path class="st11" d="M29.7,119.1c-0.3,0.1-0.4,0-0.6-0.2c-0.4-0.4-0.8-0.8-1.2-1.2c1-1,1.9-1.9,2.9-2.9c0.1-0.1,0.2-0.2,0.2-0.2
+	c-1.3-1.3-2.5-2.5-3.8-3.9c-0.1,0.1-0.1,0.2-0.2,0.3c-1,1-1.9,1.9-2.9,2.9c-1.8-1.8-3.5-3.5-5.3-5.3c-0.1-0.1-0.3-0.3-0.4-0.4
+	c-1-0.7-2.2-0.6-3,0.3c-0.8,0.9-0.7,2.2,0.1,3c2.6,2.6,5.3,5.3,7.9,7.9c0.9,0.9,1.7,1.7,2.6,2.6c0.1,0.1,0.2,0.2,0.1,0.4
+	c-0.1,0.4-0.2,0.8-0.2,1.2c-0.2,2.5,1.4,4.7,3.9,5.4c1,0.3,2,0.2,3.1-0.2c-0.1-0.1-0.2-0.2-0.3-0.2c-0.7-0.7-1.5-1.5-2.2-2.2
+	c-0.5-0.5-0.6-1.4-0.1-1.9c0.4-0.4,0.8-0.8,1.2-1.2c0.6-0.5,1.3-0.5,1.8,0c0.1,0.1,0.2,0.2,0.3,0.3c0.7,0.7,1.5,1.5,2.2,2.2
+	c0,0,0.1,0,0.1,0c0.1-0.4,0.2-0.8,0.2-1.3C36.6,120.9,33.2,118.1,29.7,119.1z M17,110.6c-0.4,0-0.7-0.3-0.7-0.7
+	c0-0.4,0.3-0.8,0.7-0.8c0.4,0,0.8,0.4,0.8,0.7C17.7,110.3,17.4,110.6,17,110.6z M27.4,113c0.3,0.3,0.6,0.7,1,1
+	c-0.7,0.7-1.4,1.4-2.1,2.1c-0.3-0.3-0.7-0.7-1-1C26,114.4,26.7,113.7,27.4,113z"/>
+<g class="st0">
+	<path class="st9" d="M18.5,141.4v1.1H14v-6.4h4.4v1.1h-3.1v1.5H18v1h-2.7v1.7H18.5z"/>
+	<path class="st9" d="M19.7,142.4v-6.4H22c0.5,0,1,0.1,1.4,0.3s0.7,0.4,1,0.7c0.3,0.3,0.5,0.6,0.6,1c0.1,0.4,0.2,0.8,0.2,1.2
+		c0,0.5-0.1,0.9-0.2,1.3c-0.1,0.4-0.4,0.7-0.6,1c-0.3,0.3-0.6,0.5-1,0.6c-0.4,0.2-0.8,0.2-1.3,0.2H19.7z M23.9,139.2
+		c0-0.3,0-0.6-0.1-0.8s-0.2-0.5-0.4-0.7c-0.2-0.2-0.4-0.3-0.6-0.4c-0.2-0.1-0.5-0.2-0.8-0.2H21v4.2H22c0.3,0,0.6-0.1,0.8-0.2
+		c0.2-0.1,0.4-0.3,0.6-0.4s0.3-0.4,0.4-0.7C23.9,139.8,23.9,139.5,23.9,139.2z"/>
+	<path class="st9" d="M26.4,142.4v-6.4h1.2v6.4H26.4z"/>
+	<path class="st9" d="M34,137.1h-2v5.3h-1.2v-5.3h-2v-1.1H34V137.1z"/>
+</g>
+</svg>
diff --git a/scripts/system/hmd.js b/scripts/system/hmd.js
new file mode 100644
index 0000000000..277af68315
--- /dev/null
+++ b/scripts/system/hmd.js
@@ -0,0 +1,57 @@
+//
+//  hmd.js
+//  scripts/system/
+//
+//  Created by Howard Stearns on 2 Jun 2016
+//  Copyright 2016 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+Script.include("libraries/toolBars.js");
+
+var headset; // The preferred headset. Default to the first one found in the following list.
+var displayMenuName = "Display";
+var desktopMenuItemName = "Desktop";
+['OpenVR (Vive)', 'Oculus Rift'].forEach(function (name) {
+    if (!headset && Menu.menuItemExists(displayMenuName, name)) {
+        headset = name;
+    }
+});
+
+function initialPosition(windowDimensions, toolbar) {
+    return {
+        x: windowDimensions.x / 2 + (2 * Tool.IMAGE_WIDTH),
+        y: windowDimensions.y
+    };
+}
+var toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.hmd.toolbar", initialPosition, {
+    x: -Tool.IMAGE_WIDTH / 2,
+    y: -Tool.IMAGE_HEIGHT
+});
+var button = toolBar.addTool({
+    imageURL: Script.resolvePath("assets/images/tools/hmd-switch-01.svg"),
+    subImage: {
+        x: 0,
+        y: Tool.IMAGE_WIDTH,
+        width: Tool.IMAGE_WIDTH,
+        height: Tool.IMAGE_HEIGHT
+    },
+    width: Tool.IMAGE_WIDTH,
+    height: Tool.IMAGE_HEIGHT,
+    alpha: 0.9,
+    visible: true,
+    showButtonDown: true
+});
+function onMousePress (event) {
+    if (event.isLeftButton && button === toolBar.clicked(Overlays.getOverlayAtPoint(event))) {
+        var isDesktop = Menu.isOptionChecked(desktopMenuItemName);
+        Menu.setIsOptionChecked(isDesktop ? headset : desktopMenuItemName, true);
+    }
+};
+Controller.mousePressEvent.connect(onMousePress)
+Script.scriptEnding.connect(function () {
+    Controller.mousePressEvent.disconnect(onMousePress)
+    toolBar.cleanup();
+});