thinclient 0.0.4
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/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/bin/ImageMatch.exe +0 -0
- data/bin/opencv_core248.dll +0 -0
- data/bin/opencv_highgui248.dll +0 -0
- data/bin/opencv_imgproc248.dll +0 -0
- data/lib/thinclient.rb +9 -0
- data/lib/thinclient/error.rb +8 -0
- data/lib/thinclient/image.rb +101 -0
- data/lib/thinclient/input.rb +60 -0
- data/lib/thinclient/keyboard.rb +102 -0
- data/lib/thinclient/keycode.rb +119 -0
- data/lib/thinclient/mouse.rb +318 -0
- data/lib/thinclient/mylogger.rb +36 -0
- data/lib/thinclient/version.rb +3 -0
- data/thinclient.gemspec +19 -0
- metadata +87 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2014 wangbaomi
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Thinclient
|
|
2
|
+
|
|
3
|
+
TODO: Write a gem description
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add this line to your application's Gemfile:
|
|
8
|
+
|
|
9
|
+
gem 'thinclient'
|
|
10
|
+
|
|
11
|
+
And then execute:
|
|
12
|
+
|
|
13
|
+
$ bundle
|
|
14
|
+
|
|
15
|
+
Or install it yourself as:
|
|
16
|
+
|
|
17
|
+
$ gem install thinclient
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
TODO: Write usage instructions here
|
|
22
|
+
|
|
23
|
+
## Contributing
|
|
24
|
+
|
|
25
|
+
1. Fork it
|
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/ImageMatch.exe
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
data/lib/thinclient.rb
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
module ThinClient
|
|
2
|
+
class Image
|
|
3
|
+
# 图片匹配程序ImageMatch.exe.如果没有找到则返回NO_IMAGEMATCH
|
|
4
|
+
def self.setImageMatch(path = "ImageMatch.exe")
|
|
5
|
+
@ImageMatch = path.nil? ? "ImageMatch.exe" : path
|
|
6
|
+
if not File.exist?(@ImageMatch)
|
|
7
|
+
print("ImageMatch.exe Not Found\n")
|
|
8
|
+
return ThinClient::Error::NO_IMAGEMATCH
|
|
9
|
+
end
|
|
10
|
+
return ThinClient::Error::SUCCESS
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# 查找图片坐标.任何错误导致的查找失败都返回坐标(0,0),查找成功返回具体坐标和查找时匹配的图片名称
|
|
14
|
+
def self.getPosition(img1, img2, similar = 0.8)
|
|
15
|
+
position = {:X => 0, :Y => 0, :resultImg => ""}
|
|
16
|
+
if (ThinClient::Error::SUCCESS != setImageMatch())
|
|
17
|
+
return position
|
|
18
|
+
end
|
|
19
|
+
if not File.exist?(img1)
|
|
20
|
+
print("Image #{img1} Not Found\n")
|
|
21
|
+
return position
|
|
22
|
+
end
|
|
23
|
+
if not File.exist?(img2)
|
|
24
|
+
print("Image #{img2} Not Found\n")
|
|
25
|
+
return position
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
info = `#{@ImageMatch} #{img1} #{img2} #{similar}`
|
|
29
|
+
if 0 === info.split(/,/)[0]
|
|
30
|
+
print("#{img1} and #{img2} Not Match\n")
|
|
31
|
+
return position
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
position[:X] = info.split(/,/)[0].to_i
|
|
35
|
+
position[:Y] = info.split(/,/)[1].to_i
|
|
36
|
+
position[:resultImg] = info.split(/,/)[2].to_s
|
|
37
|
+
return position
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# 在当前屏幕中查找指定图片的坐标
|
|
41
|
+
def self.getPositionOnScreen(img, similar = 0.8)
|
|
42
|
+
if false == screenCap("tmp.png")
|
|
43
|
+
return false
|
|
44
|
+
end
|
|
45
|
+
return getPosition("tmp.png", img, similar)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# 查找图片是否存在
|
|
49
|
+
def self.include?(img1, img2, similar = 0.8)
|
|
50
|
+
position = getPosition(img1, img2, similar)
|
|
51
|
+
if (position[:X] != 0 && position[:Y] != 0)
|
|
52
|
+
return true
|
|
53
|
+
end
|
|
54
|
+
return false
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# 在当前屏幕中是否存在指定图片
|
|
58
|
+
def self.includeOnScreen?(img, similar = 0.8)
|
|
59
|
+
if false == screenCap("tmp.png")
|
|
60
|
+
return false
|
|
61
|
+
end
|
|
62
|
+
return include?("tmp.png", img, similar)
|
|
63
|
+
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# 截图保存
|
|
67
|
+
def self.screenCap(name = "tmp.png")
|
|
68
|
+
tempFile = "/sdcard/screen.png"
|
|
69
|
+
system("adb shell screencap -p #{tempFile}")
|
|
70
|
+
system("adb pull #{tempFile} #{name}")
|
|
71
|
+
unless File.exist?(name)
|
|
72
|
+
print "Screencap Fail"
|
|
73
|
+
return false
|
|
74
|
+
end
|
|
75
|
+
return true
|
|
76
|
+
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# 通过鼠标点击图片(无论是瘦客户机的还是虚拟桌面的都可以点击到)
|
|
80
|
+
def self.mouseClick(img, similar = 0.8)
|
|
81
|
+
position = getPositionOnScreen(img, similar)
|
|
82
|
+
if (position[:X] === 0 || position[:Y] === 0)
|
|
83
|
+
return false
|
|
84
|
+
end
|
|
85
|
+
#print ("position:#{position}\n")
|
|
86
|
+
Mouse.moveTo(position[:X], position[:Y])
|
|
87
|
+
Mouse.click()
|
|
88
|
+
return true
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# 触摸图片(这个操作无法触摸到虚拟桌面内的图片)
|
|
92
|
+
def self.tap(img, similar = 0.8)
|
|
93
|
+
position = getPositionOnScreen(img, similar)
|
|
94
|
+
if (position[:X] === 0 || position[:Y] === 0)
|
|
95
|
+
return false
|
|
96
|
+
end
|
|
97
|
+
#print ("position:#{position}\n")
|
|
98
|
+
system("adb shell input tap #{position[:X]} #{position[:Y]}")
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
module ThinClient
|
|
2
|
+
class Input
|
|
3
|
+
# input text
|
|
4
|
+
def self.text(str)
|
|
5
|
+
begin
|
|
6
|
+
str =str.to_s
|
|
7
|
+
strings = str.split(/ /)
|
|
8
|
+
strings.each{ |string|
|
|
9
|
+
system("adb shell input text #{string}")
|
|
10
|
+
Keyboard.sendKeys("space")
|
|
11
|
+
}
|
|
12
|
+
rescue => ex
|
|
13
|
+
Log.error("#{ex}")
|
|
14
|
+
print("#{ex}")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# input tap
|
|
20
|
+
def self.tap(x = 0, y = 0)
|
|
21
|
+
begin
|
|
22
|
+
if (x == nil || y == nil)
|
|
23
|
+
Log.error("Usage: dragDropImage(img, x, y)")
|
|
24
|
+
print("Usage: dragDropImage(img, x, y)")
|
|
25
|
+
return
|
|
26
|
+
end
|
|
27
|
+
x = x.to_i
|
|
28
|
+
y = y.to_i
|
|
29
|
+
rescue => ex
|
|
30
|
+
Log.error("#{ex}")
|
|
31
|
+
print("#{ex}")
|
|
32
|
+
end
|
|
33
|
+
system("adb shell input tap #{x}, #{y}")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# input keyevent
|
|
37
|
+
def self.keyevent(keycode)
|
|
38
|
+
system("adb shell input keyevent #{keycode}")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# input swipe
|
|
42
|
+
def self.swipe(x1 = 0, y1 = 0, x2 = 0, y2 = 0)
|
|
43
|
+
begin
|
|
44
|
+
if (x1 == nil || y1 == nil || x2 == nil || y2 == nil)
|
|
45
|
+
Log.error("Usage: dragDropImage(img, x, y)")
|
|
46
|
+
print("Usage: dragDropImage(img, x, y)")
|
|
47
|
+
return
|
|
48
|
+
end
|
|
49
|
+
x1 = x1.to_i
|
|
50
|
+
y1 = y1.to_i
|
|
51
|
+
x2 = x2.to_i
|
|
52
|
+
y2 = y2.to_i
|
|
53
|
+
rescue => ex
|
|
54
|
+
Log.error("#{ex}")
|
|
55
|
+
print("#{ex}")
|
|
56
|
+
end
|
|
57
|
+
system("adb shell input swipe #{x1} #{y1} #{x2} #{y2}")
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
|
|
2
|
+
module ThinClient
|
|
3
|
+
|
|
4
|
+
class Keyboard
|
|
5
|
+
|
|
6
|
+
@keyboardEvent = ""
|
|
7
|
+
@SYN_EVENT = ""
|
|
8
|
+
|
|
9
|
+
def initialize()
|
|
10
|
+
getKeyboardEvent()
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# 获取键盘设备
|
|
14
|
+
def self.getKeyboardEvent()
|
|
15
|
+
# getevent获取到的键盘的名字不是统一的,观察到的名字中都包含" USB"字样
|
|
16
|
+
str = `adb shell getevent -p`
|
|
17
|
+
|
|
18
|
+
begin
|
|
19
|
+
events = str.scan(/\/dev\/input\/event.\r\n.* USB.*/)
|
|
20
|
+
Log.debug("Events:#{events}")
|
|
21
|
+
if (events.size != 2)
|
|
22
|
+
Log.error("Keyboard Not Found. It Cannot Work Without a Keyboard Connected to The ThinClient")
|
|
23
|
+
return false
|
|
24
|
+
end
|
|
25
|
+
@keyboardEvent = events[1].split(/\r/)[0]
|
|
26
|
+
@SYN_EVENT = "adb shell sendevent #{@keyboardEvent} 0 0 0"
|
|
27
|
+
#print @keyboardEvent
|
|
28
|
+
return true
|
|
29
|
+
rescue => ex
|
|
30
|
+
Log.error("#{ex}")
|
|
31
|
+
print ex
|
|
32
|
+
return false
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# 按下键盘按键
|
|
37
|
+
def self.keyDown(keycode)
|
|
38
|
+
begin
|
|
39
|
+
keycode = keycode.to_s
|
|
40
|
+
rescue => ex
|
|
41
|
+
Log.error("#{ex}")
|
|
42
|
+
print("#{ex}")
|
|
43
|
+
end
|
|
44
|
+
system("adb shell sendevent #{@keyboardEvent} 1 #{keycode} 1")
|
|
45
|
+
system("#{@SYN_EVENT}")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# 抬起键盘按键
|
|
49
|
+
def self.keyUp(keycode)
|
|
50
|
+
begin
|
|
51
|
+
keycode = keycode.to_s
|
|
52
|
+
rescue => ex
|
|
53
|
+
Log.error("#{ex}")
|
|
54
|
+
print("#{ex}")
|
|
55
|
+
end
|
|
56
|
+
system("adb shell sendevent #{@keyboardEvent} 1 #{keycode} 0")
|
|
57
|
+
system("#{@SYN_EVENT}")
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# 发送键盘按键,多个按键用"+"隔开(按键名称见KeyCode.Key)
|
|
61
|
+
def self.sendKeys(keys)
|
|
62
|
+
if false === getKeyboardEvent()
|
|
63
|
+
return
|
|
64
|
+
end
|
|
65
|
+
args = dealArg(keys)
|
|
66
|
+
args.each { |key|
|
|
67
|
+
keyDown(key)
|
|
68
|
+
}
|
|
69
|
+
args.each { |key|
|
|
70
|
+
keyUp(key)
|
|
71
|
+
}
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# 处理输入的参数
|
|
75
|
+
def self.dealArg(keys)
|
|
76
|
+
begin
|
|
77
|
+
keys = keys.to_s
|
|
78
|
+
rescue => ex
|
|
79
|
+
Log.error("#{ex}")
|
|
80
|
+
print("#{ex}")
|
|
81
|
+
end
|
|
82
|
+
args = []
|
|
83
|
+
tmp = keys.split(/\+/)
|
|
84
|
+
Log.debug("Keys:#{tmp}")
|
|
85
|
+
tmp.each { |key|
|
|
86
|
+
key = "KEY_" + key.upcase
|
|
87
|
+
index = KeyCode.key2code(key)
|
|
88
|
+
if (index == 0xffff)
|
|
89
|
+
return args
|
|
90
|
+
end
|
|
91
|
+
args.push(index)
|
|
92
|
+
}
|
|
93
|
+
return args
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
end
|
|
102
|
+
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
module ThinClient
|
|
2
|
+
class KeyCode
|
|
3
|
+
|
|
4
|
+
Code = [
|
|
5
|
+
0x0001, 0x0002, 0x0003, 0x0004,
|
|
6
|
+
0x0005, 0x0006, 0x0007, 0x0008,
|
|
7
|
+
0x0009, 0x000a, 0x000b, 0x000c,
|
|
8
|
+
0x000d, 0x000e, 0x000f, 0x0010,
|
|
9
|
+
0x0011, 0x0012, 0x0013, 0x0014,
|
|
10
|
+
0x0015, 0x0016, 0x0017, 0x0018,
|
|
11
|
+
0x0019, 0x001a, 0x001b, 0x001c,
|
|
12
|
+
0x001d, 0x001e, 0x001f, 0x0020,
|
|
13
|
+
0x0021, 0x0022, 0x0023, 0x0024,
|
|
14
|
+
0x0025, 0x0026, 0x0027, 0x0028,
|
|
15
|
+
0x0029, 0x002a, 0x002b, 0x002c,
|
|
16
|
+
0x002d, 0x002e, 0x002f, 0x0030,
|
|
17
|
+
0x0031, 0x0032, 0x0033, 0x0034,
|
|
18
|
+
0x0035, 0x0036, 0x0037, 0x0038,
|
|
19
|
+
0x0039, 0x003a, 0x003b, 0x003c,
|
|
20
|
+
0x003d, 0x003e, 0x003f, 0x0040,
|
|
21
|
+
0x0041, 0x0042, 0x0043, 0x0044,
|
|
22
|
+
0x0045, 0x0046, 0x0047, 0x0048,
|
|
23
|
+
0x0049, 0x004a, 0x004b, 0x004c,
|
|
24
|
+
0x004d, 0x004e, 0x004f, 0x0050,
|
|
25
|
+
0x0051, 0x0052, 0x0053, 0x0056,
|
|
26
|
+
0x0057, 0x0058, 0x0059, 0x005c,
|
|
27
|
+
0x005d, 0x005e, 0x005f, 0x0060,
|
|
28
|
+
0x0061, 0x0062, 0x0063, 0x0064,
|
|
29
|
+
0x0066, 0x0067, 0x0068, 0x0069,
|
|
30
|
+
0x006a, 0x006b, 0x006c, 0x006d,
|
|
31
|
+
0x006e, 0x006f, 0x0071, 0x0072,
|
|
32
|
+
0x0073, 0x0074, 0x0075, 0x0077,
|
|
33
|
+
0x0079, 0x007a, 0x007b, 0x007c,
|
|
34
|
+
0x007d, 0x007e, 0x007f, 0x0080,
|
|
35
|
+
0x0081, 0x0082, 0x0083, 0x0084,
|
|
36
|
+
0x0085, 0x0086, 0x0087, 0x0088,
|
|
37
|
+
0x0089, 0x008a, 0x00b7, 0x00b8,
|
|
38
|
+
0x00b9, 0x00ba, 0x00bb, 0x00bc,
|
|
39
|
+
0x00bd, 0x00be, 0x00bf, 0x00c0,
|
|
40
|
+
0x00c1, 0x00c2, 0x00f0,
|
|
41
|
+
#add
|
|
42
|
+
0x007d, 0x0038, 0x001d, 0x002a,
|
|
43
|
+
0x0029, 0x000c, 0x000d, 0x001a,
|
|
44
|
+
0x001b, 0X0027, 0X0028, 0X002B,
|
|
45
|
+
0X0033, 0X0034, 0X0035
|
|
46
|
+
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
Key = [
|
|
50
|
+
"KEY_ESC", "KEY_1", "KEY_2", "KEY_3",
|
|
51
|
+
"KEY_4", "KEY_5", "KEY_6", "KEY_7",
|
|
52
|
+
"KEY_8", "KEY_9", "KEY_0", "KEY_MINUS",
|
|
53
|
+
"KEY_EQUAL", "KEY_BACKSPACE", "KEY_TAB", "KEY_Q",
|
|
54
|
+
"KEY_W", "KEY_E", "KEY_R", "KEY_T",
|
|
55
|
+
"KEY_Y", "KEY_U", "KEY_I", "KEY_O",
|
|
56
|
+
"KEY_P", "KEY_LEFTBRACE", "KEY_RIGHTBRACE",
|
|
57
|
+
"KEY_ENTER", "KEY_LEFTCTRL", "KEY_A","KEY_S",
|
|
58
|
+
"KEY_D", "KEY_F", "KEY_G", "KEY_H",
|
|
59
|
+
"KEY_J", "KEY_K", "KEY_L", "KEY_SEMICOLON",
|
|
60
|
+
"KEY_APOSTROPHE", "KEY_GRAVE","KEY_LEFTSHIFT","KEY_BACKSLASH",
|
|
61
|
+
"KEY_Z", "KEY_X", "KEY_C", "KEY_V",
|
|
62
|
+
"KEY_B", "KEY_N", "KEY_M", "KEY_COMMA",
|
|
63
|
+
"KEY_DOT", "KEY_SLASH","KEY_RIGHTSHIFT","KEY_KPASTERISK",
|
|
64
|
+
"KEY_LEFTALT", "KEY_SPACE","KEY_CAPSLOCK","KEY_F1",
|
|
65
|
+
"KEY_F2", "KEY_F3", "KEY_F4", "KEY_F5",
|
|
66
|
+
"KEY_F6", "KEY_F7", "KEY_F8", "KEY_F9",
|
|
67
|
+
"KEY_F10", "KEY_NUMLOCK","KEY_SCROLLLOCK","KEY_KP7",
|
|
68
|
+
"KEY_KP8", "KEY_KP9", "KEY_KPMINUS","KEY_KP4",
|
|
69
|
+
"KEY_KP5", "KEY_KP6", "KEY_KPPLUS","KEY_KP1",
|
|
70
|
+
"KEY_KP2", "KEY_KP3", "KEY_KP0", "KEY_KPDOT",
|
|
71
|
+
"KEY_102ND", "KEY_F11", "KEY_F12", "KEY_RO",
|
|
72
|
+
"KEY_HENKAN", "KEY_KATAKANAHIRAGANA","KEY_MUHENKAN","KEY_KPJPCOMMA",
|
|
73
|
+
"KEY_KPENTER", "KEY_RIGHTCTRL","KEY_KPSLASH","KEY_SYSRQ",
|
|
74
|
+
"KEY_RIGHTALT", "KEY_HOME","KEY_UP","KEY_PAGEUP",
|
|
75
|
+
"KEY_LEFT", "KEY_RIGHT","KEY_END","KEY_DOWN",
|
|
76
|
+
"KEY_PAGEDOWN", "KEY_INSERT","KEY_DELETE","KEY_MUTE",
|
|
77
|
+
"KEY_VOLUMEDOWN", "KEY_VOLUMEUP","KEY_POWER","KEY_KPEQUAL",
|
|
78
|
+
"KEY_PAUSE", "KEY_KPCOMMA","KEY_HANGEUL","KEY_HANJA",
|
|
79
|
+
"KEY_YEN", "KEY_LEFTMETA","KEY_RIGHTMETA","KEY_COMPOSE",
|
|
80
|
+
"KEY_STOP", "KEY_AGAIN","KEY_PROPS","KEY_UNDO",
|
|
81
|
+
"KEY_FRONT", "KEY_COPY","KEY_OPEN","KEY_PASTE",
|
|
82
|
+
"KEY_FIND", "KEY_CUT","KEY_HELP","KEY_F13",
|
|
83
|
+
"KEY_F14", "KEY_F15","KEY_F16","KEY_F17",
|
|
84
|
+
"KEY_F18", "KEY_F19","KEY_F20","KEY_F21","KEY_F22",
|
|
85
|
+
"KEY_F23", "KEY_F24", "KEY_UNKNOWN",
|
|
86
|
+
#add
|
|
87
|
+
"KEY_WIN", "KEY_ALT", "KEY_CTRL", "KEY_SHIFT",
|
|
88
|
+
"KEY_`", "KEY_-", "KEY_=", "KEY_[",
|
|
89
|
+
"KEY_]", "KEY_;", "KEY_'", "KEY_\\",
|
|
90
|
+
"KEY_,", "KEY_.", "KEY_/"
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
def self.key2code(key)
|
|
94
|
+
begin
|
|
95
|
+
key = key.to_s
|
|
96
|
+
rescue => ex
|
|
97
|
+
Log.error("#{ex}")
|
|
98
|
+
print("#{ex}")
|
|
99
|
+
end
|
|
100
|
+
index = Key.index(key)
|
|
101
|
+
if (index != nil)
|
|
102
|
+
return Code[index]
|
|
103
|
+
end
|
|
104
|
+
print "No such key:#{key}\n"
|
|
105
|
+
Log.error("No such key:#{key}")
|
|
106
|
+
return 0xffff
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def self.code2key(code)
|
|
110
|
+
index = Code.index(code)
|
|
111
|
+
if (index != nil)
|
|
112
|
+
return Key[index]
|
|
113
|
+
end
|
|
114
|
+
return "NULL"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
end
|
|
119
|
+
end
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
module ThinClient
|
|
2
|
+
class Mouse
|
|
3
|
+
@mouseEvent = ""
|
|
4
|
+
@SYN_EVENT = ""
|
|
5
|
+
@realX = 1920.0
|
|
6
|
+
@realY = 1080.0
|
|
7
|
+
@virtualX = 1280.0
|
|
8
|
+
@virtualY = 720.0
|
|
9
|
+
# 瘦客户机上移动n个像素,实际需要移动n+n*0.2的
|
|
10
|
+
@diffX = 0.2
|
|
11
|
+
@diffY = 0.2
|
|
12
|
+
def initialize()
|
|
13
|
+
getMouseEvent()
|
|
14
|
+
end
|
|
15
|
+
# 先设定独享桌面的分辨率
|
|
16
|
+
def self.setResolution(x = 1280, y = 720)
|
|
17
|
+
@virtualX = x
|
|
18
|
+
@virtualY = y
|
|
19
|
+
end
|
|
20
|
+
def self.getMouseEvent()
|
|
21
|
+
if @mouseEvent != ""
|
|
22
|
+
return true
|
|
23
|
+
end
|
|
24
|
+
str = `adb shell getevent -p`
|
|
25
|
+
begin
|
|
26
|
+
events = str.scan(/\/dev\/input\/event.\r\n.* Mouse.*/)
|
|
27
|
+
Log.debug("Events:#{events}")
|
|
28
|
+
if (events.size != 1)
|
|
29
|
+
Log.error("Mouse Not Found. It Cannot Work Without a Mouse Connected to The ThinClient")
|
|
30
|
+
return false
|
|
31
|
+
end
|
|
32
|
+
@mouseEvent = events[0].split(/\r/)[0]
|
|
33
|
+
|
|
34
|
+
@SYN_EVENT = "adb shell sendevent #{@mouseEvent} 0 0 0"
|
|
35
|
+
|
|
36
|
+
setResolution()
|
|
37
|
+
#print @mouseEvent
|
|
38
|
+
return true
|
|
39
|
+
rescue => ex
|
|
40
|
+
Log.error("#{ex}")
|
|
41
|
+
print ex
|
|
42
|
+
return false
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# 向左移动(n个像素)
|
|
47
|
+
def self.left(n = 1)
|
|
48
|
+
begin
|
|
49
|
+
n = n.to_i
|
|
50
|
+
rescue => ex
|
|
51
|
+
Log.error("#{ex}")
|
|
52
|
+
print("#{ex}")
|
|
53
|
+
end
|
|
54
|
+
getMouseEvent()
|
|
55
|
+
n = n / (1 + @diffX)
|
|
56
|
+
system("adb shell sendevent #{@mouseEvent} 2 0 -#{n}")
|
|
57
|
+
system("#{@SYN_EVENT}")
|
|
58
|
+
end
|
|
59
|
+
# 向右移动(n个像素)
|
|
60
|
+
def self.right(n = 1)
|
|
61
|
+
begin
|
|
62
|
+
n = n.to_i
|
|
63
|
+
rescue => ex
|
|
64
|
+
Log.error("#{ex}")
|
|
65
|
+
print("#{ex}")
|
|
66
|
+
end
|
|
67
|
+
getMouseEvent()
|
|
68
|
+
n = n / (1 + @diffX)
|
|
69
|
+
system("adb shell sendevent #{@mouseEvent} 2 0 #{n}")
|
|
70
|
+
system("#{@SYN_EVENT}")
|
|
71
|
+
end
|
|
72
|
+
# 向上移动(n个像素)
|
|
73
|
+
def self.up(n = 1)
|
|
74
|
+
begin
|
|
75
|
+
n = n.to_i
|
|
76
|
+
rescue => ex
|
|
77
|
+
Log.error("#{ex}")
|
|
78
|
+
print("#{ex}")
|
|
79
|
+
end
|
|
80
|
+
getMouseEvent()
|
|
81
|
+
n = n / (1 + @diffY)
|
|
82
|
+
system("adb shell sendevent #{@mouseEvent} 2 1 -#{n}")
|
|
83
|
+
system("#{@SYN_EVENT}")
|
|
84
|
+
end
|
|
85
|
+
# 向下移动(n个像素)
|
|
86
|
+
def self.down(n = 1)
|
|
87
|
+
begin
|
|
88
|
+
n = n.to_i
|
|
89
|
+
rescue => ex
|
|
90
|
+
Log.error("#{ex}")
|
|
91
|
+
print("#{ex}")
|
|
92
|
+
end
|
|
93
|
+
getMouseEvent()
|
|
94
|
+
n = n / (1 + @diffY)
|
|
95
|
+
system("adb shell sendevent #{@mouseEvent} 2 1 #{n}")
|
|
96
|
+
system("#{@SYN_EVENT}")
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# 移动到某个位置
|
|
100
|
+
def self.moveTo(x = nil, y = nil)
|
|
101
|
+
if false === getMouseEvent()
|
|
102
|
+
return false
|
|
103
|
+
end
|
|
104
|
+
if (x != nil && y != nil)
|
|
105
|
+
left(5000)
|
|
106
|
+
up(5000)
|
|
107
|
+
right(x)
|
|
108
|
+
down(y)
|
|
109
|
+
return
|
|
110
|
+
end
|
|
111
|
+
if (x != nil)
|
|
112
|
+
left(5000)
|
|
113
|
+
right(x)
|
|
114
|
+
end
|
|
115
|
+
if (y != nil)
|
|
116
|
+
up(5000)
|
|
117
|
+
down(y)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
# 左键按下
|
|
124
|
+
def self.leftBtnDown()
|
|
125
|
+
system("adb shell sendevent #{@mouseEvent} 1 272 1")
|
|
126
|
+
system("#{@SYN_EVENT}")
|
|
127
|
+
end
|
|
128
|
+
# 左键抬起
|
|
129
|
+
def self.leftBtnUp()
|
|
130
|
+
system("adb shell sendevent #{@mouseEvent} 1 272 0")
|
|
131
|
+
system("#{@SYN_EVENT}")
|
|
132
|
+
end
|
|
133
|
+
# 右键按下
|
|
134
|
+
def self.rightBtnDown()
|
|
135
|
+
system("adb shell sendevent #{@mouseEvent} 1 273 1")
|
|
136
|
+
system("#{@SYN_EVENT}")
|
|
137
|
+
end
|
|
138
|
+
# 右键抬起
|
|
139
|
+
def self.rightBtnUp()
|
|
140
|
+
system("adb shell sendevent #{@mouseEvent} 1 273 0")
|
|
141
|
+
system("#{@SYN_EVENT}")
|
|
142
|
+
end
|
|
143
|
+
# 滚轮按下
|
|
144
|
+
def self.middleBtnDown()
|
|
145
|
+
system("adb shell sendevent #{@mouseEvent} 1 274 1")
|
|
146
|
+
system("#{@SYN_EVENT}")
|
|
147
|
+
end
|
|
148
|
+
# 滚轮抬起
|
|
149
|
+
def self.middleBtnUp()
|
|
150
|
+
system("adb shell sendevent #{@mouseEvent} 1 274 0")
|
|
151
|
+
system("#{@SYN_EVENT}")
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# 单击(某个坐标)
|
|
155
|
+
def self.click(x = nil, y = nil)
|
|
156
|
+
moveTo(x, y)
|
|
157
|
+
leftBtnDown()
|
|
158
|
+
leftBtnUp()
|
|
159
|
+
end
|
|
160
|
+
# 双击(某个坐标)
|
|
161
|
+
def self.doubleClick(x = nil, y = nil)
|
|
162
|
+
moveTo(x, y)
|
|
163
|
+
leftBtnDown()
|
|
164
|
+
leftBtnUp()
|
|
165
|
+
leftBtnDown()
|
|
166
|
+
leftBtnUp()
|
|
167
|
+
end
|
|
168
|
+
# 按下(按下时间,某个坐标)
|
|
169
|
+
def self.press(t = 2, x = nil, y = nil)
|
|
170
|
+
moveTo(x, y)
|
|
171
|
+
leftBtnDown()
|
|
172
|
+
sleep(t)
|
|
173
|
+
leftBtnUp()
|
|
174
|
+
end
|
|
175
|
+
# 拖拽
|
|
176
|
+
def self.dragDrop(x1 = 0, y1 = 0, x2 = 0, y2 = 0)
|
|
177
|
+
moveTo(x1, y1)
|
|
178
|
+
leftBtnDown()
|
|
179
|
+
#moveTo(x2, y2)
|
|
180
|
+
begin
|
|
181
|
+
x1 = x1.to_i
|
|
182
|
+
y1 = y1.to_i
|
|
183
|
+
x2 = x2.to_i
|
|
184
|
+
y2 = y2.to_i
|
|
185
|
+
rescue => ex
|
|
186
|
+
Log.error("#{ex}")
|
|
187
|
+
print("#{ex}")
|
|
188
|
+
end
|
|
189
|
+
right(x2 - x1)
|
|
190
|
+
down(y2 - y1)
|
|
191
|
+
leftBtnUp()
|
|
192
|
+
end
|
|
193
|
+
# 右击
|
|
194
|
+
def self.rightClick(x = nil, y = nil)
|
|
195
|
+
moveTo(x, y)
|
|
196
|
+
rightBtnDown()
|
|
197
|
+
rightBtnUp()
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# 滚轮向下滚动
|
|
201
|
+
def self.rollDown(n = 1)
|
|
202
|
+
begin
|
|
203
|
+
n = n.to_i
|
|
204
|
+
rescue => ex
|
|
205
|
+
Log.error("#{ex}")
|
|
206
|
+
print("#{ex}")
|
|
207
|
+
end
|
|
208
|
+
if false === getMouseEvent()
|
|
209
|
+
return
|
|
210
|
+
end
|
|
211
|
+
while (n > 0)
|
|
212
|
+
system("adb shell sendevent #{@mouseEvent} 2 8 -1")
|
|
213
|
+
system("#{@SYN_EVENT}")
|
|
214
|
+
n -= 1
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
# 滚轮向上滚动
|
|
218
|
+
def self.rollUp(n = 1)
|
|
219
|
+
begin
|
|
220
|
+
n = n.to_i
|
|
221
|
+
rescue => ex
|
|
222
|
+
Log.error("#{ex}")
|
|
223
|
+
print("#{ex}")
|
|
224
|
+
end
|
|
225
|
+
if false === getMouseEvent()
|
|
226
|
+
return
|
|
227
|
+
end
|
|
228
|
+
while (n > 0)
|
|
229
|
+
system("adb shell sendevent #{@mouseEvent} 2 8 1")
|
|
230
|
+
system("#{@SYN_EVENT}")
|
|
231
|
+
n -= 1
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
# 单击滚轮
|
|
235
|
+
def self.rollClick()
|
|
236
|
+
if false === getMouseEvent()
|
|
237
|
+
return
|
|
238
|
+
end
|
|
239
|
+
middleBtnDown()
|
|
240
|
+
middleBtnUp()
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# 单击图片
|
|
244
|
+
def self.clickImage(img, similar = 0.8)
|
|
245
|
+
position = Image.getPositionOnScreen(img, similar)
|
|
246
|
+
if (position[:X] === 0 || position[:Y] === 0)
|
|
247
|
+
return false
|
|
248
|
+
end
|
|
249
|
+
click(position[:X], position[:Y])
|
|
250
|
+
return true
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# 双击图片
|
|
254
|
+
def self.doubleClickImage(img, similar = 0.8)
|
|
255
|
+
position = Image.getPositionOnScreen(img, similar)
|
|
256
|
+
if (position[:X] === 0 || position[:Y] === 0)
|
|
257
|
+
return false
|
|
258
|
+
end
|
|
259
|
+
doubleClick(position[:X], position[:Y])
|
|
260
|
+
return true
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# 右击图片
|
|
264
|
+
def self.rightClickImage(img, similar = 0.8)
|
|
265
|
+
position = Image.getPositionOnScreen(img, similar)
|
|
266
|
+
if (position[:X] === 0 || position[:Y] === 0)
|
|
267
|
+
return false
|
|
268
|
+
end
|
|
269
|
+
rightClick(position[:X], position[:Y])
|
|
270
|
+
return true
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# 长按图片
|
|
274
|
+
def self.pressImage(img, t = 2, similar = 0.8)
|
|
275
|
+
position = Image.getPositionOnScreen(img, similar)
|
|
276
|
+
if (position[:X] === 0 || position[:Y] === 0)
|
|
277
|
+
return false
|
|
278
|
+
end
|
|
279
|
+
press(t, position[:X], position[:Y])
|
|
280
|
+
return true
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# 拖拽图片到指定位置
|
|
284
|
+
def self.dragDropImage(img, x = 0, y = 0, similar = 0.8)
|
|
285
|
+
begin
|
|
286
|
+
if (x == nil || y == nil)
|
|
287
|
+
Log.error("Usage: dragDropImage(img, x, y)")
|
|
288
|
+
print("Usage: dragDropImage(img, x, y)")
|
|
289
|
+
return
|
|
290
|
+
end
|
|
291
|
+
x = x.to_i
|
|
292
|
+
y = y.to_i
|
|
293
|
+
rescue => ex
|
|
294
|
+
Log.error("#{ex}")
|
|
295
|
+
print("#{ex}")
|
|
296
|
+
end
|
|
297
|
+
position = Image.getPositionOnScreen(img, similar)
|
|
298
|
+
if (position[:X] === 0 || position[:Y] === 0)
|
|
299
|
+
return false
|
|
300
|
+
end
|
|
301
|
+
dragDrop(position[:X], position[:Y], x, y)
|
|
302
|
+
return true
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# 拖拽图片1到图片2上
|
|
306
|
+
def self.dragDropImagetoImage(img1, img2, similar1 = 0.8, similar2 = 0.8)
|
|
307
|
+
position1 = Image.getPositionOnScreen(img, similar1)
|
|
308
|
+
position2 = Image.getPositionOnScreen(img2, similar2)
|
|
309
|
+
if (position1[:X] === 0 || position1[:Y] === 0 || position2[:X] === 0 || position2[:Y] ===0)
|
|
310
|
+
return false
|
|
311
|
+
end
|
|
312
|
+
dragDrop(position1[:X], position1[:Y], position2[:X], position2[:Y])
|
|
313
|
+
return true
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
end
|
|
318
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
require 'rubygems' if RUBY_VERSION =~ /^1.8/
|
|
2
|
+
require 'logger'
|
|
3
|
+
|
|
4
|
+
module ThinClient
|
|
5
|
+
class Log
|
|
6
|
+
def self.init()
|
|
7
|
+
@myLogger = Logger.new(File.new(File.join("ThinClient.log"), "a"))
|
|
8
|
+
@myLogger.datetime_format = "%m%d-%H:%M:%S"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.info(str)
|
|
12
|
+
init()
|
|
13
|
+
@myLogger.info(str)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.debug(str)
|
|
17
|
+
init()
|
|
18
|
+
@myLogger.debug(str)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.error(str)
|
|
22
|
+
init()
|
|
23
|
+
@myLogger.error(str)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.warn(str)
|
|
27
|
+
init()
|
|
28
|
+
@myLogger.warn(str)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.fatal(str)
|
|
32
|
+
init()
|
|
33
|
+
@myLogger.fatal(str)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
data/thinclient.gemspec
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'thinclient/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |gem|
|
|
7
|
+
gem.name = "thinclient"
|
|
8
|
+
gem.version = Thinclient::VERSION
|
|
9
|
+
gem.authors = ["wangbaomi"]
|
|
10
|
+
gem.email = ["system1029@gmail.com"]
|
|
11
|
+
gem.description = %q{SSL ThinClict}
|
|
12
|
+
gem.summary = %q{SSL ThinClict}
|
|
13
|
+
gem.homepage = ""
|
|
14
|
+
|
|
15
|
+
gem.files = `git ls-files`.split($/)
|
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
|
18
|
+
gem.require_paths = ["lib"]
|
|
19
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: thinclient
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
hash: 23
|
|
5
|
+
prerelease:
|
|
6
|
+
segments:
|
|
7
|
+
- 0
|
|
8
|
+
- 0
|
|
9
|
+
- 4
|
|
10
|
+
version: 0.0.4
|
|
11
|
+
platform: ruby
|
|
12
|
+
authors:
|
|
13
|
+
- wangbaomi
|
|
14
|
+
autorequire:
|
|
15
|
+
bindir: bin
|
|
16
|
+
cert_chain: []
|
|
17
|
+
|
|
18
|
+
date: 2014-05-21 00:00:00 Z
|
|
19
|
+
dependencies: []
|
|
20
|
+
|
|
21
|
+
description: SSL ThinClict
|
|
22
|
+
email:
|
|
23
|
+
- system1029@gmail.com
|
|
24
|
+
executables:
|
|
25
|
+
- ImageMatch.exe
|
|
26
|
+
- opencv_core248.dll
|
|
27
|
+
- opencv_highgui248.dll
|
|
28
|
+
- opencv_imgproc248.dll
|
|
29
|
+
extensions: []
|
|
30
|
+
|
|
31
|
+
extra_rdoc_files: []
|
|
32
|
+
|
|
33
|
+
files:
|
|
34
|
+
- .gitignore
|
|
35
|
+
- Gemfile
|
|
36
|
+
- LICENSE.txt
|
|
37
|
+
- README.md
|
|
38
|
+
- Rakefile
|
|
39
|
+
- bin/ImageMatch.exe
|
|
40
|
+
- bin/opencv_core248.dll
|
|
41
|
+
- bin/opencv_highgui248.dll
|
|
42
|
+
- bin/opencv_imgproc248.dll
|
|
43
|
+
- lib/thinclient.rb
|
|
44
|
+
- lib/thinclient/error.rb
|
|
45
|
+
- lib/thinclient/image.rb
|
|
46
|
+
- lib/thinclient/input.rb
|
|
47
|
+
- lib/thinclient/keyboard.rb
|
|
48
|
+
- lib/thinclient/keycode.rb
|
|
49
|
+
- lib/thinclient/mouse.rb
|
|
50
|
+
- lib/thinclient/mylogger.rb
|
|
51
|
+
- lib/thinclient/version.rb
|
|
52
|
+
- thinclient.gemspec
|
|
53
|
+
homepage: ""
|
|
54
|
+
licenses: []
|
|
55
|
+
|
|
56
|
+
post_install_message:
|
|
57
|
+
rdoc_options: []
|
|
58
|
+
|
|
59
|
+
require_paths:
|
|
60
|
+
- lib
|
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
62
|
+
none: false
|
|
63
|
+
requirements:
|
|
64
|
+
- - ">="
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
hash: 3
|
|
67
|
+
segments:
|
|
68
|
+
- 0
|
|
69
|
+
version: "0"
|
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
71
|
+
none: false
|
|
72
|
+
requirements:
|
|
73
|
+
- - ">="
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
hash: 3
|
|
76
|
+
segments:
|
|
77
|
+
- 0
|
|
78
|
+
version: "0"
|
|
79
|
+
requirements: []
|
|
80
|
+
|
|
81
|
+
rubyforge_project:
|
|
82
|
+
rubygems_version: 1.8.24
|
|
83
|
+
signing_key:
|
|
84
|
+
specification_version: 3
|
|
85
|
+
summary: SSL ThinClict
|
|
86
|
+
test_files: []
|
|
87
|
+
|