(function() {
/* TODO(sissel): This could use some serious refactoring. */
$(document).ready(function() {
var status = $("#status");
var keyboard = $('#keyboard');
var keyboard_button = keyboard.prev('a');
keyboard.width(keyboard_button.width());
keyboard.height(keyboard_button.height());
/* TODO(sissel): get the computed width (margin, padding, width) */
keyboard.css('margin-left', '-' + keyboard_button.width() + 'px');
keyboard.show();
keyboard.bind("focus", function() {
/* move the textarea away so we don't see the caret */
keyboard.css('margin-left', '-10000px');
state.keyboard = true;
$(window).triggerHandler("resize");
});
keyboard.bind("blur", function(){
keyboard.css('margin-left', '-' + keyboard_button.width() + 'px');
state.keyboard = false;
$(window).triggerHandler("resize");
});
keyboard.bind("keypress", function(event) {
var e = event.originalEvent;
var key = e.charCode;
if (!key) {
key = (e.keyCode ? e.keyCode : e.which);
}
state.websocket.send(JSON.stringify({
action: "log",
shift: e.shiftKey,
char: e.charCode,
ctrl: e.ctrlKey,
meta: e.ctrlKey,
}));
state.websocket.send(JSON.stringify({
action: "keypress",
key: key,
shift: e.shiftKey,
}));
/* Only prevent default if we're not backspace,
* this lets 'backspace' do keyrepeat. */
if (key != 8) {
event.preventDefault();
}
}).bind("change", function(event) {
/* Skip empty changes */
if (keyboard.val() == "") {
return;
}
state.websocket.send(JSON.stringify({
action: "type",
string: keyboard.val(),
}));
/* Clear the field */
keyboard.val("");
});
keyboard.bind("keyup", function(event) {
var e = event.originalEvent;
state.websocket.send(JSON.stringify({
action: "log",
shift: e.shiftKey,
char: e.charCode,
key: e.which,
ctrl: e.ctrlKey,
meta: e.ctrlKey,
}));
var key = (e.keyCode ? e.keyCode : e.which);
if (key >= 32 && key <= 127) {
/* skip printable keys (a-z, etc) */
return;
}
state.websocket.send(JSON.stringify({
action: "keypress",
key: key,
shift: e.shiftKey,
}));
event.preventDefault();
});
var config = function (key, value, default_value) {
if (value) {
status.html("config[" + key + "] = " + value);
//alert(key + " => " + value);
window.localStorage[key] = value
return value
} else {
return window.localStorage[key] || default_value
}
};
var state = {
x: -1,
y: -1,
moving: false,
dragging: false,
width: window.innerWidth,
height: window.innerHeight,
key: undefined, /* TODO(sissel): unused? */
keyboard: false,
touchpad_active: false,
mouse: { },
scroll: {
y: 0,
}
};
state.message_callback = function(request) {
action = request["action"];
switch (action) {
case "status":
/* Use eval to do unicode escaping */
var status = eval("\"" + request["status"] + "\"");
var el = $("
" + status + "
");
$("#area").empty().append(el);
el.delay(500).fadeOut(500, function() { $(this).remove() });
break;
}
};
/* Sync configuration elements */
/* Mouse movement */
console.log(config("fingerpoken/mouse/movement"));
$("input[name = \"mouse-config\"]")
.bind("change", function(event) {
config("fingerpoken/mouse/movement", event.target.value);
}).filter("[value = \"" + config("fingerpoken/mouse/movement") + "\"]")
.attr("checked", "checked").click()
$("input[name = \"mouse-acceleration\"]")
.bind("change", function(event) {
config("fingerpoken/mouse/acceleration", parseInt(event.target.value));
}).val(config("fingerpoken/mouse/acceleration")).change();
/* Changing orientation sometimes leaves the viewport
* not starting at 0,0. Fix it with this hack.
* Also, we want to make the content size full height. */
$(window).bind("orientationchange resize pageshow", function(event) {
scroll(0, 0);
var header = $(".header:visible");
var footer = $(".footer:visible");
var content = $(".content:visible");
var viewport_height = $(window).height();
var content_height = viewport_height - header.outerHeight() - footer.outerHeight();
/* Trim margin/border/padding height */
content_height -= (content.outerHeight() - content.height());
/* TODO(sissel): Make this special handling only for iphones.
* http://developer.apple.com/library/safari/#documentation/appleapplications/reference/safariwebcontent/UsingtheViewport/UsingtheViewport.html
*/
if (state.keyboard) {
if (window.orientation == 90 || window.orientation == -90) {
content_height -= 162; /* landscape orientation keyboard */
content_height -= 32; /* "form assistant" aka FormFill, this height is undocumented. */
} else {
content_height -= 216; /* portrait orientation keyboard */
content_height -= 44; /* "form assistant" aka FormFill */
}
}
status.html("Resize / " + window.orientation + " / " + state.keyboard + " / " + content_height);
content.height(content_height);
});
var connect = function(state) {
status.html("connecting...");
var websocket = new WebSocket("ws://" + document.location.hostname + ":5001");
websocket.onopen = function(event) {
status.html("websocket ready");
}
websocket.onclose = function(event) {
status.html("Closed, trying to reopen.");
setTimeout(function() {
connect(state);
}, 1000);
}
websocket.onmessage = function(event) {
var request = JSON.parse(event.data);
state.message_callback(request)
}
state.websocket = websocket;
}
connect(state);
/* This will track orientation/motion changes with the accelerometer and
* gyroscope. Not sure how useful this would be... */
//$(window).bind("devicemotion", function(event) {
//var e = event.originalEvent;
//state.accel = e.accelerationIncludingGravity;
/* Trim shakes */
//if (Math.abs(state.accel.x) < 0.22 && Math.abs(state.accel.y) < 0.22) {
//return;
//}
//status.html("Motion: \nx: " + state.accel.x + "\ny: " + state.accel.y + "\nz: " + state.accel.z);
//state.websocket.send(JSON.stringify({
//action: "move",
//rel_x: Math.ceil(state.accel.x) * -1,
//rel_y: Math.ceil(state.accel.y) * -1,
//}));
//});
/* TODO(sissel): add mousedown/mousemove/mouseup support */
$("#area").bind("touchstart mousedown", function(event) {
var e = event.originalEvent;
state.touchpad_active = true;
/* if no 'touches', use the event itself, one finger/mouse */
var touches = e.touches || [ e ];
var output = "Start: " + touches[0].clientX + "," + touches[0].clientY + "\n";
output += "Fingers: " + touches.length + "\n";
status.html(output);
/* number of fingers == mouse button */
state.fingers = touches.length;
switch (state.fingers) {
case 1: state.button = 1; break;
case 2: state.button = 3; break;
case 3: state.button = 2; break;
}
var now = (new Date()).getTime();
if ((now - state.last_click) < 170) {
/* Start dragging */
state.websocket.send(JSON.stringify({
action: "mousedown",
button: state.button,
}))
state.dragging = true;
}
event.preventDefault();
}).bind("touchend mouseup", function(event) { /* $("#touchpadsurface").bind("touchend" ... */
var e = event.originalEvent;
var touches = e.touches || [ e ];
if (state.mouse.vectorTimer) {
clearInterval(state.mouse.vectorTimer);
state.mouse.vectorTimer = null;
}
if (state.dragging) {
state.websocket.send(JSON.stringify({
action: "mouseup",
button: state.button,
}));
state.dragging = false;
} else {
if (state.moving && !state.scrolling) {
var e = state.last_move;
var r = e.rotation;
if (r < 0) {
r += 360;
}
status.html(r);
} else if (state.scrolling) {
/* nothing for now */
} else {
/* No movement, click! */
status.html("Click!");
state.websocket.send(JSON.stringify({
action: "click",
button: state.button,
}));
state.last_click = (new Date()).getTime();
}
}
if (touches.length == 0 || !e.touches) {
state.moving = false;
state.scrolling = false;
state.touchpad_active = false;
}
event.preventDefault();
}).bind("touchmove mousemove", function(event) { /* $("#touchpadsurface").bind("touchmove" ... */
var e = event.originalEvent;
var touches = e.touches || [ e ];
//if (!state.touchpad_active) {
//event.preventDefault();
//return;
//}
if (!state.moving) {
/* Start calculating delta offsets now */
state.moving = true;
state.start_x = state.x = touches[0].clientX;
state.start_y = state.y = touches[0].clientY;
/* Skip this event */
return;
}
state.last_move = e;
var output = "";
for (var i in touches) {
output += i + ": " + touches[i].clientX + "," + touches[i].clientY + "\n";
}
var r = e.rotation;
if (r < 0) {
r += 360;
}
output += "rotation: " + r + "\n";
output += "scale: " + e.scale + "\n";
var x = touches[0].clientX;
var y = touches[0].clientY;
var delta_x = (x - state.x);
var delta_y = (y - state.y);
/* Apply acceleration */
var sign_x = (delta_x < 0 ? -1 : 1);
var sign_y = (delta_y < 0 ? -1 : 1);
/* jQuery Mobile or HTML 'range' inputs don't support floating point.
* Hack around it by using larger numbers and compensating. */
var accel = config("fingerpoken/mouse/acceleration", null, 150) / 100.0;
output += "Accel: " + accel + "\n";
var delta_x = Math.ceil(Math.pow(Math.abs(delta_x), accel) * sign_x);
var delta_y = Math.ceil(Math.pow(Math.abs(delta_y), accel) * sign_y);
output += "Delta: " + delta_x + ", " + delta_y + "\n";
state.x = x;
state.y = y;
/* TODO(sissel): Make this a config option */
if (e.rotation < -10 || e.rotation > 10) {
/* Skip rotations that are probably not mouse-cursor-wanting movements */
return;
}
/* TODO(sissel): Make this a config option */
if (e.scale < 0.9 || e.scale > 1.1) {
/* Skip scales that are probably not mouse-cursor-wanting movements */
return;
}
if (touches.length > 1 && !state.dragging) {
/* Multifinger movement, probably should scroll? */
if (Math.abs(delta_y) > 0) {
/* Scroll */
state.scroll.y += delta_y;
/* Don't scroll every time we move, wait until we move enough
* that it is more than 10 pixels. */
/* TODO(sissel): Make this a config option */
if (Math.abs(state.scroll.y) > 10) {
state.scrolling = true;
state.moving = false;
state.scroll.y = 0;
state.websocket.send(JSON.stringify({
action: "click",
button: (delta_y < 0) ? 4 : 5,
}))
}
} /* if (Math.abs(delta_y) > 0) */
} else {
/* Only 1 finger, and we aren't dragging. So let's move! */
/* TODO(sissel): Refactor these in to fumctions */
var movement = config("fingerpoken/mouse/movement");
if (movement == "relative") {
state.websocket.send(JSON.stringify({
action: "mousemove_relative",
rel_x: delta_x,
rel_y: delta_y
}));
} else if (movement == "absolute") {
/* Send absolute in terms of percentages. */
var content = $(".content:visible");
state.websocket.send(JSON.stringify({
action: "mousemove_absolute",
percent_x: x / content.innerWidth(),
percent_y: y / content.innerHeight(),
}));
} else if (movement == "vector") {
if (!state.mouse.vectorTimer) {
state.mouse.vectorTimer = setInterval(function() {
var rx = state.x - state.start_x;
var ry = state.y - state.start_y;
if (rx == 0 || ry == 0) {
return;
}
var sign_rx = (rx < 0 ? -1 : 1);
var sign_ry = (ry < 0 ? -1 : 1);
var vector_accel = accel / 1.7 /* feels like the right ratio */
output = "rx,ry = " + rx + ", " + ry + "\n";
rx = Math.ceil(Math.pow(Math.abs(rx), vector_accel) * sign_rx);
ry = Math.ceil(Math.pow(Math.abs(ry), vector_accel) * sign_ry);
output += "rx2,ry2 = " + rx + ", " + ry + "\n";
state.websocket.send(JSON.stringify({
action: "mousemove_relative",
rel_x: rx,
rel_y: ry
}));
}, 15);
} /* if (!state.mouse.vectorTimer) */
} /* mouse vector movement */
status.html(output)
} /* finger movement */
}); /* $("#touchpadsurface").bind( ... )*/
/* Take commands like this:
*
* Key press:
*
*
* Mouse click
*
*/
$("a.command").bind("touchstart", function(event) {
state.touchelement = this;
}).bind("mousedown", function(event) {
state.touchelement = this;
event.preventDefault();
}).bind("touchmove mousemove", function(event) {
event.preventDefault();
}).bind("touchend mouseup", function(event) {
if (state.touchelement == this) {
state.websocket.send(JSON.stringify({
action: $(this).attr("data-action"),
key: $(this).attr("data-key"),
button: parseInt($(this).attr("data-button")),
}));
}
});
}); /* $(document).ready */
})();