Leap Motion Controller Support

Control web page with your Leap Motion Controller

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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
// @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é <[email protected]>

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);
        };
    }
}