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 +9 -2
- data/lib/fingerpoken/target.rb +53 -3
- data/lib/fingerpoken/vnc.rb +49 -6
- data/public/js/2.2.0-crypto-md5.js +11 -0
- data/public/js/2.2.0-hmac-min.js +1 -0
- data/public/js/CRYPTO_JS_LICENSE +9 -0
- data/public/js/fingerpoken.js +201 -142
- data/views/index.haml +6 -0
- metadata +76 -17
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
|
-
|
94
|
+
main(ARGV)
|
data/lib/fingerpoken/target.rb
CHANGED
@@ -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)
|
data/lib/fingerpoken/vnc.rb
CHANGED
@@ -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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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.
|
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)}})();
|
data/public/js/fingerpoken.js
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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 (
|
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 + " / " +
|
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
|
-
|
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
|
-
//
|
247
|
+
//fingerpoken.accel = e.accelerationIncludingGravity;
|
207
248
|
|
208
249
|
/* Trim shakes */
|
209
|
-
//if (Math.abs(
|
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: " +
|
213
|
-
//
|
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(
|
216
|
-
//rel_y: Math.ceil(
|
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
|
-
|
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
|
-
|
233
|
-
switch (
|
234
|
-
case 1:
|
235
|
-
case 2:
|
236
|
-
case 3:
|
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 -
|
281
|
+
if ((now - fingerpoken.last_click) < 170) {
|
241
282
|
/* Start dragging */
|
242
|
-
|
283
|
+
fingerpoken.send({
|
243
284
|
action: "mousedown",
|
244
|
-
button:
|
245
|
-
})
|
246
|
-
|
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 (
|
254
|
-
clearInterval(
|
255
|
-
|
294
|
+
if (fingerpoken.mouse.vectorTimer) {
|
295
|
+
clearInterval(fingerpoken.mouse.vectorTimer);
|
296
|
+
fingerpoken.mouse.vectorTimer = null;
|
256
297
|
}
|
257
298
|
|
258
|
-
if (
|
259
|
-
|
299
|
+
if (fingerpoken.dragging) {
|
300
|
+
fingerpoken.send({
|
260
301
|
action: "mouseup",
|
261
|
-
button:
|
262
|
-
})
|
263
|
-
|
302
|
+
button: fingerpoken.button,
|
303
|
+
});
|
304
|
+
fingerpoken.dragging = false;
|
264
305
|
} else {
|
265
|
-
if (
|
266
|
-
var e =
|
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 (
|
314
|
+
} else if (fingerpoken.scrolling) {
|
274
315
|
/* nothing for now */
|
275
316
|
} else {
|
276
317
|
/* No movement, click! */
|
277
318
|
status.html("Click!");
|
278
|
-
|
319
|
+
console.log("click");
|
320
|
+
fingerpoken.send({
|
279
321
|
action: "click",
|
280
|
-
button:
|
281
|
-
})
|
282
|
-
|
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
|
-
|
287
|
-
|
288
|
-
|
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 (!
|
337
|
+
//if (!fingerpoken.touchpad_active) {
|
296
338
|
//event.preventDefault();
|
297
339
|
//return;
|
298
340
|
//}
|
299
341
|
|
300
|
-
if (!
|
342
|
+
if (!fingerpoken.moving) {
|
301
343
|
/* Start calculating delta offsets now */
|
302
|
-
|
303
|
-
|
304
|
-
|
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
|
-
|
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 -
|
326
|
-
var delta_y = (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
|
-
|
342
|
-
|
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 && !
|
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
|
-
|
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(
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
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
|
-
|
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
|
-
|
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 (!
|
394
|
-
|
395
|
-
var rx =
|
396
|
-
var ry =
|
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
|
-
|
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 (!
|
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
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
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
|
439
|
-
if (
|
440
|
-
|
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:
|
4
|
+
hash: 19
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 0.
|
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-
|
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:
|
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:
|
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
|
-
-
|
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/
|
80
|
-
- public/
|
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/
|
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/
|
88
|
-
- public/images/icons-
|
146
|
+
- public/images/icon-search-black.png
|
147
|
+
- public/images/icons-36-white.png
|
89
148
|
- public/images/form-check-off.png
|
90
|
-
- public/
|
149
|
+
- public/images/icons-18-black.png
|
91
150
|
- views/style.sass
|
92
151
|
- views/index.haml
|
93
152
|
- bin/fingerpoken.rb
|