fingerpoken 0.2.20110104190832 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/bin/fingerpoken.rb CHANGED
@@ -28,6 +28,8 @@ end
28
28
 
29
29
  def main(args)
30
30
  targets = []
31
+ passphrase = nil
32
+
31
33
  opts = OptionParser.new do |opts|
32
34
  opts.banner = "Usage: #{$0} [options]"
33
35
 
@@ -49,6 +51,10 @@ def main(args)
49
51
  targets << [:Tivo, { :host => target.host }]
50
52
  end
51
53
  end
54
+
55
+ opts.on("--passphrase PASSPHRASE", "Passphrase for verifying client calls") do |val|
56
+ passphrase = val
57
+ end
52
58
  end
53
59
  opts.parse(args)
54
60
 
@@ -62,8 +68,9 @@ def main(args)
62
68
  channel = EventMachine::Channel.new
63
69
 
64
70
  targets.each do |klass, args|
65
- args.merge!({ :channel => channel })
71
+ args.merge!({ :channel => channel, :passphrase => passphrase })
66
72
  target = FingerPoken::Target.const_get(klass).new(args)
73
+
67
74
  target.register
68
75
  end # targets.each
69
76
 
@@ -84,4 +91,4 @@ def main(args)
84
91
  end # EventMachine::run
85
92
  end
86
93
 
87
- exit(main(ARGV))
94
+ main(ARGV)
@@ -1,17 +1,67 @@
1
+ require "rubygems"
1
2
  require "logger"
3
+ require "hmac-md5"
2
4
 
3
5
  class FingerPoken::Target
4
6
  def initialize(config)
5
7
  @channel = config[:channel]
6
8
  @logger = Logger.new(STDERR)
7
9
  @logger.level = ($DEBUG ? Logger::DEBUG: Logger::WARN)
10
+ @passphrase = config[:passphrase] # OK if this is nil
11
+
12
+ @last_sequence = -1
8
13
  end
9
14
 
15
+ def verify(request, callback)
16
+ if !request["signature"]
17
+ # TODO(sissel): Send callback saying "passphrase required"
18
+ @logger.warn("Message with no signature")
19
+ return false
20
+ end
21
+
22
+ if request["sequence"] < @last_sequence
23
+ # Reject out of sequence or replayed messages
24
+ # TODO(sissel): Report replay attack detected
25
+ @logger.warn("Sequence #{request["sequence"]} < #{@last_sequence} "\
26
+ "(last sequence). Replay attack or bug?")
27
+ return false
28
+ end
29
+
30
+ hmac = HMAC::MD5.new(@passphrase)
31
+ hmac.update(request["sequence"].to_s)
32
+ digest_bytes = hmac.digest.bytes.to_a
33
+ if request["signature"] == digest_bytes
34
+ return true
35
+ else
36
+ # TODO(sissel): Report verification failed
37
+ @logger.warn("Signature verify failed. Server:#{digest_bytes.inspect}, Client:#{request["signature"].inspect}")
38
+ return false
39
+ end
40
+ end # def verify
41
+
10
42
  def register
43
+ if @registered
44
+ @logger.warn("Ignoring extra call to #{self.class.name}#register. Trace:\n#{caller[0..3].join("\n")}")
45
+ return
46
+ end
47
+
48
+ @registered = true
49
+ @logger.debug(:register => self.class.name)
11
50
  @channel.subscribe do |obj|
12
51
  request = obj[:request]
13
52
  callback = obj[:callback]
14
- @logger.debug(request)
53
+ @logger.debug(:request => request)
54
+
55
+ if @passphrase
56
+ verified = verify(request, callback)
57
+ if !verified
58
+ @logger.warn("Dropping corrupt/forged request")
59
+ next
60
+ end
61
+ end
62
+
63
+ @last_sequence = request["sequence"].to_i
64
+
15
65
  response = case request["action"]
16
66
  when "mousemove_relative"
17
67
  mousemove_relative(request["rel_x"], request["rel_y"])
@@ -36,8 +86,8 @@ class FingerPoken::Target
36
86
  if response.is_a?(Hash)
37
87
  callback.call(response)
38
88
  end
39
- end
40
- end
89
+ end # @channel.subscribe
90
+ end # def register
41
91
 
42
92
  # Subclasses should implement this.
43
93
  def mousemove_relative(x, y)
@@ -16,6 +16,7 @@ class FingerPoken::Target::VNC < FingerPoken::Target
16
16
  @password = (config[:password] or config[:user])
17
17
  @host = config[:host]
18
18
  @port = (config[:port] or 5900)
19
+ @ready = false
19
20
  @recenter = config[:recenter]
20
21
 
21
22
  # For eventmachine-vnc
@@ -31,7 +32,11 @@ class FingerPoken::Target::VNC < FingerPoken::Target
31
32
  @buttonmask = 0
32
33
  end
33
34
 
34
- def update
35
+ def update_mouse
36
+ if !@ready
37
+ @logger.warn("VNC connection is not ready. Ignoring update.")
38
+ return { "action" => "status", "status" => "VNC connection not ready, yet" }
39
+ end
35
40
  @vnc.pointerevent(@x, @y, @buttonmask)
36
41
 
37
42
  # TODO(sissel): Hack to make it work in TF2.
@@ -43,10 +48,15 @@ class FingerPoken::Target::VNC < FingerPoken::Target
43
48
  end
44
49
  end
45
50
 
51
+ def ready
52
+ @ready = true
53
+ return { "action" => "status", "status" => "VNC READY!" }
54
+ end
55
+
46
56
  def mousemove_relative(x, y)
47
57
  @x += x
48
58
  @y += y
49
- update
59
+ update_mouse
50
60
  return nil
51
61
  end
52
62
 
@@ -56,7 +66,7 @@ class FingerPoken::Target::VNC < FingerPoken::Target
56
66
  ybuf = @screen_y * 0.1
57
67
  @x = (((@screen_x + xbuf) * px) - (xbuf / 2)).to_i
58
68
  @y = (((@screen_y + ybuf) * py) - (ybuf / 2)).to_i
59
- update
69
+ update_mouse
60
70
  return nil
61
71
  end
62
72
 
@@ -64,7 +74,7 @@ class FingerPoken::Target::VNC < FingerPoken::Target
64
74
  button = (1 << (button.to_i - 1))
65
75
  return if @buttonmask & button != 0
66
76
  @buttonmask |= button
67
- update
77
+ update_mouse
68
78
  return nil
69
79
  end
70
80
 
@@ -72,13 +82,46 @@ class FingerPoken::Target::VNC < FingerPoken::Target
72
82
  button = (1 << (button.to_i - 1))
73
83
  return if @buttonmask & button == 0
74
84
  @buttonmask &= (~button)
75
- update
85
+ update_mouse
76
86
  return nil
77
87
  end
78
88
 
79
89
  # TODO(sissel): Add keyboard support.
80
90
  # VNC uses the same keysym values as X11, so that's a win. We can likely
81
91
  # leverage xdo's char-to-keysym magic with VNC.
92
+ def keypress(key)
93
+ puts "Got key: #{key} (#{key.class})"
94
+ if key.is_a?(String)
95
+ if key.length == 1
96
+ # Assume letter
97
+ @vnc.keyevent(key.chr, true)
98
+ @vnc.keyevent(key.chr, false)
99
+ else
100
+ # Assume keysym
101
+ puts "I don't know how to type '#{key}'"
102
+ return { :action => "status", :status => "I don't know how to type '#{key}'" }
103
+ end
104
+ else
105
+ # type printables, key others.
106
+ if 32.upto(127).include?(key)
107
+ @vnc.keyevent(key, true)
108
+ @vnc.keyevent(key, false)
109
+ else
110
+ case key
111
+ when 8
112
+ @vnc.keyevent(0xff08, true)
113
+ @vnc.keyevent(0xff08, false)
114
+ when 13
115
+ @vnc.keyevent(0xff0D, true)
116
+ @vnc.keyevent(0xff0D, false)
117
+ else
118
+ puts "I don't know how to type web keycode '#{key}'"
119
+ return { :action => "status", :status => "I don't know how to type '#{key}'" }
120
+ end # case key
121
+ end # if 32.upto(127).include?(key)
122
+ end # if key.is_a?String
123
+ return nil
124
+ end # def keypress
82
125
 
83
126
  class VNCClient < EventMachine::Connection
84
127
  include EventMachine::Protocols::VNC::Client
@@ -93,7 +136,7 @@ class FingerPoken::Target::VNC < FingerPoken::Target
93
136
  @target.buttonmask = 0
94
137
  @target.x = (@screen_width / 2).to_i
95
138
  @target.y = (@screen_height / 2).to_i
96
- @target.register
139
+ @target.ready()
97
140
  end
98
141
  end # class VNCClient
99
142
  end
@@ -0,0 +1,11 @@
1
+ if(typeof Crypto=="undefined"||!Crypto.util)(function(){var n=window.Crypto={},o=n.util={rotl:function(g,i){return g<<i|g>>>32-i},rotr:function(g,i){return g<<32-i|g>>>i},endian:function(g){if(g.constructor==Number)return o.rotl(g,8)&16711935|o.rotl(g,24)&4278255360;for(var i=0;i<g.length;i++)g[i]=o.endian(g[i]);return g},randomBytes:function(g){for(var i=[];g>0;g--)i.push(Math.floor(Math.random()*256));return i},bytesToWords:function(g){for(var i=[],h=0,a=0;h<g.length;h++,a+=8)i[a>>>5]|=g[h]<<24-
2
+ a%32;return i},wordsToBytes:function(g){for(var i=[],h=0;h<g.length*32;h+=8)i.push(g[h>>>5]>>>24-h%32&255);return i},bytesToHex:function(g){for(var i=[],h=0;h<g.length;h++){i.push((g[h]>>>4).toString(16));i.push((g[h]&15).toString(16))}return i.join("")},hexToBytes:function(g){for(var i=[],h=0;h<g.length;h+=2)i.push(parseInt(g.substr(h,2),16));return i},bytesToBase64:function(g){if(typeof btoa=="function")return btoa(p.bytesToString(g));for(var i=[],h=0;h<g.length;h+=3)for(var a=g[h]<<16|g[h+1]<<
3
+ 8|g[h+2],b=0;b<4;b++)h*8+b*6<=g.length*8?i.push("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(a>>>6*(3-b)&63)):i.push("=");return i.join("")},base64ToBytes:function(g){if(typeof atob=="function")return p.stringToBytes(atob(g));g=g.replace(/[^A-Z0-9+\/]/ig,"");for(var i=[],h=0,a=0;h<g.length;a=++h%4)a!=0&&i.push(("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".indexOf(g.charAt(h-1))&Math.pow(2,-2*a+8)-1)<<a*2|"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".indexOf(g.charAt(h))>>>
4
+ 6-a*2);return i}};n.mode={};n=n.charenc={};n.UTF8={stringToBytes:function(g){return p.stringToBytes(unescape(encodeURIComponent(g)))},bytesToString:function(g){return decodeURIComponent(escape(p.bytesToString(g)))}};var p=n.Binary={stringToBytes:function(g){for(var i=[],h=0;h<g.length;h++)i.push(g.charCodeAt(h)&255);return i},bytesToString:function(g){for(var i=[],h=0;h<g.length;h++)i.push(String.fromCharCode(g[h]));return i.join("")}}})();
5
+ (function(){var n=Crypto,o=n.util,p=n.charenc,g=p.UTF8,i=p.Binary,h=n.MD5=function(a,b){var j=o.wordsToBytes(h._md5(a));return b&&b.asBytes?j:b&&b.asString?i.bytesToString(j):o.bytesToHex(j)};h._md5=function(a){if(a.constructor==String)a=g.stringToBytes(a);var b=o.bytesToWords(a),j=a.length*8;a=1732584193;for(var d=-271733879,e=-1732584194,c=271733878,f=0;f<b.length;f++)b[f]=(b[f]<<8|b[f]>>>24)&16711935|(b[f]<<24|b[f]>>>8)&4278255360;b[j>>>5]|=128<<j%32;b[(j+64>>>9<<4)+14]=j;j=h._ff;var k=h._gg,l=
6
+ h._hh,m=h._ii;for(f=0;f<b.length;f+=16){var q=a,r=d,s=e,t=c;a=j(a,d,e,c,b[f+0],7,-680876936);c=j(c,a,d,e,b[f+1],12,-389564586);e=j(e,c,a,d,b[f+2],17,606105819);d=j(d,e,c,a,b[f+3],22,-1044525330);a=j(a,d,e,c,b[f+4],7,-176418897);c=j(c,a,d,e,b[f+5],12,1200080426);e=j(e,c,a,d,b[f+6],17,-1473231341);d=j(d,e,c,a,b[f+7],22,-45705983);a=j(a,d,e,c,b[f+8],7,1770035416);c=j(c,a,d,e,b[f+9],12,-1958414417);e=j(e,c,a,d,b[f+10],17,-42063);d=j(d,e,c,a,b[f+11],22,-1990404162);a=j(a,d,e,c,b[f+12],7,1804603682);c=
7
+ j(c,a,d,e,b[f+13],12,-40341101);e=j(e,c,a,d,b[f+14],17,-1502002290);d=j(d,e,c,a,b[f+15],22,1236535329);a=k(a,d,e,c,b[f+1],5,-165796510);c=k(c,a,d,e,b[f+6],9,-1069501632);e=k(e,c,a,d,b[f+11],14,643717713);d=k(d,e,c,a,b[f+0],20,-373897302);a=k(a,d,e,c,b[f+5],5,-701558691);c=k(c,a,d,e,b[f+10],9,38016083);e=k(e,c,a,d,b[f+15],14,-660478335);d=k(d,e,c,a,b[f+4],20,-405537848);a=k(a,d,e,c,b[f+9],5,568446438);c=k(c,a,d,e,b[f+14],9,-1019803690);e=k(e,c,a,d,b[f+3],14,-187363961);d=k(d,e,c,a,b[f+8],20,1163531501);
8
+ a=k(a,d,e,c,b[f+13],5,-1444681467);c=k(c,a,d,e,b[f+2],9,-51403784);e=k(e,c,a,d,b[f+7],14,1735328473);d=k(d,e,c,a,b[f+12],20,-1926607734);a=l(a,d,e,c,b[f+5],4,-378558);c=l(c,a,d,e,b[f+8],11,-2022574463);e=l(e,c,a,d,b[f+11],16,1839030562);d=l(d,e,c,a,b[f+14],23,-35309556);a=l(a,d,e,c,b[f+1],4,-1530992060);c=l(c,a,d,e,b[f+4],11,1272893353);e=l(e,c,a,d,b[f+7],16,-155497632);d=l(d,e,c,a,b[f+10],23,-1094730640);a=l(a,d,e,c,b[f+13],4,681279174);c=l(c,a,d,e,b[f+0],11,-358537222);e=l(e,c,a,d,b[f+3],16,-722521979);
9
+ d=l(d,e,c,a,b[f+6],23,76029189);a=l(a,d,e,c,b[f+9],4,-640364487);c=l(c,a,d,e,b[f+12],11,-421815835);e=l(e,c,a,d,b[f+15],16,530742520);d=l(d,e,c,a,b[f+2],23,-995338651);a=m(a,d,e,c,b[f+0],6,-198630844);c=m(c,a,d,e,b[f+7],10,1126891415);e=m(e,c,a,d,b[f+14],15,-1416354905);d=m(d,e,c,a,b[f+5],21,-57434055);a=m(a,d,e,c,b[f+12],6,1700485571);c=m(c,a,d,e,b[f+3],10,-1894986606);e=m(e,c,a,d,b[f+10],15,-1051523);d=m(d,e,c,a,b[f+1],21,-2054922799);a=m(a,d,e,c,b[f+8],6,1873313359);c=m(c,a,d,e,b[f+15],10,-30611744);
10
+ e=m(e,c,a,d,b[f+6],15,-1560198380);d=m(d,e,c,a,b[f+13],21,1309151649);a=m(a,d,e,c,b[f+4],6,-145523070);c=m(c,a,d,e,b[f+11],10,-1120210379);e=m(e,c,a,d,b[f+2],15,718787259);d=m(d,e,c,a,b[f+9],21,-343485551);a=a+q>>>0;d=d+r>>>0;e=e+s>>>0;c=c+t>>>0}return o.endian([a,d,e,c])};h._ff=function(a,b,j,d,e,c,f){a=a+(b&j|~b&d)+(e>>>0)+f;return(a<<c|a>>>32-c)+b};h._gg=function(a,b,j,d,e,c,f){a=a+(b&d|j&~d)+(e>>>0)+f;return(a<<c|a>>>32-c)+b};h._hh=function(a,b,j,d,e,c,f){a=a+(b^j^d)+(e>>>0)+f;return(a<<c|a>>>
11
+ 32-c)+b};h._ii=function(a,b,j,d,e,c,f){a=a+(j^(b|~d))+(e>>>0)+f;return(a<<c|a>>>32-c)+b};h._blocksize=16;h._digestsize=16})();
@@ -0,0 +1 @@
1
+ (function(){var f=Crypto,j=f.util,g=f.charenc,h=g.UTF8,k=g.Binary;f.HMAC=function(b,c,a,d){if(c.constructor==String)c=h.stringToBytes(c);if(a.constructor==String)a=h.stringToBytes(a);if(a.length>b._blocksize*4)a=b(a,{asBytes:true});var i=a.slice(0);a=a.slice(0);for(var e=0;e<b._blocksize*4;e++){i[e]^=92;a[e]^=54}b=b(i.concat(b(a.concat(c),{asBytes:true})),{asBytes:true});return d&&d.asBytes?b:d&&d.asString?k.bytesToString(b):j.bytesToHex(b)}})();
@@ -0,0 +1,9 @@
1
+ Files:
2
+ 2.2.0-crypto-md5.js
3
+ 2.2.0-hmac-min.js
4
+
5
+ Source:
6
+ http://code.google.com/p/crypto-js/
7
+
8
+ License:
9
+ New BSD
@@ -1,10 +1,95 @@
1
1
  (function() {
2
2
  /* TODO(sissel): This could use some serious refactoring. */
3
3
 
4
+ /* From http://alastairc.ac/2010/03/detecting-touch-based-browsing/ */
5
+ function isTouchDevice() {
6
+ var el = document.createElement('div');
7
+ el.setAttribute('ongesturestart', 'return;');
8
+ if (typeof el.ongesturestart == "function"){
9
+ return true;
10
+ } else {
11
+ return false;
12
+ }
13
+ };
14
+ var TOUCH_SUPPORTED = isTouchDevice;
15
+
16
+ var Fingerpoken = function() {
17
+ this.x = -1;
18
+ this.y = -1;
19
+ this.moving = false;
20
+ this.dragging = false;
21
+ this.width = window.innerWidth;
22
+ this.height = window.innerHeight;
23
+ this.key = undefined; /* TODO(sissel) = unused? */
24
+ this.keyboard = false;
25
+ this.touchpad_active = false;
26
+ this.mouse = { };
27
+ this.scroll = {
28
+ y: 0
29
+ };
30
+ this.sequence = Math.floor(Math.random() * 10000);
31
+ };
32
+
33
+ Fingerpoken.prototype.send = function(obj) {
34
+ this.sign(obj);
35
+ this.websocket.send(JSON.stringify(obj))
36
+ };
37
+
38
+ Fingerpoken.prototype.sign = function(obj) {
39
+ var passphrase = this.config("fingerpoken/passphrase");
40
+ obj.sequence = this.sequence;
41
+ this.sequence += 1;
42
+
43
+ console.log("Passphrase: " + passphrase);
44
+ console.log("Sequence: " + this.sequence);
45
+ obj.signature = Crypto.HMAC(Crypto.MD5, "" + obj.sequence, passphrase, { asBytes: true });
46
+ };
47
+
48
+ Fingerpoken.prototype.connect = function() {
49
+ if (this.status === undefined) {
50
+ this.status = $("#status");
51
+ }
52
+ this.status.html("connecting...");
53
+ var websocket = new WebSocket("ws://" + document.location.hostname + ":5001");
54
+ var self = this;
55
+ websocket.onopen = function(event) {
56
+ self.status.html("websocket ready");
57
+ }
58
+
59
+ websocket.onclose = function(event) {
60
+ self.status.html("Closed, trying to reopen.");
61
+ setTimeout(function() {
62
+ self.connect();
63
+ }, 1000);
64
+ }
65
+
66
+ websocket.onmessage = function(event) {
67
+ var request = JSON.parse(event.data);
68
+ fingerpoken.message_callback(request)
69
+ }
70
+
71
+ this.websocket = websocket;
72
+ };
73
+
74
+ Fingerpoken.prototype.config = function(key, value, default_value) {
75
+ if (value) {
76
+ this.status.html("config[" + key + "] = " + value);
77
+ //alert(key + " => " + value);
78
+
79
+ window.localStorage[key] = value
80
+ return value
81
+ } else {
82
+ return window.localStorage[key] || default_value
83
+ }
84
+ };
85
+
4
86
  $(document).ready(function() {
5
87
  var status = $("#status");
6
88
  var keyboard = $('#keyboard');
7
89
  var keyboard_button = keyboard.prev('a');
90
+ var fingerpoken = new Fingerpoken();
91
+ fingerpoken.status = status;
92
+
8
93
  keyboard.width(keyboard_button.width());
9
94
  keyboard.height(keyboard_button.height());
10
95
  /* TODO(sissel): get the computed width (margin, padding, width) */
@@ -14,12 +99,12 @@
14
99
  keyboard.bind("focus", function() {
15
100
  /* move the textarea away so we don't see the caret */
16
101
  keyboard.css('margin-left', '-10000px');
17
- state.keyboard = true;
102
+ fingerpoken.keyboard = true;
18
103
  $(window).triggerHandler("resize");
19
104
  });
20
105
  keyboard.bind("blur", function(){
21
106
  keyboard.css('margin-left', '-' + keyboard_button.width() + 'px');
22
- state.keyboard = false;
107
+ fingerpoken.keyboard = false;
23
108
  $(window).triggerHandler("resize");
24
109
  });
25
110
  keyboard.bind("keypress", function(event) {
@@ -28,14 +113,14 @@
28
113
  if (!key) {
29
114
  key = (e.keyCode ? e.keyCode : e.which);
30
115
  }
31
- state.websocket.send(JSON.stringify({
116
+ fingerpoken.send(JSON.stringify({
32
117
  action: "log",
33
118
  shift: e.shiftKey,
34
119
  char: e.charCode,
35
120
  ctrl: e.ctrlKey,
36
121
  meta: e.ctrlKey,
37
122
  }));
38
- state.websocket.send(JSON.stringify({
123
+ fingerpoken.send(JSON.stringify({
39
124
  action: "keypress",
40
125
  key: key,
41
126
  shift: e.shiftKey,
@@ -52,7 +137,7 @@
52
137
  return;
53
138
  }
54
139
 
55
- state.websocket.send(JSON.stringify({
140
+ fingerpoken.send(JSON.stringify({
56
141
  action: "type",
57
142
  string: keyboard.val(),
58
143
  }));
@@ -63,7 +148,7 @@
63
148
 
64
149
  keyboard.bind("keyup", function(event) {
65
150
  var e = event.originalEvent;
66
- state.websocket.send(JSON.stringify({
151
+ fingerpoken.send(JSON.stringify({
67
152
  action: "log",
68
153
  shift: e.shiftKey,
69
154
  char: e.charCode,
@@ -78,7 +163,7 @@
78
163
  return;
79
164
  }
80
165
 
81
- state.websocket.send(JSON.stringify({
166
+ fingerpoken.send(JSON.stringify({
82
167
  action: "keypress",
83
168
  key: key,
84
169
  shift: e.shiftKey,
@@ -87,35 +172,7 @@
87
172
  event.preventDefault();
88
173
  });
89
174
 
90
- var config = function (key, value, default_value) {
91
- if (value) {
92
- status.html("config[" + key + "] = " + value);
93
- //alert(key + " => " + value);
94
-
95
- window.localStorage[key] = value
96
- return value
97
- } else {
98
- return window.localStorage[key] || default_value
99
- }
100
- };
101
-
102
- var state = {
103
- x: -1,
104
- y: -1,
105
- moving: false,
106
- dragging: false,
107
- width: window.innerWidth,
108
- height: window.innerHeight,
109
- key: undefined, /* TODO(sissel): unused? */
110
- keyboard: false,
111
- touchpad_active: false,
112
- mouse: { },
113
- scroll: {
114
- y: 0,
115
- }
116
- };
117
-
118
- state.message_callback = function(request) {
175
+ fingerpoken.message_callback = function(request) {
119
176
  action = request["action"];
120
177
  switch (action) {
121
178
  case "status":
@@ -131,17 +188,22 @@
131
188
  /* Sync configuration elements */
132
189
 
133
190
  /* Mouse movement */
134
- console.log(config("fingerpoken/mouse/movement"));
191
+ console.log(fingerpoken.config("fingerpoken/mouse/movement"));
135
192
  $("input[name = \"mouse-config\"]")
136
193
  .bind("change", function(event) {
137
- config("fingerpoken/mouse/movement", event.target.value);
138
- }).filter("[value = \"" + config("fingerpoken/mouse/movement") + "\"]")
194
+ fingerpoken.config("fingerpoken/mouse/movement", event.target.value);
195
+ }).filter("[value = \"" + fingerpoken.config("fingerpoken/mouse/movement") + "\"]")
139
196
  .attr("checked", "checked").click()
140
197
 
141
198
  $("input[name = \"mouse-acceleration\"]")
142
199
  .bind("change", function(event) {
143
- config("fingerpoken/mouse/acceleration", parseInt(event.target.value));
144
- }).val(config("fingerpoken/mouse/acceleration")).change();
200
+ fingerpoken.config("fingerpoken/mouse/acceleration", parseInt(event.target.value));
201
+ }).val(fingerpoken.config("fingerpoken/mouse/acceleration")).change();
202
+
203
+ $("input[name = \"fingerpoken-passphrase\"]")
204
+ .bind("change", function(event) {
205
+ fingerpoken.config("fingerpoken/passphrase", event.target.value);
206
+ }).val(fingerpoken.config("fingerpoken/passphrase")).change();
145
207
 
146
208
  /* Changing orientation sometimes leaves the viewport
147
209
  * not starting at 0,0. Fix it with this hack.
@@ -162,7 +224,7 @@
162
224
  /* TODO(sissel): Make this special handling only for iphones.
163
225
  * http://developer.apple.com/library/safari/#documentation/appleapplications/reference/safariwebcontent/UsingtheViewport/UsingtheViewport.html
164
226
  */
165
- if (state.keyboard) {
227
+ if (fingerpoken.keyboard) {
166
228
  if (window.orientation == 90 || window.orientation == -90) {
167
229
  content_height -= 162; /* landscape orientation keyboard */
168
230
  content_height -= 32; /* "form assistant" aka FormFill, this height is undocumented. */
@@ -171,57 +233,36 @@
171
233
  content_height -= 44; /* "form assistant" aka FormFill */
172
234
  }
173
235
  }
174
- status.html("Resize / " + window.orientation + " / " + state.keyboard + " / " + content_height);
236
+ status.html("Resize / " + window.orientation + " / " + fingerpoken.keyboard + " / " + content_height);
175
237
  content.height(content_height);
176
238
  });
177
239
 
178
- var connect = function(state) {
179
- status.html("connecting...");
180
- var websocket = new WebSocket("ws://" + document.location.hostname + ":5001");
181
- websocket.onopen = function(event) {
182
- status.html("websocket ready");
183
- }
184
240
 
185
- websocket.onclose = function(event) {
186
- status.html("Closed, trying to reopen.");
187
- setTimeout(function() {
188
- connect(state);
189
- }, 1000);
190
- }
191
-
192
- websocket.onmessage = function(event) {
193
- var request = JSON.parse(event.data);
194
- state.message_callback(request)
195
- }
196
-
197
- state.websocket = websocket;
198
- }
199
-
200
- connect(state);
241
+ fingerpoken.connect();
201
242
 
202
243
  /* This will track orientation/motion changes with the accelerometer and
203
244
  * gyroscope. Not sure how useful this would be... */
204
245
  //$(window).bind("devicemotion", function(event) {
205
246
  //var e = event.originalEvent;
206
- //state.accel = e.accelerationIncludingGravity;
247
+ //fingerpoken.accel = e.accelerationIncludingGravity;
207
248
 
208
249
  /* Trim shakes */
209
- //if (Math.abs(state.accel.x) < 0.22 && Math.abs(state.accel.y) < 0.22) {
250
+ //if (Math.abs(fingerpoken.accel.x) < 0.22 && Math.abs(fingerpoken.accel.y) < 0.22) {
210
251
  //return;
211
252
  //}
212
- //status.html("Motion: \nx: " + state.accel.x + "\ny: " + state.accel.y + "\nz: " + state.accel.z);
213
- //state.websocket.send(JSON.stringify({
253
+ //status.html("Motion: \nx: " + fingerpoken.accel.x + "\ny: " + fingerpoken.accel.y + "\nz: " + fingerpoken.accel.z);
254
+ //fingerpoken.send({
214
255
  //action: "move",
215
- //rel_x: Math.ceil(state.accel.x) * -1,
216
- //rel_y: Math.ceil(state.accel.y) * -1,
217
- //}));
256
+ //rel_x: Math.ceil(fingerpoken.accel.x) * -1,
257
+ //rel_y: Math.ceil(fingerpoken.accel.y) * -1,
258
+ //});
218
259
  //});
219
260
 
220
261
 
221
262
  /* TODO(sissel): add mousedown/mousemove/mouseup support */
222
263
  $("#area").bind("touchstart mousedown", function(event) {
223
264
  var e = event.originalEvent;
224
- state.touchpad_active = true;
265
+ fingerpoken.touchpad_active = true;
225
266
  /* if no 'touches', use the event itself, one finger/mouse */
226
267
  var touches = e.touches || [ e ];
227
268
  var output = "Start: " + touches[0].clientX + "," + touches[0].clientY + "\n";
@@ -229,84 +270,85 @@
229
270
  status.html(output);
230
271
 
231
272
  /* number of fingers == mouse button */
232
- state.fingers = touches.length;
233
- switch (state.fingers) {
234
- case 1: state.button = 1; break;
235
- case 2: state.button = 3; break;
236
- case 3: state.button = 2; break;
273
+ fingerpoken.fingers = touches.length;
274
+ switch (fingerpoken.fingers) {
275
+ case 1: fingerpoken.button = 1; break;
276
+ case 2: fingerpoken.button = 3; break;
277
+ case 3: fingerpoken.button = 2; break;
237
278
  }
238
279
 
239
280
  var now = (new Date()).getTime();
240
- if ((now - state.last_click) < 170) {
281
+ if ((now - fingerpoken.last_click) < 170) {
241
282
  /* Start dragging */
242
- state.websocket.send(JSON.stringify({
283
+ fingerpoken.send({
243
284
  action: "mousedown",
244
- button: state.button,
245
- }))
246
- state.dragging = true;
285
+ button: fingerpoken.button,
286
+ })
287
+ fingerpoken.dragging = true;
247
288
  }
248
289
  event.preventDefault();
249
290
  }).bind("touchend mouseup", function(event) { /* $("#touchpadsurface").bind("touchend" ... */
250
291
  var e = event.originalEvent;
251
292
  var touches = e.touches || [ e ];
252
293
 
253
- if (state.mouse.vectorTimer) {
254
- clearInterval(state.mouse.vectorTimer);
255
- state.mouse.vectorTimer = null;
294
+ if (fingerpoken.mouse.vectorTimer) {
295
+ clearInterval(fingerpoken.mouse.vectorTimer);
296
+ fingerpoken.mouse.vectorTimer = null;
256
297
  }
257
298
 
258
- if (state.dragging) {
259
- state.websocket.send(JSON.stringify({
299
+ if (fingerpoken.dragging) {
300
+ fingerpoken.send({
260
301
  action: "mouseup",
261
- button: state.button,
262
- }));
263
- state.dragging = false;
302
+ button: fingerpoken.button,
303
+ });
304
+ fingerpoken.dragging = false;
264
305
  } else {
265
- if (state.moving && !state.scrolling) {
266
- var e = state.last_move;
306
+ if (fingerpoken.moving && !fingerpoken.scrolling) {
307
+ var e = fingerpoken.last_move;
267
308
  var r = e.rotation;
268
309
  if (r < 0) {
269
310
  r += 360;
270
311
  }
271
312
 
272
313
  status.html(r);
273
- } else if (state.scrolling) {
314
+ } else if (fingerpoken.scrolling) {
274
315
  /* nothing for now */
275
316
  } else {
276
317
  /* No movement, click! */
277
318
  status.html("Click!");
278
- state.websocket.send(JSON.stringify({
319
+ console.log("click");
320
+ fingerpoken.send({
279
321
  action: "click",
280
- button: state.button,
281
- }));
282
- state.last_click = (new Date()).getTime();
322
+ button: fingerpoken.button,
323
+ });
324
+ fingerpoken.last_click = (new Date()).getTime();
283
325
  }
284
326
  }
285
327
  if (touches.length == 0 || !e.touches) {
286
- state.moving = false;
287
- state.scrolling = false;
288
- state.touchpad_active = false;
328
+ fingerpoken.moving = false;
329
+ fingerpoken.scrolling = false;
330
+ fingerpoken.touchpad_active = false;
289
331
  }
290
332
  event.preventDefault();
291
333
  }).bind("touchmove mousemove", function(event) { /* $("#touchpadsurface").bind("touchmove" ... */
292
334
  var e = event.originalEvent;
293
335
  var touches = e.touches || [ e ];
294
336
 
295
- //if (!state.touchpad_active) {
337
+ //if (!fingerpoken.touchpad_active) {
296
338
  //event.preventDefault();
297
339
  //return;
298
340
  //}
299
341
 
300
- if (!state.moving) {
342
+ if (!fingerpoken.moving) {
301
343
  /* Start calculating delta offsets now */
302
- state.moving = true;
303
- state.start_x = state.x = touches[0].clientX;
304
- state.start_y = state.y = touches[0].clientY;
344
+ fingerpoken.moving = true;
345
+ fingerpoken.start_x = fingerpoken.x = touches[0].clientX;
346
+ fingerpoken.start_y = fingerpoken.y = touches[0].clientY;
305
347
  /* Skip this event */
306
348
  return;
307
349
  }
308
350
 
309
- state.last_move = e;
351
+ fingerpoken.last_move = e;
310
352
 
311
353
  var output = "";
312
354
  for (var i in touches) {
@@ -322,8 +364,8 @@
322
364
 
323
365
  var x = touches[0].clientX;
324
366
  var y = touches[0].clientY;
325
- var delta_x = (x - state.x);
326
- var delta_y = (y - state.y);
367
+ var delta_x = (x - fingerpoken.x);
368
+ var delta_y = (y - fingerpoken.y);
327
369
 
328
370
  /* Apply acceleration */
329
371
  var sign_x = (delta_x < 0 ? -1 : 1);
@@ -331,15 +373,15 @@
331
373
 
332
374
  /* jQuery Mobile or HTML 'range' inputs don't support floating point.
333
375
  * Hack around it by using larger numbers and compensating. */
334
- var accel = config("fingerpoken/mouse/acceleration", null, 150) / 100.0;
376
+ var accel = fingerpoken.config("fingerpoken/mouse/acceleration", null, 150) / 100.0;
335
377
  output += "Accel: " + accel + "\n";
336
378
 
337
379
  var delta_x = Math.ceil(Math.pow(Math.abs(delta_x), accel) * sign_x);
338
380
  var delta_y = Math.ceil(Math.pow(Math.abs(delta_y), accel) * sign_y);
339
381
  output += "Delta: " + delta_x + ", " + delta_y + "\n";
340
382
 
341
- state.x = x;
342
- state.y = y;
383
+ fingerpoken.x = x;
384
+ fingerpoken.y = y;
343
385
 
344
386
  /* TODO(sissel): Make this a config option */
345
387
  if (e.rotation < -10 || e.rotation > 10) {
@@ -352,48 +394,49 @@
352
394
  return;
353
395
  }
354
396
 
355
- if (touches.length > 1 && !state.dragging) {
397
+ if (touches.length > 1 && !fingerpoken.dragging) {
356
398
  /* Multifinger movement, probably should scroll? */
357
399
  if (Math.abs(delta_y) > 0) {
358
400
  /* Scroll */
359
- state.scroll.y += delta_y;
401
+ fingerpoken.scroll.y += delta_y;
360
402
 
361
403
  /* Don't scroll every time we move, wait until we move enough
362
404
  * that it is more than 10 pixels. */
363
405
  /* TODO(sissel): Make this a config option */
364
- if (Math.abs(state.scroll.y) > 10) {
365
- state.scrolling = true;
366
- state.moving = false;
367
- state.scroll.y = 0;
368
- state.websocket.send(JSON.stringify({
406
+ if (Math.abs(fingerpoken.scroll.y) > 10) {
407
+ fingerpoken.scrolling = true;
408
+ fingerpoken.moving = false;
409
+ /* TODO(sissel): Support horizontal scrolling (buttons 6 and 7) */
410
+ fingerpoken.scroll.y = 0;
411
+ fingerpoken.send({
369
412
  action: "click",
370
413
  button: (delta_y < 0) ? 4 : 5,
371
- }))
414
+ });
372
415
  }
373
416
  } /* if (Math.abs(delta_y) > 0) */
374
417
  } else {
375
418
  /* Only 1 finger, and we aren't dragging. So let's move! */
376
419
  /* TODO(sissel): Refactor these in to fumctions */
377
- var movement = config("fingerpoken/mouse/movement");
420
+ var movement = fingerpoken.config("fingerpoken/mouse/movement");
378
421
  if (movement == "relative") {
379
- state.websocket.send(JSON.stringify({
422
+ fingerpoken.send({
380
423
  action: "mousemove_relative",
381
424
  rel_x: delta_x,
382
425
  rel_y: delta_y
383
- }));
426
+ });
384
427
  } else if (movement == "absolute") {
385
428
  /* Send absolute in terms of percentages. */
386
429
  var content = $(".content:visible");
387
- state.websocket.send(JSON.stringify({
430
+ fingerpoken.send({
388
431
  action: "mousemove_absolute",
389
432
  percent_x: x / content.innerWidth(),
390
433
  percent_y: y / content.innerHeight(),
391
- }));
434
+ });
392
435
  } else if (movement == "vector") {
393
- if (!state.mouse.vectorTimer) {
394
- state.mouse.vectorTimer = setInterval(function() {
395
- var rx = state.x - state.start_x;
396
- var ry = state.y - state.start_y;
436
+ if (!fingerpoken.mouse.vectorTimer) {
437
+ fingerpoken.mouse.vectorTimer = setInterval(function() {
438
+ var rx = fingerpoken.x - fingerpoken.start_x;
439
+ var ry = fingerpoken.y - fingerpoken.start_y;
397
440
  if (rx == 0 || ry == 0) {
398
441
  return;
399
442
  }
@@ -407,13 +450,13 @@
407
450
  ry = Math.ceil(Math.pow(Math.abs(ry), vector_accel) * sign_ry);
408
451
  output += "rx2,ry2 = " + rx + ", " + ry + "\n";
409
452
 
410
- state.websocket.send(JSON.stringify({
453
+ fingerpoken.send({
411
454
  action: "mousemove_relative",
412
455
  rel_x: rx,
413
456
  rel_y: ry
414
- }));
457
+ });
415
458
  }, 15);
416
- } /* if (!state.mouse.vectorTimer) */
459
+ } /* if (!fingerpoken.mouse.vectorTimer) */
417
460
  } /* mouse vector movement */
418
461
  status.html(output)
419
462
  } /* finger movement */
@@ -428,22 +471,38 @@
428
471
  * Mouse click
429
472
  * <a class="command" data-action="click" data-button="button to click">
430
473
  */
431
- $("a.command").bind("touchstart", function(event) {
432
- state.touchelement = this;
433
- }).bind("mousedown", function(event) {
434
- state.touchelement = this;
435
- event.preventDefault();
436
- }).bind("touchmove mousemove", function(event) {
474
+
475
+ var cmd = $("a.command")
476
+ cmd.bind("touchstart", function(event) {
477
+ fingerpoken.touchelement = this;
478
+ }).bind("touchmove", function(event) {
437
479
  event.preventDefault();
438
- }).bind("touchend mouseup", function(event) {
439
- if (state.touchelement == this) {
440
- state.websocket.send(JSON.stringify({
480
+ }).bind("touchend", function(event) {
481
+ if (fingerpoken.touchelement == this) {
482
+ fingerpoken.send({
441
483
  action: $(this).attr("data-action"),
442
484
  key: $(this).attr("data-key"),
443
485
  button: parseInt($(this).attr("data-button")),
444
- }));
486
+ });
445
487
  }
446
488
  });
447
489
 
490
+ if (!TOUCH_SUPPORTED) {
491
+ cmd.bind("mousedown", function(event) {
492
+ fingerpoken.touchelement = this;
493
+ event.preventDefault();
494
+ }).bind("mousemove", function(event) {
495
+ event.preventDefault();
496
+ }).bind("touchend", function(event) {
497
+ if (fingerpoken.touchelement == this) {
498
+ fingerpoken.send({
499
+ action: $(this).attr("data-action"),
500
+ key: $(this).attr("data-key"),
501
+ button: parseInt($(this).attr("data-button")),
502
+ });
503
+ }
504
+ });
505
+ } /* if !TOUCH_SUPPORTED */
506
+
448
507
  }); /* $(document).ready */
449
508
  })();
data/views/index.haml CHANGED
@@ -15,6 +15,8 @@
15
15
  %script{ :src => "/js/jquery.mobile-1.0a2.min.js",
16
16
  :type => "text/javascript" }
17
17
 
18
+ %script{ :src => "/js/2.2.0-crypto-md5.js", :type => "text/javascript" }
19
+ %script{ :src => "/js/2.2.0-hmac-min.js", :type => "text/javascript" }
18
20
  %body
19
21
  / touchpad
20
22
  %div{"data-role" => "page", "data-theme" => "a", "id" => "touchpad"}
@@ -71,5 +73,9 @@
71
73
  / Mouse acceleration
72
74
  %label{:for => "mouse-acceleration"} Acceleration
73
75
  %input{:type => "range", :name => "mouse-acceleration", :id => "mouse-acceleration", :min => 1, :max => 300, :step => "any"}
76
+ / Crypto passphrase
77
+ %br
78
+ %label{:for => "fingerpoken-passphrase"} Passphrase
79
+ %input{:type => "text", :name => "fingerpoken-passphrase", :id => "fingerpoken-passphrase"}
74
80
 
75
81
  %script{ :src => "/js/fingerpoken.js" }
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fingerpoken
3
3
  version: !ruby/object:Gem::Version
4
- hash: 40220208381687
4
+ hash: 19
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 2
9
- - 20110104190832
10
- version: 0.2.20110104190832
8
+ - 3
9
+ - 0
10
+ version: 0.3.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Jordan Sissel
@@ -15,11 +15,11 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-01-04 00:00:00 -08:00
18
+ date: 2011-04-20 00:00:00 -07:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
22
- name: eventmachine-vnc
22
+ name: ffi
23
23
  prerelease: false
24
24
  requirement: &id001 !ruby/object:Gem::Requirement
25
25
  none: false
@@ -33,7 +33,7 @@ dependencies:
33
33
  type: :runtime
34
34
  version_requirements: *id001
35
35
  - !ruby/object:Gem::Dependency
36
- name: ffi
36
+ name: ruby-hmac
37
37
  prerelease: false
38
38
  requirement: &id002 !ruby/object:Gem::Requirement
39
39
  none: false
@@ -60,6 +60,62 @@ dependencies:
60
60
  version: "0"
61
61
  type: :runtime
62
62
  version_requirements: *id003
63
+ - !ruby/object:Gem::Dependency
64
+ name: em-websocket
65
+ prerelease: false
66
+ requirement: &id004 !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ hash: 3
72
+ segments:
73
+ - 0
74
+ version: "0"
75
+ type: :runtime
76
+ version_requirements: *id004
77
+ - !ruby/object:Gem::Dependency
78
+ name: async_sinatra
79
+ prerelease: false
80
+ requirement: &id005 !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ hash: 3
86
+ segments:
87
+ - 0
88
+ version: "0"
89
+ type: :runtime
90
+ version_requirements: *id005
91
+ - !ruby/object:Gem::Dependency
92
+ name: haml
93
+ prerelease: false
94
+ requirement: &id006 !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ hash: 3
100
+ segments:
101
+ - 0
102
+ version: "0"
103
+ type: :runtime
104
+ version_requirements: *id006
105
+ - !ruby/object:Gem::Dependency
106
+ name: eventmachine-vnc
107
+ prerelease: false
108
+ requirement: &id007 !ruby/object:Gem::Requirement
109
+ none: false
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ hash: 3
114
+ segments:
115
+ - 0
116
+ version: "0"
117
+ type: :runtime
118
+ version_requirements: *id007
63
119
  description: fingerpoken - turns your ipad/itouch/iphone into a remote touchpad, keyboard, etc
64
120
  email: jls@semicomplete.com
65
121
  executables:
@@ -69,25 +125,28 @@ extensions: []
69
125
  extra_rdoc_files: []
70
126
 
71
127
  files:
72
- - lib/fingerpoken/xdo.rb
73
128
  - lib/fingerpoken/target.rb
74
- - lib/fingerpoken/tivo.rb
75
129
  - lib/fingerpoken/vnc.rb
76
- - public/reset.css
130
+ - lib/fingerpoken/tivo.rb
131
+ - lib/fingerpoken/xdo.rb
132
+ - public/js/jquery.mobile-1.0a2.min.js
133
+ - public/js/CRYPTO_JS_LICENSE
77
134
  - public/js/fingerpoken.js
78
135
  - public/js/jquery.min.js
79
- - public/js/jquery.mobile-1.0a2.min.js
80
- - public/images/form-radio-on.png
136
+ - public/js/2.2.0-hmac-min.js
137
+ - public/js/2.2.0-crypto-md5.js
138
+ - public/jquery.mobile-1.0a2.min.css
139
+ - public/reset.css
81
140
  - public/images/form-check-on.png
82
- - public/images/icons-36-white.png
141
+ - public/images/form-radio-off.png
83
142
  - public/images/icons-36-black.png
84
- - public/images/icon-search-black.png
85
143
  - public/images/icons-18-white.png
144
+ - public/images/form-radio-on.png
86
145
  - public/images/ajax-loader.png
87
- - public/images/form-radio-off.png
88
- - public/images/icons-18-black.png
146
+ - public/images/icon-search-black.png
147
+ - public/images/icons-36-white.png
89
148
  - public/images/form-check-off.png
90
- - public/jquery.mobile-1.0a2.min.css
149
+ - public/images/icons-18-black.png
91
150
  - views/style.sass
92
151
  - views/index.haml
93
152
  - bin/fingerpoken.rb