fingerpoken 0.2.20110101195735 → 0.2.20110102034531
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/fingerpoken/target.rb +2 -0
- data/lib/fingerpoken/vnc.rb +16 -2
- data/lib/fingerpoken/xdo.rb +44 -0
- data/public/js/fingerpoken.js +127 -92
- data/views/index.haml +3 -1
- data/views/style.sass +7 -3
- metadata +4 -4
data/lib/fingerpoken/target.rb
CHANGED
@@ -15,6 +15,8 @@ class FingerPoken::Target
|
|
15
15
|
response = case request["action"]
|
16
16
|
when "mousemove_relative"
|
17
17
|
mousemove_relative(request["rel_x"], request["rel_y"])
|
18
|
+
when "mousemove_absolute"
|
19
|
+
mousemove_absolute(request["percent_x"], request["percent_y"])
|
18
20
|
when "move_end"
|
19
21
|
move_end()
|
20
22
|
when "click"
|
data/lib/fingerpoken/vnc.rb
CHANGED
@@ -5,6 +5,8 @@ require "fingerpoken/target"
|
|
5
5
|
class FingerPoken::Target::VNC < FingerPoken::Target
|
6
6
|
attr_accessor :x
|
7
7
|
attr_accessor :y
|
8
|
+
attr_accessor :screen_x
|
9
|
+
attr_accessor :screen_y
|
8
10
|
attr_accessor :buttonmask
|
9
11
|
|
10
12
|
def initialize(config)
|
@@ -48,6 +50,16 @@ class FingerPoken::Target::VNC < FingerPoken::Target
|
|
48
50
|
return nil
|
49
51
|
end
|
50
52
|
|
53
|
+
def mousemove_absolute(px, py)
|
54
|
+
# Edges may be hard to hit on some devices, so inflate things a bit.
|
55
|
+
xbuf = @screen_x * 0.1
|
56
|
+
ybuf = @screen_y * 0.1
|
57
|
+
@x = (((@screen_x + xbuf) * px) - (xbuf / 2)).to_i
|
58
|
+
@y = (((@screen_y + ybuf) * py) - (ybuf / 2)).to_i
|
59
|
+
update
|
60
|
+
return nil
|
61
|
+
end
|
62
|
+
|
51
63
|
def mousedown(button)
|
52
64
|
button = (1 << (button.to_i - 1))
|
53
65
|
return if @buttonmask & button != 0
|
@@ -72,10 +84,12 @@ class FingerPoken::Target::VNC < FingerPoken::Target
|
|
72
84
|
end
|
73
85
|
|
74
86
|
def ready
|
75
|
-
@target.
|
87
|
+
@target.screen_x = @screen_width
|
88
|
+
@target.screen_y = @screen_height
|
89
|
+
@target.buttonmask = 0
|
76
90
|
@target.x = (@screen_width / 2).to_i
|
77
91
|
@target.y = (@screen_height / 2).to_i
|
78
|
-
@target.
|
92
|
+
@target.register
|
79
93
|
end
|
80
94
|
end # class VNCClient
|
81
95
|
end
|
data/lib/fingerpoken/xdo.rb
CHANGED
@@ -17,20 +17,64 @@ class FingerPoken::Target::Xdo < FingerPoken::Target
|
|
17
17
|
attach_function :xdo_mouseup, [:pointer, :long, :int], :int
|
18
18
|
attach_function :xdo_type, [:pointer, :long, :string, :long], :int
|
19
19
|
attach_function :xdo_keysequence, [:pointer, :long, :string, :long], :int
|
20
|
+
attach_function :xdo_get_window_size, [:pointer, :long, :pointer, :pointer], :int
|
21
|
+
attach_function :xdo_window_search, [:pointer, :pointer, :pointer, :pointer], :int
|
20
22
|
end
|
21
23
|
|
24
|
+
class XdoSearch < FFI::Struct
|
25
|
+
layout :title, :pointer,
|
26
|
+
:winclass, :pointer,
|
27
|
+
:winclassname, :pointer,
|
28
|
+
:winname, :pointer,
|
29
|
+
:pid, :int,
|
30
|
+
:max_depth, :long,
|
31
|
+
:only_visible, :int,
|
32
|
+
:screen, :int,
|
33
|
+
:require, :int,
|
34
|
+
:searchmask, :uint,
|
35
|
+
:desktop, :long
|
36
|
+
end # class XdoSearch
|
37
|
+
|
22
38
|
def initialize(config)
|
23
39
|
super(config)
|
24
40
|
@xdo = LibXdo::xdo_new(nil)
|
25
41
|
if @xdo.null?
|
26
42
|
raise "xdo_new failed"
|
27
43
|
end
|
44
|
+
|
45
|
+
search = XdoSearch.new
|
46
|
+
search[:searchmask] = 1 << 2 # SEARCH_NAME, from xdo.h
|
47
|
+
search[:max_depth] = 0
|
48
|
+
search[:winname] = FFI::MemoryPointer.new(:char, 3)
|
49
|
+
search[:winname].put_string(0, ".*")
|
50
|
+
ptr_nwindows = FFI::MemoryPointer.new(:ulong, 1)
|
51
|
+
ptr_winlist = FFI::MemoryPointer.new(:pointer, 1)
|
52
|
+
LibXdo::xdo_window_search(@xdo, search, ptr_winlist, ptr_nwindows)
|
53
|
+
nwindows = ptr_nwindows.read_long
|
54
|
+
@rootwin = ptr_winlist.read_pointer.read_array_of_long(nwindows)[0]
|
55
|
+
|
56
|
+
ptr_x = FFI::MemoryPointer.new(:int, 1)
|
57
|
+
ptr_y = FFI::MemoryPointer.new(:int, 1)
|
58
|
+
|
59
|
+
LibXdo::xdo_get_window_size(@xdo, @rootwin, ptr_x, ptr_y)
|
60
|
+
@screen_x = ptr_x.read_int
|
61
|
+
@screen_y = ptr_y.read_int
|
28
62
|
end
|
29
63
|
|
30
64
|
def mousemove_relative(x, y)
|
31
65
|
return LibXdo::xdo_mousemove_relative(@xdo, x, y)
|
32
66
|
end
|
33
67
|
|
68
|
+
def mousemove_absolute(px, py)
|
69
|
+
# Edges may be hard to hit on some devices, so inflate things a bit.
|
70
|
+
xbuf = @screen_x * 0.1
|
71
|
+
ybuf = @screen_y * 0.1
|
72
|
+
x = (((@screen_x + xbuf) * px) - (xbuf / 2)).to_i
|
73
|
+
y = (((@screen_y + ybuf) * py) - (ybuf / 2)).to_i
|
74
|
+
|
75
|
+
return LibXdo::xdo_mousemove(@xdo, x, y, 0)
|
76
|
+
end
|
77
|
+
|
34
78
|
def click(button)
|
35
79
|
return LibXdo::xdo_click(@xdo, 0, button.to_i)
|
36
80
|
end
|
data/public/js/fingerpoken.js
CHANGED
@@ -3,6 +3,90 @@
|
|
3
3
|
|
4
4
|
$(document).ready(function() {
|
5
5
|
var status = $("#status");
|
6
|
+
var keyboard = $('#keyboard');
|
7
|
+
var keyboard_button = keyboard.prev('a');
|
8
|
+
keyboard.width(keyboard_button.width());
|
9
|
+
keyboard.height(keyboard_button.height());
|
10
|
+
/* TODO(sissel): get the computed width (margin, padding, width) */
|
11
|
+
keyboard.css('margin-left', '-' + keyboard_button.width() + 'px');
|
12
|
+
keyboard.show();
|
13
|
+
|
14
|
+
keyboard.bind("focus", function() {
|
15
|
+
/* move the textarea away so we don't see the caret */
|
16
|
+
keyboard.css('margin-left', '-10000px');
|
17
|
+
state.keyboard = true;
|
18
|
+
$(window).triggerHandler("resize");
|
19
|
+
});
|
20
|
+
keyboard.bind("blur", function(){
|
21
|
+
keyboard.css('margin-left', '-' + keyboard_button.width() + 'px');
|
22
|
+
state.keyboard = false;
|
23
|
+
$(window).triggerHandler("resize");
|
24
|
+
});
|
25
|
+
keyboard.bind("keypress", function(event) {
|
26
|
+
var e = event.originalEvent;
|
27
|
+
var key = e.charCode;
|
28
|
+
if (!key) {
|
29
|
+
key = (e.keyCode ? e.keyCode : e.which);
|
30
|
+
}
|
31
|
+
state.websocket.send(JSON.stringify({
|
32
|
+
action: "log",
|
33
|
+
shift: e.shiftKey,
|
34
|
+
char: e.charCode,
|
35
|
+
ctrl: e.ctrlKey,
|
36
|
+
meta: e.ctrlKey,
|
37
|
+
}));
|
38
|
+
state.websocket.send(JSON.stringify({
|
39
|
+
action: "keypress",
|
40
|
+
key: key,
|
41
|
+
shift: e.shiftKey,
|
42
|
+
}));
|
43
|
+
|
44
|
+
/* Only prevent default if we're not backspace,
|
45
|
+
* this lets 'backspace' do keyrepeat. */
|
46
|
+
if (key != 8) {
|
47
|
+
event.preventDefault();
|
48
|
+
}
|
49
|
+
}).bind("change", function(event) {
|
50
|
+
/* Skip empty changes */
|
51
|
+
if (keyboard.val() == "") {
|
52
|
+
return;
|
53
|
+
}
|
54
|
+
|
55
|
+
state.websocket.send(JSON.stringify({
|
56
|
+
action: "type",
|
57
|
+
string: keyboard.val(),
|
58
|
+
}));
|
59
|
+
|
60
|
+
/* Clear the field */
|
61
|
+
keyboard.val("");
|
62
|
+
});
|
63
|
+
|
64
|
+
keyboard.bind("keyup", function(event) {
|
65
|
+
var e = event.originalEvent;
|
66
|
+
state.websocket.send(JSON.stringify({
|
67
|
+
action: "log",
|
68
|
+
shift: e.shiftKey,
|
69
|
+
char: e.charCode,
|
70
|
+
key: e.which,
|
71
|
+
ctrl: e.ctrlKey,
|
72
|
+
meta: e.ctrlKey,
|
73
|
+
}));
|
74
|
+
|
75
|
+
var key = (e.keyCode ? e.keyCode : e.which);
|
76
|
+
if (key >= 32 && key <= 127) {
|
77
|
+
/* skip printable keys (a-z, etc) */
|
78
|
+
return;
|
79
|
+
}
|
80
|
+
|
81
|
+
state.websocket.send(JSON.stringify({
|
82
|
+
action: "keypress",
|
83
|
+
key: key,
|
84
|
+
shift: e.shiftKey,
|
85
|
+
}));
|
86
|
+
|
87
|
+
event.preventDefault();
|
88
|
+
});
|
89
|
+
|
6
90
|
var config = function (key, value, default_value) {
|
7
91
|
if (value) {
|
8
92
|
status.html("config[" + key + "] = " + value);
|
@@ -22,7 +106,9 @@
|
|
22
106
|
dragging: false,
|
23
107
|
width: window.innerWidth,
|
24
108
|
height: window.innerHeight,
|
25
|
-
key: undefined,
|
109
|
+
key: undefined, /* TODO(sissel): unused? */
|
110
|
+
keyboard: false,
|
111
|
+
touchpad_active: false,
|
26
112
|
mouse: { },
|
27
113
|
scroll: {
|
28
114
|
y: 0,
|
@@ -55,7 +141,6 @@
|
|
55
141
|
$("input[name = \"mouse-acceleration\"]")
|
56
142
|
.bind("change", function(event) {
|
57
143
|
config("fingerpoken/mouse/acceleration", parseInt(event.target.value));
|
58
|
-
//status.html(event.target.value);
|
59
144
|
}).val(config("fingerpoken/mouse/acceleration")).change();
|
60
145
|
|
61
146
|
/* Changing orientation sometimes leaves the viewport
|
@@ -63,7 +148,6 @@
|
|
63
148
|
* Also, we want to make the content size full height. */
|
64
149
|
$(window).bind("orientationchange resize pageshow", function(event) {
|
65
150
|
scroll(0, 0);
|
66
|
-
console.log(window.orientation);
|
67
151
|
|
68
152
|
var header = $(".header:visible");
|
69
153
|
var footer = $(".footer:visible");
|
@@ -74,6 +158,20 @@
|
|
74
158
|
|
75
159
|
/* Trim margin/border/padding height */
|
76
160
|
content_height -= (content.outerHeight() - content.height());
|
161
|
+
|
162
|
+
/* TODO(sissel): Make this special handling only for iphones.
|
163
|
+
* http://developer.apple.com/library/safari/#documentation/appleapplications/reference/safariwebcontent/UsingtheViewport/UsingtheViewport.html
|
164
|
+
*/
|
165
|
+
if (state.keyboard) {
|
166
|
+
if (window.orientation == 90 || window.orientation == -90) {
|
167
|
+
content_height -= 162; /* landscape orientation keyboard */
|
168
|
+
content_height -= 32; /* "form assistant" aka FormFill, this height is undocumented. */
|
169
|
+
} else {
|
170
|
+
content_height -= 216; /* portrait orientation keyboard */
|
171
|
+
content_height -= 44; /* "form assistant" aka FormFill */
|
172
|
+
}
|
173
|
+
}
|
174
|
+
status.html("Resize / " + window.orientation + " / " + state.keyboard + " / " + content_height);
|
77
175
|
content.height(content_height);
|
78
176
|
});
|
79
177
|
|
@@ -121,11 +219,11 @@
|
|
121
219
|
|
122
220
|
|
123
221
|
/* TODO(sissel): add mousedown/mousemove/mouseup support */
|
124
|
-
|
125
|
-
$("#area").bind("touchstart", function(event) {
|
126
|
-
event.preventDefault();
|
222
|
+
$("#area").bind("touchstart mousedown", function(event) {
|
127
223
|
var e = event.originalEvent;
|
128
|
-
|
224
|
+
state.touchpad_active = true;
|
225
|
+
/* if no 'touches', use the event itself, one finger/mouse */
|
226
|
+
var touches = e.touches || [ e ];
|
129
227
|
var output = "Start: " + touches[0].clientX + "," + touches[0].clientY + "\n";
|
130
228
|
output += "Fingers: " + touches.length + "\n";
|
131
229
|
status.html(output);
|
@@ -147,9 +245,10 @@
|
|
147
245
|
}))
|
148
246
|
state.dragging = true;
|
149
247
|
}
|
150
|
-
|
248
|
+
event.preventDefault();
|
249
|
+
}).bind("touchend mouseup", function(event) { /* $("#touchpadsurface").bind("touchend" ... */
|
151
250
|
var e = event.originalEvent;
|
152
|
-
var touches = e.touches;
|
251
|
+
var touches = e.touches || [ e ];
|
153
252
|
|
154
253
|
if (state.mouse.vectorTimer) {
|
155
254
|
clearInterval(state.mouse.vectorTimer);
|
@@ -171,83 +270,6 @@
|
|
171
270
|
}
|
172
271
|
|
173
272
|
status.html(r);
|
174
|
-
if (r > 75 && r < 105) {
|
175
|
-
/* Activate the keyboard when there's a ~90-degree rotation*/
|
176
|
-
var keyboard = $("<textarea id='keyboard' rows='10'></textarea>");
|
177
|
-
keyboard.css("width", "100%");
|
178
|
-
keyboard.css("height", "100%");
|
179
|
-
status.html("");
|
180
|
-
keyboard.appendTo(status).focus();
|
181
|
-
keyboard.bind("keypress", function(event) {
|
182
|
-
var e = event.originalEvent;
|
183
|
-
var key = e.charCode;
|
184
|
-
console.log(key);
|
185
|
-
if (!key) {
|
186
|
-
key = (e.keyCode ? e.keyCode : e.which);
|
187
|
-
}
|
188
|
-
state.websocket.send(JSON.stringify({
|
189
|
-
action: "log",
|
190
|
-
shift: e.shiftKey,
|
191
|
-
char: e.charCode,
|
192
|
-
ctrl: e.ctrlKey,
|
193
|
-
meta: e.ctrlKey,
|
194
|
-
}));
|
195
|
-
state.websocket.send(JSON.stringify({
|
196
|
-
action: "keypress",
|
197
|
-
key: key,
|
198
|
-
shift: e.shiftKey,
|
199
|
-
}));
|
200
|
-
|
201
|
-
e.preventDefault();
|
202
|
-
}).bind("change", function(event) {
|
203
|
-
/* Skip empty changes */
|
204
|
-
if (keyboard.val() == "") {
|
205
|
-
return;
|
206
|
-
}
|
207
|
-
|
208
|
-
state.websocket.send(JSON.stringify({
|
209
|
-
action: "type",
|
210
|
-
string: keyboard.val(),
|
211
|
-
}));
|
212
|
-
|
213
|
-
/* Clear the field */
|
214
|
-
keyboard.val("");
|
215
|
-
});
|
216
|
-
|
217
|
-
keyboard.bind("keyup", function(event) {
|
218
|
-
var e = event.originalEvent;
|
219
|
-
state.websocket.send(JSON.stringify({
|
220
|
-
action: "log",
|
221
|
-
shift: e.shiftKey,
|
222
|
-
char: e.charCode,
|
223
|
-
key: e.which,
|
224
|
-
ctrl: e.ctrlKey,
|
225
|
-
meta: e.ctrlKey,
|
226
|
-
}));
|
227
|
-
|
228
|
-
console.log(key);
|
229
|
-
var key = (e.keyCode ? e.keyCode : e.which);
|
230
|
-
if (key >= 32 && key <= 127) {
|
231
|
-
/* skip printable keys (a-z, etc) */
|
232
|
-
return;
|
233
|
-
}
|
234
|
-
|
235
|
-
state.websocket.send(JSON.stringify({
|
236
|
-
action: "keypress",
|
237
|
-
key: key,
|
238
|
-
shift: e.shiftKey,
|
239
|
-
}));
|
240
|
-
|
241
|
-
e.preventDefault();
|
242
|
-
});
|
243
|
-
//status.html("<textarea id='keyboard'></textarea>");
|
244
|
-
//$("#keyboard").focus();
|
245
|
-
} else { /* Otherwise, we didn't rotate */
|
246
|
-
state.websocket.send(JSON.stringify({
|
247
|
-
action: "move_end",
|
248
|
-
now: (new Date()),
|
249
|
-
}));
|
250
|
-
}
|
251
273
|
} else if (state.scrolling) {
|
252
274
|
/* nothing for now */
|
253
275
|
} else {
|
@@ -260,15 +282,21 @@
|
|
260
282
|
state.last_click = (new Date()).getTime();
|
261
283
|
}
|
262
284
|
}
|
263
|
-
if (touches.length == 0) {
|
285
|
+
if (touches.length == 0 || !e.touches) {
|
264
286
|
state.moving = false;
|
265
287
|
state.scrolling = false;
|
288
|
+
state.touchpad_active = false;
|
266
289
|
}
|
267
290
|
event.preventDefault();
|
268
|
-
}).bind("touchmove", function(event) { /* $("#touchpadsurface").bind("touchmove" ... */
|
291
|
+
}).bind("touchmove mousemove", function(event) { /* $("#touchpadsurface").bind("touchmove" ... */
|
269
292
|
var e = event.originalEvent;
|
270
|
-
var touches = e.touches;
|
271
|
-
|
293
|
+
var touches = e.touches || [ e ];
|
294
|
+
|
295
|
+
//if (!state.touchpad_active) {
|
296
|
+
//event.preventDefault();
|
297
|
+
//return;
|
298
|
+
//}
|
299
|
+
|
272
300
|
if (!state.moving) {
|
273
301
|
/* Start calculating delta offsets now */
|
274
302
|
state.moving = true;
|
@@ -348,12 +376,19 @@
|
|
348
376
|
/* TODO(sissel): Refactor these in to fumctions */
|
349
377
|
var movement = config("fingerpoken/mouse/movement");
|
350
378
|
if (movement == "relative") {
|
351
|
-
status.html(output);
|
352
379
|
state.websocket.send(JSON.stringify({
|
353
380
|
action: "mousemove_relative",
|
354
381
|
rel_x: delta_x,
|
355
382
|
rel_y: delta_y
|
356
383
|
}));
|
384
|
+
} else if (movement == "absolute") {
|
385
|
+
/* Send absolute in terms of percentages. */
|
386
|
+
var content = $(".content:visible");
|
387
|
+
state.websocket.send(JSON.stringify({
|
388
|
+
action: "mousemove_absolute",
|
389
|
+
percent_x: x / content.innerWidth(),
|
390
|
+
percent_y: y / content.innerHeight(),
|
391
|
+
}));
|
357
392
|
} else if (movement == "vector") {
|
358
393
|
if (!state.mouse.vectorTimer) {
|
359
394
|
state.mouse.vectorTimer = setInterval(function() {
|
@@ -371,7 +406,6 @@
|
|
371
406
|
rx = Math.ceil(Math.pow(Math.abs(rx), vector_accel) * sign_rx);
|
372
407
|
ry = Math.ceil(Math.pow(Math.abs(ry), vector_accel) * sign_ry);
|
373
408
|
output += "rx2,ry2 = " + rx + ", " + ry + "\n";
|
374
|
-
status.html(output)
|
375
409
|
|
376
410
|
state.websocket.send(JSON.stringify({
|
377
411
|
action: "mousemove_relative",
|
@@ -381,6 +415,7 @@
|
|
381
415
|
}, 15);
|
382
416
|
} /* if (!state.mouse.vectorTimer) */
|
383
417
|
} /* mouse vector movement */
|
418
|
+
status.html(output)
|
384
419
|
} /* finger movement */
|
385
420
|
}); /* $("#touchpadsurface").bind( ... )*/
|
386
421
|
|
data/views/index.haml
CHANGED
@@ -24,11 +24,13 @@
|
|
24
24
|
%a{:href => "javascript:window.location.reload()", "data-role" => "button", "data-inline" => true} Reload
|
25
25
|
%div.content{"data-role" => "content"}
|
26
26
|
#area
|
27
|
-
|
27
|
+
%pre#status
|
28
28
|
|
29
29
|
%div.footer{"data-role" => "footer"}
|
30
30
|
%span.left
|
31
31
|
%a{:href => "#commands", "data-role" => "button", "data-rel" => "dialog", "data-transition" => "pop"} Commands
|
32
|
+
%a{:href => "#keyboard", "data-role" => "button", "data-action" => "keyboard"} Keyboard
|
33
|
+
%textarea#keyboard{:autocapitalize => "off", :autocorrect => "off"}
|
32
34
|
%span.right
|
33
35
|
%a.command{:href => "#", "data-role" => "button", "data-action" => "click", "data-button" => "1"} Left
|
34
36
|
%a.command{:href => "#", "data-role" => "button", "data-action" => "click", "data-button" => "3"} Right
|
data/views/style.sass
CHANGED
@@ -20,6 +20,7 @@ html, body
|
|
20
20
|
color: orange
|
21
21
|
|
22
22
|
.content
|
23
|
+
-webkit-transition: height 0.1s linear
|
23
24
|
h1.status
|
24
25
|
font-size: 200%
|
25
26
|
color: white
|
@@ -37,8 +38,11 @@ html, body
|
|
37
38
|
padding: 10px
|
38
39
|
|
39
40
|
#keyboard
|
40
|
-
|
41
|
-
|
41
|
+
border: 0
|
42
|
+
background: transparent
|
42
43
|
margin: 0
|
43
44
|
padding: 0
|
44
|
-
|
45
|
+
position: absolute
|
46
|
+
outline: none
|
47
|
+
resize: none
|
48
|
+
display: none
|
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: 40220204069073
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 2
|
9
|
-
-
|
10
|
-
version: 0.2.
|
9
|
+
- 20110102034531
|
10
|
+
version: 0.2.20110102034531
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Jordan Sissel
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-01-
|
18
|
+
date: 2011-01-02 00:00:00 -08:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|