fingerpoken 0.2.20110101195735 → 0.2.20110102034531
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/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
|