fingerpoken 0.2.20110104190832 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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