sleipnir-api 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.txt +72 -40
- data/Manifest.txt +10 -1
- data/README.txt +10 -0
- data/TODO.txt +5 -4
- data/examples/screenshot.rb +33 -0
- data/lib/sleipnir_api.rb +6 -2
- data/lib/sleipnir_api/bitmap.rb +113 -0
- data/lib/sleipnir_api/ffi.rb +4 -0
- data/lib/sleipnir_api/ffi/base.rb +22 -0
- data/lib/sleipnir_api/ffi/gdi32.rb +165 -0
- data/lib/sleipnir_api/ffi/kernel32.rb +20 -0
- data/lib/sleipnir_api/ffi/struct.rb +246 -0
- data/lib/sleipnir_api/ffi/user32.rb +116 -0
- data/lib/sleipnir_api/process.rb +6 -3
- data/lib/sleipnir_api/profile.rb +6 -3
- data/lib/sleipnir_api/profile/ini.rb +2 -2
- data/lib/sleipnir_api/profile/section.rb +2 -2
- data/lib/sleipnir_api/registry.rb +3 -3
- data/lib/sleipnir_api/screen_captor.rb +138 -0
- data/lib/sleipnir_api/sleipnir.rb +35 -0
- data/lib/sleipnir_api/tab.rb +90 -5
- data/lib/sleipnir_api/version.rb +1 -1
- data/spec/sleipnir_api/ffi_spec.rb +38 -0
- metadata +12 -3
- data/lib/sleipnir_api/win32api.rb +0 -17
@@ -0,0 +1,20 @@
|
|
1
|
+
require "sleipnir_api/ffi/base"
|
2
|
+
|
3
|
+
module SleipnirAPI::FFI
|
4
|
+
module Kernel32 #:nodoc:
|
5
|
+
extend SleipnirAPI::FFI::Base
|
6
|
+
|
7
|
+
define_ffi_entry(:GetLongPathName, "PPL", "L", "kernel32", "GetLongPathNameA")
|
8
|
+
define_ffi_entry(:RtlZeroMemory, "PL", "L", "kernel32")
|
9
|
+
define_ffi_entry(:GetLastError, "V", "L", "kernel32")
|
10
|
+
|
11
|
+
module_function
|
12
|
+
|
13
|
+
def get_long_path_name(short_path)
|
14
|
+
buf = "\0" * 1024
|
15
|
+
len = GetLongPathName(short_path, buf, buf.length)
|
16
|
+
buf[0...len]
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,246 @@
|
|
1
|
+
require "sleipnir_api/ffi/base"
|
2
|
+
|
3
|
+
module SleipnirAPI::FFI
|
4
|
+
|
5
|
+
class Type #:nodoc:
|
6
|
+
attr_reader :name
|
7
|
+
def initialize(name)
|
8
|
+
@name = name
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class PrimitiveType < Type #:nodoc:
|
13
|
+
BASE_TYPES = {
|
14
|
+
:int8 => ["c", 1],
|
15
|
+
:uint8 => ["C", 1],
|
16
|
+
:int16 => ["s", 2],
|
17
|
+
:uint16 => ["S", 2],
|
18
|
+
:int32 => ["l", 4],
|
19
|
+
:uint32 => ["L", 4],
|
20
|
+
:float => ["f", 4],
|
21
|
+
:double => ["d", 8],
|
22
|
+
:string => ["p", 4],
|
23
|
+
:pointer => ["P", 4],
|
24
|
+
}
|
25
|
+
|
26
|
+
attr_reader :size, :format
|
27
|
+
def initialize(name, size=guess_size(name), format=guess_format(name))
|
28
|
+
super(name)
|
29
|
+
@size = size
|
30
|
+
@format = format
|
31
|
+
end
|
32
|
+
|
33
|
+
def pack(value)
|
34
|
+
[value].pack(format)
|
35
|
+
end
|
36
|
+
|
37
|
+
def guess_format(name)
|
38
|
+
BASE_TYPES[name][0]
|
39
|
+
end
|
40
|
+
private :guess_format
|
41
|
+
|
42
|
+
def guess_size(name)
|
43
|
+
BASE_TYPES[name][1]
|
44
|
+
end
|
45
|
+
private :guess_size
|
46
|
+
end
|
47
|
+
|
48
|
+
class StructureType < Type #:nodoc:
|
49
|
+
PACKING_ALIGN = 4
|
50
|
+
|
51
|
+
attr_reader :name, :size, :ruby_class, :fields
|
52
|
+
def initialize(name, size=nil)
|
53
|
+
super(name)
|
54
|
+
@ruby_class = nil
|
55
|
+
@fields = []
|
56
|
+
@size = size
|
57
|
+
end
|
58
|
+
|
59
|
+
def calc_offsets
|
60
|
+
offset = 0
|
61
|
+
@fields.each do |f|
|
62
|
+
align = [f.type.size, PACKING_ALIGN].min
|
63
|
+
offset = (offset.to_f / align).ceil * align
|
64
|
+
size = f.size
|
65
|
+
if f.offset.nil?
|
66
|
+
f.offset = offset
|
67
|
+
offset += size
|
68
|
+
else
|
69
|
+
offset = f.offset
|
70
|
+
offset += size
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def calc_size
|
76
|
+
return @size if @size
|
77
|
+
if @fields.empty?
|
78
|
+
@size = 0
|
79
|
+
return
|
80
|
+
end
|
81
|
+
align = [@fields.first.type.size, PACKING_ALIGN].min
|
82
|
+
offset = @fields.last.offset + @fields.last.size
|
83
|
+
@size = (offset.to_f / align).ceil * align
|
84
|
+
end
|
85
|
+
|
86
|
+
def pack(value)
|
87
|
+
packed = @fields.zip(value.values).map{|f,v| [f.offset, f.pack(v)] }
|
88
|
+
r = ""
|
89
|
+
packed.each do |offset,c|
|
90
|
+
align!(r, offset)
|
91
|
+
r << c
|
92
|
+
end
|
93
|
+
align!(r, size)
|
94
|
+
r
|
95
|
+
end
|
96
|
+
|
97
|
+
def align!(r, offset)
|
98
|
+
if offset < r.length
|
99
|
+
raise "buffer over flow: expected offset #{offset}, but was #{r.length}"
|
100
|
+
end
|
101
|
+
if r.length < offset
|
102
|
+
r << "\0" * (offset - r.length)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def define!(ns)
|
107
|
+
calc_offsets
|
108
|
+
calc_size
|
109
|
+
define_ruby_class(ns)
|
110
|
+
end
|
111
|
+
|
112
|
+
def define_ruby_class(ns)
|
113
|
+
types = self
|
114
|
+
name = @name
|
115
|
+
clazz = Class.new
|
116
|
+
clazz.instance_eval {
|
117
|
+
types.fields.each {|f|
|
118
|
+
attr_accessor f.name
|
119
|
+
}
|
120
|
+
define_method(:initialize) {|*args|
|
121
|
+
types.fields.zip(args).each {|f,arg|
|
122
|
+
instance_variable_set(f.var, arg || f.init)
|
123
|
+
}
|
124
|
+
}
|
125
|
+
define_method(:pack) {
|
126
|
+
types.pack(self)
|
127
|
+
}
|
128
|
+
}
|
129
|
+
clazz.class_eval %Q{
|
130
|
+
def names
|
131
|
+
[#{types.fields.map{|f| ":#{f.name}"} * ','}]
|
132
|
+
end
|
133
|
+
def values
|
134
|
+
[#{types.fields.map{|f| f.var} * ','}]
|
135
|
+
end
|
136
|
+
}
|
137
|
+
(class <<clazz; self; end).instance_eval {
|
138
|
+
define_method(:packed_size) {
|
139
|
+
CStruct.sizeof(name)
|
140
|
+
}
|
141
|
+
define_method(:c_type) {
|
142
|
+
CStruct.c_type(name)
|
143
|
+
}
|
144
|
+
}
|
145
|
+
@ruby_class = clazz
|
146
|
+
ns.const_set(@name, clazz)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
class Field #:nodoc:
|
151
|
+
attr_reader :type, :name, :count, :var
|
152
|
+
attr_accessor :offset
|
153
|
+
def initialize(type, name, opts=nil)
|
154
|
+
@type = type
|
155
|
+
@name = name
|
156
|
+
@var = "@#{name}".to_sym
|
157
|
+
@opts = opts || {}
|
158
|
+
@offset = @opts[:offset] || nil
|
159
|
+
@count = @opts[:dim] || 1
|
160
|
+
end
|
161
|
+
|
162
|
+
def array?
|
163
|
+
count > 0
|
164
|
+
end
|
165
|
+
|
166
|
+
def init
|
167
|
+
if array?
|
168
|
+
init_one
|
169
|
+
else
|
170
|
+
(0...count).map{ init_one }
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def size
|
175
|
+
@type.size * count
|
176
|
+
end
|
177
|
+
|
178
|
+
def pack(value)
|
179
|
+
if count == 1
|
180
|
+
@type.pack(value)
|
181
|
+
else
|
182
|
+
raise "hoge" unless Array === value
|
183
|
+
raise "foo" unless value.length == count
|
184
|
+
value.map{|e| @type.pack(e) }.join
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def init_one
|
189
|
+
if PrimitiveType === @type
|
190
|
+
0
|
191
|
+
else
|
192
|
+
@type.ruby_class.new
|
193
|
+
end
|
194
|
+
end
|
195
|
+
private :init_one
|
196
|
+
end
|
197
|
+
|
198
|
+
module CStruct #:nodoc:
|
199
|
+
TYPES = PrimitiveType::BASE_TYPES.inject({}){|h,t|
|
200
|
+
h[t[0]] = PrimitiveType.new(t[0])
|
201
|
+
h
|
202
|
+
}
|
203
|
+
|
204
|
+
def define_c_struct(name, size=nil, &block)
|
205
|
+
define_c_struct_under(self, name, size, &block)
|
206
|
+
end
|
207
|
+
|
208
|
+
module_function
|
209
|
+
|
210
|
+
def define_c_struct_under(ns, name, size=nil, &block)
|
211
|
+
dsl = DSL.new(name, size)
|
212
|
+
dsl.instance_eval(&block)
|
213
|
+
dsl.struct.define!(ns)
|
214
|
+
TYPES[name] = dsl.struct
|
215
|
+
dsl.struct
|
216
|
+
end
|
217
|
+
|
218
|
+
def sizeof(type)
|
219
|
+
c_type(type).size
|
220
|
+
end
|
221
|
+
|
222
|
+
def c_type(type)
|
223
|
+
TYPES[type]
|
224
|
+
end
|
225
|
+
|
226
|
+
def define_c_type(type, decl)
|
227
|
+
TYPES[decl] = TYPES[type]
|
228
|
+
end
|
229
|
+
|
230
|
+
class DSL #:nodoc:
|
231
|
+
attr_reader :struct
|
232
|
+
|
233
|
+
def initialize(name, size)
|
234
|
+
@struct = StructureType.new(name, size)
|
235
|
+
end
|
236
|
+
|
237
|
+
def method_missing(name, *args)
|
238
|
+
type = CStruct.c_type(name)
|
239
|
+
raise "unknown type: #{name}" unless type
|
240
|
+
name = args.shift
|
241
|
+
@struct.fields << Field.new(type, name, *args)
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require "sleipnir_api/ffi/base"
|
2
|
+
|
3
|
+
module SleipnirAPI::FFI
|
4
|
+
module User32 #:nodoc:
|
5
|
+
extend SleipnirAPI::FFI::Base
|
6
|
+
|
7
|
+
GW_HWNDFIRST = 0
|
8
|
+
GW_HWNDLAST = 1
|
9
|
+
GW_HWNDNEXT = 2
|
10
|
+
GW_HWNDPREV = 3
|
11
|
+
|
12
|
+
define_ffi_entry(:GetWindowThreadProcessId, "LP", "L", "user32")
|
13
|
+
define_ffi_entry(:GetDC, 'L', 'L', 'user32')
|
14
|
+
define_ffi_entry(:GetWindowDC, 'L', 'L', 'user32')
|
15
|
+
define_ffi_entry(:ReleaseDC, 'LL', 'I', 'user32')
|
16
|
+
|
17
|
+
define_ffi_entry(:OpenIcon, 'L', 'B', 'user32')
|
18
|
+
define_ffi_entry(:CloseWindow, 'L', 'B', 'user32')
|
19
|
+
define_ffi_entry(:BringWindowToTop, 'L', 'B', 'user32')
|
20
|
+
define_ffi_entry(:GetActiveWindow, '', 'L', 'user32')
|
21
|
+
define_ffi_entry(:GetForegroundWindow, '', 'L', 'user32')
|
22
|
+
define_ffi_entry(:GetTopWindow, 'L', 'L', 'user32')
|
23
|
+
define_ffi_entry(:GetWindow, 'LI', 'L', 'user32')
|
24
|
+
define_ffi_entry(:GetWindowRect, 'LP', 'B', 'user32')
|
25
|
+
|
26
|
+
module_function
|
27
|
+
|
28
|
+
def get_window_thread_process_id(hwnd)
|
29
|
+
n = "\0" * 4
|
30
|
+
th = GetWindowThreadProcessId.call(hwnd, n)
|
31
|
+
return [th, n.unpack("L")[0]]
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_window_thread_id(hwnd)
|
35
|
+
get_window_thread_process_id(hwnd)[0]
|
36
|
+
end
|
37
|
+
|
38
|
+
def get_window_process_id(hwnd)
|
39
|
+
get_window_thread_process_id(hwnd)[1]
|
40
|
+
end
|
41
|
+
|
42
|
+
def with_window_dc(hwnd)
|
43
|
+
hdc = GetWindowDC(hwnd)
|
44
|
+
raise Win32APIError, "GetDC failed" if hdc.nil? or hdc.zero?
|
45
|
+
begin
|
46
|
+
yield hdc
|
47
|
+
ensure
|
48
|
+
ReleaseDC(hwnd, hdc)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def get_window_rect(hwnd)
|
53
|
+
buf = [0, 0, 0, 0].pack("L4")
|
54
|
+
GetWindowRect(hwnd, buf)
|
55
|
+
buf.unpack("L4")
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
def get_top_window(hwnd)
|
60
|
+
r = GetTopWindow(hwnd)
|
61
|
+
if r.nil? or r.zero?
|
62
|
+
nil
|
63
|
+
else
|
64
|
+
r
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def get_window(hwnd, cmd)
|
69
|
+
r = GetWindow(hwnd, cmd)
|
70
|
+
if r.nil? or r.zero?
|
71
|
+
nil
|
72
|
+
else
|
73
|
+
r
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def get_first_child(hwnd)
|
78
|
+
get_top_window(hwnd)
|
79
|
+
end
|
80
|
+
|
81
|
+
def get_first_sibling(hwnd)
|
82
|
+
get_window(hwnd, GW_HWNDFIRST)
|
83
|
+
end
|
84
|
+
|
85
|
+
def get_last_sibling(hwnd)
|
86
|
+
get_window(hwnd, GW_HWNDLAST)
|
87
|
+
end
|
88
|
+
|
89
|
+
def get_next_sibling(hwnd)
|
90
|
+
get_window(hwnd, GW_HWNDNEXT)
|
91
|
+
end
|
92
|
+
|
93
|
+
def get_prev_sibling(hwnd)
|
94
|
+
get_window(hwnd, GW_HWNDPREV)
|
95
|
+
end
|
96
|
+
|
97
|
+
def each_child_window(hwnd)
|
98
|
+
child = get_first_child(hwnd)
|
99
|
+
if child
|
100
|
+
yield child
|
101
|
+
while child = get_next_sibling(child)
|
102
|
+
yield child
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def list_window(hwnd)
|
108
|
+
r = [hwnd]
|
109
|
+
each_child_window(hwnd) {|child|
|
110
|
+
r << list_window(child)
|
111
|
+
}
|
112
|
+
r
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
data/lib/sleipnir_api/process.rb
CHANGED
@@ -6,8 +6,9 @@ require "sleipnir_api/registry"
|
|
6
6
|
module SleipnirAPI
|
7
7
|
class Process # :nodoc:
|
8
8
|
class <<self
|
9
|
-
|
10
9
|
include SleipnirAPI::Registry
|
10
|
+
include SleipnirAPI::FFI::User32
|
11
|
+
include SleipnirAPI::FFI::Kernel32
|
11
12
|
|
12
13
|
@@wbem = nil
|
13
14
|
|
@@ -37,13 +38,15 @@ module SleipnirAPI
|
|
37
38
|
end
|
38
39
|
|
39
40
|
def raise_create_error(msg)
|
40
|
-
raise
|
41
|
+
raise SleipnirAPI::ProcessError, "Failed to create Sleipnir process: #{msg}."
|
41
42
|
end
|
42
43
|
|
43
44
|
def terminate!(handle)
|
45
|
+
proc_id = get_window_process_id(handle)
|
44
46
|
query_sleipnir.each do |obj|
|
45
|
-
obj.Terminate
|
47
|
+
return obj.Terminate if obj.ProcessId == proc_id
|
46
48
|
end
|
49
|
+
nil
|
47
50
|
end
|
48
51
|
|
49
52
|
def query_sleipnir
|
data/lib/sleipnir_api/profile.rb
CHANGED
@@ -68,7 +68,10 @@ module SleipnirAPI
|
|
68
68
|
include DataUtil
|
69
69
|
|
70
70
|
# SleipnirAPI::Sleipnir object
|
71
|
-
attr_reader :sleipnir
|
71
|
+
attr_reader :sleipnir
|
72
|
+
|
73
|
+
# default options
|
74
|
+
attr_reader :default_opts
|
72
75
|
|
73
76
|
def initialize(sleipnir, default_opts = nil)
|
74
77
|
@sleipnir = sleipnir
|
@@ -262,7 +265,7 @@ module SleipnirAPI
|
|
262
265
|
expand_user_path("script.ini")
|
263
266
|
end
|
264
267
|
|
265
|
-
# 指定された ini ファイルを操作する
|
268
|
+
# 指定された ini ファイルを操作する SleipnirAPI::Profile::Ini オブジェクトを返します。
|
266
269
|
#
|
267
270
|
# pnir = SleipnirAPI.connect
|
268
271
|
# proxy = pnir.profile.ini("Proxy.ini", :default => 123)
|
@@ -273,7 +276,7 @@ module SleipnirAPI
|
|
273
276
|
Ini.new(self, str(name), options(opts))
|
274
277
|
end
|
275
278
|
|
276
|
-
# メソッド名を ini ファイル名とみなして
|
279
|
+
# メソッド名を ini ファイル名とみなして SleipnirAPI::Profile::Ini オブジェクトを返します。
|
277
280
|
#
|
278
281
|
# pnir = SleipnirAPI.connect
|
279
282
|
# proxy = pnir.profile.Proxy(:default => 123)
|
@@ -147,7 +147,7 @@ module SleipnirAPI
|
|
147
147
|
not script_ini?
|
148
148
|
end
|
149
149
|
|
150
|
-
# 指定されたセクションを操作する
|
150
|
+
# 指定されたセクションを操作する SleipnirAPI::Profile::Section オブジェクトを返します。
|
151
151
|
#
|
152
152
|
# pnir = SleipnirAPI.connect
|
153
153
|
# proxy = pnir.profile.ini("Proxy.ini", :default => 123)
|
@@ -159,7 +159,7 @@ module SleipnirAPI
|
|
159
159
|
Section.new(self, str(name), options(opts))
|
160
160
|
end
|
161
161
|
|
162
|
-
# メソッド名をセクション名とみなして
|
162
|
+
# メソッド名をセクション名とみなして SleipnirAPI::Profile::Section オブジェクトを返します。
|
163
163
|
#
|
164
164
|
# pnir = SleipnirAPI.connect
|
165
165
|
# proxy = pnir.profile.Proxy(:default => 123)
|