NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @name Leap Motion Controller Support
// @namespace https://levelkro.com
// @version 0.46.1
// @description Control web page with your Leap Motion Controller
// @author levelKro (https://levelkro.com)
// @include http://*/*
// @include https://*/*
// @exclude *github.*
// @exclude *yahoo.*
// @exclude *asus.*
// @noframes
// @run-at document-idle
// @grant window.close
// @grant window.focus
// @connect self
// @connect localhost
// @connect 127.0.0.1
// @license MIT
// @updateURL https://openuserjs.org/meta/levelKro/Leap_Motion_Controller_Support.meta.js
// @copyright 2018, levelKro (https://levelkro.com) (https://openuserjs.org/users/levelKro)
// @license MIT
// ==/UserScript==
/*
Leap Motion Controller Support
Source from;
- JSonViewer in Leap Motion SDK 4.0.0
Modified by;
- Mathieu <levelKro> Légaré <levelkro@yahoo.ca>
Excluded sites
- Please read https://www.reddit.com/r/mturk/comments/ao3mkf/limited_runtime_host_permissions_might_break_some/
Know issues
- GitHub and Yahoo have a content policy and block script.
- Tou.tv scrolling freeze
Uses
- Hand grab
- Move : Scrolling page (open hand and close it for reset center of move)
- Open (5 fingers) : reset hold time
- 2,3 or 4 fingers (open hand and keep fingers you want open for enable center of move)
- move left : back in history
- move right : next in history
- move up : go to top of page
- move down : refresh page
- move up-left :
- move up-right :
- move down-left :
- move down-right :
Version History
0.46
- Added 4 actions (up+left/up+right/down+left/down+right)
- Minors design fix
- Fix hands reports
- Clean codes
- First real stable release
*/
// Customs variables
var LMDebug=false;// Enable debug messages (warning; slow motion)
var LMScrollY=5;// Scroll jump effect (horizontal)
var LMScrollX=1;// Scroll jump effect (vertical)
var LMHold=150;// Holding time in ms for repeat/other command
// Static variables, don't touch
var handCount=0;
var handLeft=false;
var handRight=false;
var handLeftGrab=false;
var handRightGrab=false;
var handLeftHeight=0;
var handRightHeight=0;
var handLeftWidth=0;
var handRightWidth=0;
var scrollStartY=0;
var scrollStartX=0;
var handLeftStartY=0;
var handRightStartY=0;
var handLeftStartX=0;
var handRightStartX=0;
var fingersCount=0;
var fingersInc=0;
var fingersStartX=0;
var fingersStartY=0;
var fingersHeight=0;
var fingersWidth=0;
var fingersWait=LMHold;
var LMws;
var focusListener;
var blurListener;
var LMtimeout;
var drawUse='<div class="LMindicator Use">&nsbp;</div>';
var drawOk='<div class="LMindicator Ok">&nsbp;</div>';
var drawWarn='<div class="LMindicator Warn">&nsbp;</div>';
var drawError='<div class="LMindicator Error">&nsbp;</div>';
var toInput = '<style>';
toInput+=' .LMindicator {line-height:0 !important;font-size:1px !important;border-radius:4px !important;border:1px solid #666 !important;overflow:hidden !important;width:8px !important;height:8px !important;display:inline-block !important;}';
toInput+=' .LMindicator.Ok {background-color:#6ce20b !important;} .LMindicator.Warn {background-color:#eded10 !important;} .LMindicator.Use {background-color:#24a2e5 !important;} .LMindicator.Error {background-color:#ef1c46 !important;}';
toInput+=' .LMwidget {cursor:not-allowed !important;position:fixed !important;top:0 !important;left:0 !important;border:none !important;border-radius:0 0 5px 0 !important; padding:3px 5px !important; float:left !important; display:inline-block !important; z-index:9999 !important; background-color:#333 !important;box-shadow:1px 1px 15px #333 !important; font-size:12px !important; font-family:Arial !important; color:#eee !important; line-height:0.8 !important;}';
toInput+=' .LMhand {font-size:12px !important; font-family:Arial !important; color:#ddd !important;} .LMhand sup {font-size:8px !important;vertical-align: super !important;position:inherit !important;}';
toInput+=' .LMlogo {background-color:#333 !important;font-family:Arial !important; color:#eee !important;} .LMlogo span {color:#d8ea64 !important;}';
toInput+=' .LMsep {color:#666 !important;text-shadow:1px 1px 15px #333 !important;line-height: 0 !important;font-size: 13px !important;margin: -2px 0 !important;padding: 0 !important;} .LMdebug {font-size:12px !important;color:#222 !important; display:block !important; min-width:250px; min-height:300px; max-height:650px; padding:5px !important; background-color:#fff !important; border:1px solid #000;overflow:auto;}';
toInput+='</style>';
toInput+='<div class="LMwidget" title="Leap Motion Controller : Activities monitor">';
toInput+='<span class="LMlogo">Leap <span>Motion</span> </span><span id="LMState">'+drawWarn+'</span> ';
toInput+='<b class="LMsep"> | </b>';
toInput+='<span class="LMhand"> left <span id="LMStateLeft">'+drawError+'</span></span>';
toInput+='<span class="LMhand"> right <span id="LMStateRight">'+drawError+'</span></span>';
toInput+='<span class="LMhand"> fingers <span id="LMStateFingers">'+drawError+'<sup>0</sup></span></span>';
if(LMDebug) toInput+='<div id="LMMain" style="visibility:hidden;">Output:<div id="LMJSon" class="LMdebug"></div></div>';
toInput+='</div>';
// Support both the WebSocket and MozWebSocket objects
if ((typeof(WebSocket) == 'undefined') &&
(typeof(MozWebSocket) != 'undefined')) {
WebSocket = MozWebSocket;
}
(function(){startLM();})(); // Run baby, RUN!
function startLM(){
console.log("[LM] Leap Motion Controller Support loaded");
if(window.self !== window.top){
// Is framed, or not at the top, stop script
if(LMDebug) console.log("[LM] Frame detected, stop execution.");
LMws.close();
LMws = null;
if(LMDebug) console.log("[LM] Turn off plugin");
window.removeEventListener("focus", focusListener);
window.removeEventListener("blur", blurListener);
return;
}
else{
console.log("[LM] Starting ...");
var newHTML = document.createElement ('div');
newHTML.innerHTML = toInput;
document.body.appendChild (newHTML);
document.getElementById("LMStateLeft").innerHTML=drawError;
document.getElementById("LMStateRight").innerHTML=drawError;
document.getElementById("LMStateFingers").innerHTML=drawError+"<sup>0</sup>";
// Create the socket with event handlers
// Create and open the socket
if(LMDebug) console.log("[LM] Connecting to Leap Motion Web Socket Service");
LMws = new WebSocket("ws://localhost:6437/v7.json");
// On successful connection
LMws.onopen = function(event) {
document.getElementById("LMState").innerHTML=drawWarn;
LMws.send(JSON.stringify({focused: true})); // claim focus
focusListener = window.addEventListener('focus', function(e) {
LMws.send(JSON.stringify({focused: true})); // claim focus
});
blurListener = window.addEventListener('blur', function(e) {
LMws.send(JSON.stringify({focused: false})); // relinquish focus
});
if(LMDebug) document.getElementById("LMMain").style.visibility = "visible";
if(LMDebug) console.log("[LM] Connected to Leap Motion Web Socket Service");
console.log("[LM] Connected to device");
};
// On message received
LMws.onmessage = function(event) {
var obj = JSON.parse(event.data);
if(LMDebug) var str = JSON.stringify(obj, undefined, 2);
if(obj.hasOwnProperty("hands")){
// Hands count detection
if (obj["hands"].length==2){
if(handCount!=2) {
document.getElementById("LMStateLeft").innerHTML=drawOk;
document.getElementById("LMStateRight").innerHTML=drawOk;
document.getElementById("LMStateFingers").innerHTML=drawWarn+"<sup>"+fingersCount+"</sup>";
handCount=2;
}
} else if (obj["hands"].length==1){
if(handCount!=1) {
if(handLeft) document.getElementById("LMStateLeft").innerHTML=drawOk;
else document.getElementById("LMStateLeft").innerHTML=drawError;
if(handRight) document.getElementById("LMStateRight").innerHTML=drawOk;
else document.getElementById("LMStateRight").innerHTML=drawError;
document.getElementById("LMStateFingers").innerHTML=drawWarn+"<sup>"+fingersCount+"</sup>";
handCount=1;
}
}
else {
if(handCount!=0) {
handCount=0;
fingersCount=0;
handLeft=false;
handRight=false;
document.getElementById("LMStateLeft").innerHTML=drawError;
document.getElementById("LMStateRight").innerHTML=drawError;
document.getElementById("LMStateFingers").innerHTML=drawError+"<sup>0</sup>";
}
}
// Hands Grips detection
if(handCount>=1) {
handLeft=false;
handRight=false;
obj["hands"].forEach(function(hand) {
if(hand["type"]=="left") {
document.getElementById("LMStateLeft").innerHTML=drawOk;
handLeft=true;
if(hand["grabStrength"]>=0.8 && !handLeftGrab) {
handLeftGrab=true;
handLeftStartY=hand["palmPosition"][1];
handLeftStartX=hand["palmPosition"][0];
scrollStartY=window.scrollY;
scrollStartX=window.scrollX;
}
else if(hand["grabStrength"]<=0.8 && handLeftGrab) {
handLeftGrab=false;
}
handLeftHeight=hand["palmPosition"][1];
handLeftWidth=hand["palmPosition"][0];
}
else if(hand["type"]=="right") {
document.getElementById("LMStateRight").innerHTML=drawOk;
handRight=true;
if(hand["grabStrength"]>=0.8 && !handRightGrab) {
handRightGrab=true;
handRightStartY=hand["palmPosition"][1];
handRightStartX=hand["palmPosition"][0];
scrollStartY=window.scrollY;
scrollStartX=window.scrollX;
}
else if(hand["grabStrength"]<=0.8 && handRightGrab) {
handRightGrab=false;
}
handRightHeight=hand["palmPosition"][1];
handRightWidth=hand["palmPosition"][0];
}
});
fingersInc=0;
obj["pointables"].forEach(function(finger) {
if(finger['extended']==true) fingersInc++;
});
var finger = obj["pointables"][0];
if(fingersCount!=fingersInc) {
fingersCount=fingersInc;
fingersStartX=finger["dipPosition"][0];
fingersStartY=finger["dipPosition"][1];
if(fingersInc==5) {
fingersWait=LMHold;
document.getElementById("LMStateFingers").innerHTML=drawUse+"<sup>"+fingersInc+"</sup>";
}
}
fingersWidth=finger["dipPosition"][0];
fingersHeight=finger["dipPosition"][1];
}
else {
handLeftGrab=false;
handRightGrab=false;
handLeft=false;
handRight=false;
fingersCount=0;
}
if (!document.hidden) {
// Hands actions
if(handCount==2){
}
else if(handLeft){
// Left hand actions
if(handLeftGrab){
// Scrolling page
var LYscroll=Math.floor(-1*LMScrollY)*Math.floor(handLeftStartY - handLeftHeight);
var LYnewScroll=Math.floor(scrollStartY + LYscroll);
var LXscroll=LMScrollX*Math.floor(handLeftStartX - handLeftWidth);
var LXnewScroll=Math.floor(scrollStartX + LXscroll);
if(LXnewScroll<=0) LXnewScroll=0;
if(LYnewScroll<=0) LYnewScroll=0;
window.scrollTo(LXnewScroll, LYnewScroll);
document.getElementById("LMStateLeft").innerHTML=drawUse;
}
}
else if(handRight){
// Right hand actions
if(handRightGrab){
// Scrolling page
var RYscroll=Math.floor(-1*LMScrollY)*Math.floor(handRightStartY - handRightHeight);
var RYnewScroll=Math.floor(scrollStartY + RYscroll);
var RXscroll=LMScrollX*Math.floor(handRightStartX - handRightWidth);
var RXnewScroll=Math.floor(scrollStartX + RXscroll);
if(RXnewScroll<=0) RXnewScroll=0;
if(RYnewScroll<=0) RYnewScroll=0;
window.scrollTo(RXnewScroll, RYnewScroll);
document.getElementById("LMStateRight").innerHTML=drawUse;
}
}
if(fingersCount>=1){
// Fingers actions
document.getElementById("LMStateFingers").innerHTML=drawOk+"<sup>"+fingersCount+"</sup>";
if(fingersCount<=4 && fingersCount>=2){
fingersWait--; // Security wait for "flood"
// Action by move position
document.getElementById("LMStateFingers").innerHTML=drawUse+"<sup>"+fingersCount+"</sup>";
if(fingersWait<=0){
fingersWait=1;
var FYmove=Math.floor(fingersStartY - fingersHeight);
var FXmove=Math.floor(fingersStartX - fingersWidth);
if(FYmove<=-100 && FXmove>=50) {
return; // Up+Left :
}
else if(FYmove<=-100 && FXmove<=-50) {
fingersWait=LMHold;
return; // Up+Right :
}
else if(FYmove>=100 && FXmove>=50) {
fingersWait=LMHold;
return; // Down+Left :
}
else if(FYmove>=100 && FXmove<=-50) {
fingersWait=LMHold;
return; //Down+Right :
}
else if(FYmove<=-100) {
window.scrollTo(0, 0); // Up : Go to top
fingersWait=LMHold;
}
else if(FYmove>=100) {
window.location.reload(); // Down : Refresh action
fingersWait=LMHold;
}
else if(FXmove>=50) {
window.history.go(-1); // Left : Back
fingersWait=LMHold;
}
else if(FXmove<=-50) {
window.history.go(+1); // Right: Next
fingersWait=LMHold;
}
}
}
}
else {
// No fingers found
document.getElementById("LMStateFingers").innerHTML=drawError+"<sup>0</sup>";
fingersWait=LMHold;
fingersCount=0;
}
}
}
if(obj.hasOwnProperty("timestamp")){
if(handCount>=1) document.getElementById("LMState").innerHTML=drawUse;
else if(obj.hasOwnProperty("hands")) document.getElementById("LMState").innerHTML=drawOk;
else document.getElementById("LMState").innerHTML=drawWarn;
if(LMDebug) document.getElementById("LMJSon").innerHTML = '<pre>' + str + '</pre>';
}
else if(obj.hasOwnProperty("serviceVersion")){
document.getElementById("LMState").innerHTML=drawOk;
if(LMDebug) console.log("[LM] Using Leap Motion software version : "+obj["serviceVersion"]+" with Web Socket Service version : "+obj["version"]);
}
else if(obj.hasOwnProperty("event")){
document.getElementById("LMState").innerHTML=drawOk;
if(!obj['event']['state']['streaming']) {
document.getElementById("LMState").innerHTML=drawWarn;
console.log("[LM] Device on pause, enable the device for use it");
}
}
else{
if(LMDebug) console.log("[LM] Bad answer from Leap Motion Web Socket Service: "+str);
document.getElementById("LMState").innerHTML=drawError;
}
// Catch the pause of device
window.clearTimeout(LMtimeout);
LMtimeout=window.setTimeout(function(){document.getElementById("LMState").innerHTML=drawWarn;console.log("[LM] Device on pause, enable the device for use it");}, 100);
};
// On socket close
LMws.onclose = function(event) {
LMws = null;
window.removeEventListener("focus", focusListener);
window.removeEventListener("blur", blurListener);
if(LMDebug) document.getElementById("LMMain").style.visibility = "hidden";
document.getElementById("LMState").innerHTML=drawError;
if(LMDebug) console.log("[LM] Deconnected from Leap Motion Web Socket Service");
}
// On socket error
LMws.onerror = function(event) {
var obj = JSON.parse(event.data);
var str = JSON.stringify(obj, undefined, 2);
document.getElementById("LMState").innerHTML=drawWarn;
document.getElementById("LMStateLeft").innerHTML=drawError;
document.getElementById("LMStateRight").innerHTML=drawError;
document.getElementById("LMStateFingers").innerHTML=drawError+"<sup>0</sup>";
console.log("[LM] Error: "+str);
};
}
}