vapir-common 1.7.2 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1 +1,8 @@
1
+ base = File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
2
+ ['vapir-common','vapir-ie','vapir-firefox'].each do |lib|
3
+ libdir = File.join(base, lib, 'lib')
4
+ if File.directory?(libdir) && !$LOAD_PATH.any?{|lp| File.expand_path(lp) == File.expand_path(libdir) }
5
+ $LOAD_PATH.unshift(libdir)
6
+ end
7
+ end
1
8
  require 'vapir-common'
metadata CHANGED
@@ -1,13 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vapir-common
3
3
  version: !ruby/object:Gem::Version
4
- hash: 15
5
4
  prerelease: false
6
5
  segments:
7
6
  - 1
8
- - 7
9
- - 2
10
- version: 1.7.2
7
+ - 8
8
+ - 0
9
+ version: 1.8.0
11
10
  platform: ruby
12
11
  authors:
13
12
  - Ethan
@@ -15,23 +14,10 @@ autorequire:
15
14
  bindir: bin
16
15
  cert_chain: []
17
16
 
18
- date: 2011-01-13 00:00:00 -05:00
17
+ date: 2011-04-19 00:00:00 -04:00
19
18
  default_executable:
20
- dependencies:
21
- - !ruby/object:Gem::Dependency
22
- name: user-choices
23
- prerelease: false
24
- requirement: &id001 !ruby/object:Gem::Requirement
25
- none: false
26
- requirements:
27
- - - ">="
28
- - !ruby/object:Gem::Version
29
- hash: 3
30
- segments:
31
- - 0
32
- version: "0"
33
- type: :runtime
34
- version_requirements: *id001
19
+ dependencies: []
20
+
35
21
  description: " Vapir Common is a library containing common code shared among browser-specific\n Vapir implementations which programatically drive the web browsers,\n exposing a simple-to-use and powerful API to make automated testing a \n simple and joyous affair. \n Forked from the Watir library. \n"
36
22
  email: vapir@googlegroups.com
37
23
  executables: []
@@ -47,22 +33,25 @@ files:
47
33
  - lib/vapir/common.rb
48
34
  - lib/vapir.rb
49
35
  - lib/watir-vapir.rb
36
+ - lib/vapir-common/version.rb
37
+ - lib/vapir-common/config.rb
50
38
  - lib/vapir-common/browser.rb
51
39
  - lib/vapir-common/browsers.rb
52
40
  - lib/vapir-common/container.rb
53
41
  - lib/vapir-common/page_container.rb
54
42
  - lib/vapir-common/modal_dialog.rb
55
43
  - lib/vapir-common/specifier.rb
44
+ - lib/vapir-common/element_class_and_module.rb
56
45
  - lib/vapir-common/element.rb
57
46
  - lib/vapir-common/elements/elements.rb
58
47
  - lib/vapir-common/element_collection.rb
59
48
  - lib/vapir-common/elements.rb
49
+ - lib/vapir-common/keycodes.rb
60
50
  - lib/vapir-common/exceptions.rb
61
51
  - lib/vapir-common/handle_options.rb
62
52
  - lib/vapir-common/options.rb
63
- - lib/vapir-common/testcase.rb
64
53
  - lib/vapir-common/waiter.rb
65
- - lib/vapir-common/win_window.rb
54
+ - lib/vapir-common/external/core_extensions.rb
66
55
  has_rdoc: true
67
56
  homepage: http://www.vapir.org/
68
57
  licenses: []
@@ -78,27 +67,23 @@ rdoc_options:
78
67
  require_paths:
79
68
  - lib
80
69
  required_ruby_version: !ruby/object:Gem::Requirement
81
- none: false
82
70
  requirements:
83
71
  - - ">="
84
72
  - !ruby/object:Gem::Version
85
- hash: 3
86
73
  segments:
87
74
  - 0
88
75
  version: "0"
89
76
  required_rubygems_version: !ruby/object:Gem::Requirement
90
- none: false
91
77
  requirements:
92
78
  - - ">="
93
79
  - !ruby/object:Gem::Version
94
- hash: 3
95
80
  segments:
96
81
  - 0
97
82
  version: "0"
98
83
  requirements: []
99
84
 
100
85
  rubyforge_project:
101
- rubygems_version: 1.3.7
86
+ rubygems_version: 1.3.6
102
87
  signing_key:
103
88
  specification_version: 3
104
89
  summary: Common basis for Vapir libraries for automating web browsers in Ruby
@@ -1,89 +0,0 @@
1
- require 'test/unit'
2
- require 'test/unit/assertions'
3
-
4
- module Vapir
5
- # Verification methods
6
- module Assertions
7
- include Test::Unit::Assertions
8
-
9
- # Log a failure if the boolean is true. The message is the failure
10
- # message logged.
11
- # Whether true or false, the assertion count is incremented.
12
- def verify boolean, message = 'verify failed.'
13
- add_assertion
14
- add_failure message.to_s, caller unless boolean
15
- end
16
-
17
- def verify_equal expected, actual, message=nil
18
- full_message = build_message(message, <<EOT, expected, actual)
19
- <?> expected but was
20
- <?>.
21
- EOT
22
- verify(expected == actual, full_message)
23
- end
24
- def verify_match pattern, string, message=nil
25
- pattern = case(pattern)
26
- when String
27
- Regexp.new(Regexp.escape(pattern))
28
- else
29
- pattern
30
- end
31
- full_message = build_message(message, "<?> expected to be =~\n<?>.", string, pattern)
32
- verify(string =~ pattern, full_message)
33
- end
34
-
35
- end
36
-
37
- class TestCase < Test::Unit::TestCase
38
- include Vapir::Assertions
39
- @@order = :sequentially
40
- def initialize name
41
- throw :invalid_test if name == :default_test && self.class == Vapir::TestCase
42
- super
43
- end
44
- class << self
45
- attr_accessor :test_methods, :order
46
- def test_methods
47
- @test_methods ||= []
48
- end
49
- def order
50
- @order || @@order
51
- end
52
- def default_order= order
53
- @@order = order
54
- end
55
- def sorted_test_methods
56
- case order
57
- when :alphabetically then test_methods.sort
58
- when :sequentially then test_methods
59
- when :reversed_sequentially then test_methods.reverse
60
- when :reversed_alphabetically then test_methods.sort.reverse
61
- else raise ArgumentError, "Execute option not supported: #{@order}"
62
- end
63
- end
64
- def suite
65
- suite = Test::Unit::TestSuite.new(name)
66
- sorted_test_methods.each do |test|
67
- catch :invalid_test do
68
- suite << new(test)
69
- end
70
- end
71
- if (suite.empty?)
72
- catch :invalid_test do
73
- suite << new(:default_test)
74
- end
75
- end
76
- return suite
77
- end
78
- def method_added id
79
- name = id.id2name
80
- test_methods << name if name =~ /^test./
81
- end
82
- def execute order
83
- @order = order
84
- end
85
- end
86
- public :add_assertion
87
- end
88
-
89
- end
@@ -1,1227 +0,0 @@
1
- require 'vapir-common/handle_options'
2
- require 'vapir-common/waiter'
3
-
4
- class WinWindow
5
- # Class that wraps useful methods of user32.dll involving windows in MS Windows (oh wonderful naming)
6
- #
7
- #--
8
- #
9
- # todo:
10
- # * GetTitleBarInfo http://msdn.microsoft.com/en-us/library/ms633513(VS.85).aspx
11
- # * GetWindowInfo http://msdn.microsoft.com/en-us/library/ms633516(VS.85).aspx http://msdn.microsoft.com/en-us/library/ms632610(VS.85).aspx
12
- # * ? ShowOwnedPopups http://msdn.microsoft.com/en-us/library/ms633547(VS.85).aspx
13
- # * FindWindow http://msdn.microsoft.com/en-us/library/ms633499(VS.85).aspx
14
- # * other useful stuff, see http://msdn.microsoft.com/en-us/library/ms632595(VS.85).aspx
15
- # * expand SendMessage / PostMessage http://msdn.microsoft.com/en-us/library/ms644950(VS.85).aspx http://msdn.microsoft.com/en-us/library/ms644944(VS.85).aspx
16
-
17
- class Error < StandardError;end
18
- class SystemError < Error;end
19
- class NotExistsError < Error;end
20
- class MatchError < Error;end
21
-
22
- # this module exists because I've implemented this library for DL, for FFI, and for Win32::API.
23
- # Getting tired of changing everything everywhere, now it just takes changes to Types,
24
- # and a few methods (use_lib, attach, callback) to switch to another library.
25
- module AttachLib
26
- IsWin64=nil # TODO/FIX: detect this!
27
-
28
- #=begin
29
- # here begins the FFI version. this one needs to hack improperly into FFI's internals, bypassing its
30
- # broken API for #callback which doesn't set the calling convention, causing segfaults.
31
- require 'ffi'
32
-
33
- # types that FFI recognizes
34
- Types=[:char, :uchar, :int, :uint, :short, :ushort, :long, :ulong, :void, :pointer, :string].inject({}) do |type_hash, type|
35
- type_hash[type]=type
36
- type_hash
37
- end
38
-
39
- def self.extended(extender)
40
- ffi_module=Module.new
41
- ffi_module.send(:extend, FFI::Library)
42
- extender.send(:instance_variable_set, '@ffi_module', ffi_module)
43
- end
44
- def use_lib(lib)
45
- @ffi_module.ffi_lib lib
46
- @ffi_module.ffi_convention :stdcall
47
- end
48
- # this takes arguments in the order that they're given in c/c++ so that signatures look kind of like the source
49
- def attach(return_type, function_name, *arg_types)
50
- @ffi_module.attach_function(function_name, arg_types.map{|arg_type| Types[arg_type] }, Types[return_type])
51
- metaclass=class << self;self;end
52
- ffi_module=@ffi_module
53
- metaclass.send(:define_method, function_name) do |*args|
54
- ffi_module.send(function_name, *args)
55
- end
56
- nil
57
- end
58
- # this takes arguments like #attach, but with a name for the callback's type on the front.
59
- def callback(callback_type_name, return_type, callback_method_name, *arg_types)
60
-
61
- Types[callback_type_name]=callback_type_name
62
-
63
- #@ffi_module.callback(callback_type_name, arg_types.map{|type| Types[type]}, Types[return_type])
64
-
65
- # we do not call @ffi_module.callback here, because it is broken. we need to pass the convention ourselves in the options hash.
66
- # this is adapted from: http://gist.github.com/256660
67
- options={}
68
- types=Types
69
- @ffi_module.instance_eval do
70
- options[:convention] = defined?(@ffi_convention) ? @ffi_convention : :default
71
- options[:enums] = @ffi_enums if defined?(@ffi_enums)
72
-
73
- cb = FFI::CallbackInfo.new(find_type(types[return_type]), arg_types.map{|e| find_type(types[e]) }, options)
74
-
75
- @ffi_callbacks = Hash.new unless defined?(@ffi_callbacks)
76
- @ffi_callbacks[callback_type_name] = cb
77
- end
78
- #options[:convention] = @ffi_module.instance_variable_defined?('@ffi_convention') ? @ffi_module.instance_variable_get('@ffi_convention') : :default
79
- #options[:enums] = @ffi_module.instance_variable_get('@ffi_enums') if @ffi_module.instance_variable_defined?('@ffi_enums')
80
- #unless @ffi_module.instance_variable_defined?('@ffi_callbacks')
81
- # @ffi_module.instance_variable_set('@ffi_callbacks', cb)
82
- #end
83
-
84
- # perform some hideous class_eval'ing to dynamically define the callback method such that it will take a block
85
- metaclass=class << self;self;end
86
-
87
- # FFI just takes the block itself. don't need anything fancy here.
88
- metaclass.class_eval("def #{callback_method_name}(&block)
89
- block
90
- end
91
- def remove_#{callback_method_name}(callback_method)
92
- # FFI has no support for removing callbacks?
93
- nil
94
- end")
95
- # don't use define_method as this will be called from an ensure block which segfaults ruby 1.9.1. see http://redmine.ruby-lang.org/issues/show/2728
96
- #metaclass.send(:define_method, "remove_"+callback_method_name.to_s) do |callback_method|
97
- # nil
98
- #end
99
- nil
100
- end
101
- #=end
102
- =begin
103
- # here begins the Win32::API version. this one doesn't work because of a hard-coded limit on
104
- # callbacks in win32/api.c combined with a lack of any capacity to remove any callbacks.
105
- require 'win32/api'
106
-
107
- # basic types that Win32::API recognizes
108
- Types={ :char => 'I', # no 8-bit type in Win32::API?
109
- :uchar => 'I', # no unsigned types in Win32::API?
110
- :int => 'I',
111
- :uint => 'I',
112
- :long => 'L',
113
- :ulong => 'L',
114
- :void => 'V',
115
- :pointer => 'P',
116
- :callback => 'K',
117
- :string => 'P', # 'S' works here on mingw32, but not on mswin32
118
- }
119
-
120
- def use_lib(lib)
121
- @lib=lib
122
- end
123
- # this takes arguments in the order that they're given in c/c++ so that signatures look kind of like the source
124
- def attach(return_type, function_name, *arg_types)
125
- the_function=Win32::API.new(function_name.to_s, arg_types.map{|arg_type| Types[arg_type] }.join(''), Types[return_type], @lib)
126
- metaclass=class << self;self;end
127
- metaclass.send(:define_method, function_name) do |*args|
128
- the_function.call(*args)
129
- end
130
- nil
131
- end
132
- # this takes arguments like #attach, but with a name for the callback's type on the front.
133
- def callback(callback_type_name, return_type, callback_method_name, *arg_types)
134
- Types[callback_type_name]=Types[:callback]
135
-
136
- # perform some hideous class_eval'ing to dynamically define the callback method such that it will take a block
137
- metaclass=class << self;self;end
138
- metaclass.class_eval("def #{callback_method_name}(&block)
139
- #{callback_method_name}_with_arg_stuff_in_scope(block)
140
- end")
141
- types=Types
142
- metaclass.send(:define_method, callback_method_name.to_s+"_with_arg_stuff_in_scope") do |block|
143
- return Win32::API::Callback.new(arg_types.map{|t| types[t]}.join(''), types[return_type], &block)
144
- end
145
- def remove_#{callback_method_name}(callback_method)
146
- # Win32::API has no support for removing callbacks?
147
- nil
148
- end")
149
- # don't use define_method as this will be called from an ensure block which segfaults ruby 1.9.1. see http://redmine.ruby-lang.org/issues/show/2728
150
- #metaclass.send(:define_method, "remove_"+callback_method_name.to_s) do |callback_method|
151
- # nil
152
- #end
153
- nil
154
- end
155
- =end
156
- def self.add_type(hash)
157
- hash.each_pair do |key, value|
158
- unless Types.key?(value)
159
- raise "unrecognized type #{value.inspect}"
160
- end
161
- Types[key]=Types[value]
162
- end
163
- end
164
-
165
- end
166
- Types=AttachLib::Types
167
- # types from http://msdn.microsoft.com/en-us/library/aa383751%28VS.85%29.aspx
168
- AttachLib.add_type :buffer_in => :string
169
- AttachLib.add_type :buffer_out => :pointer
170
- AttachLib.add_type :HWND => :ulong # this is a lie. really void*, but easier to deal with as a long.
171
- AttachLib.add_type :HDC => :pointer
172
- AttachLib.add_type :LPSTR => :pointer # char*
173
- AttachLib.add_type :LPWSTR => :pointer # wchar_t*
174
- AttachLib.add_type :LPCSTR => :buffer_in # const char*
175
- AttachLib.add_type :LPCWSTR => :pointer # const wchar_t*
176
- AttachLib.add_type :LONG_PTR => (AttachLib::IsWin64 ? :int64 : :long) # TODO/FIX: there is no :int64 type defined on Win32::API
177
- AttachLib.add_type :LRESULT => :LONG_PTR
178
- AttachLib.add_type :WPARAM => (AttachLib::IsWin64 ? :uint64 : :uint) # TODO/FIX: no :uint64 on Win3::API
179
- AttachLib.add_type :LPARAM => :pointer #:LONG_PTR # this is supposed to be a LONG_PTR (a long type for pointer precision), but casting around is annoying - just going to use it as a pointer.
180
- AttachLib.add_type :BOOL => :int
181
- AttachLib.add_type :BYTE => :uchar
182
- AttachLib.add_type :WORD => :ushort
183
- AttachLib.add_type :DWORD => :ulong
184
- AttachLib.add_type :LPRECT => :pointer
185
- AttachLib.add_type :LPDWORD => :pointer
186
- module WinUser
187
- extend AttachLib
188
- use_lib 'user32'
189
-
190
- attach :int, :GetWindowTextA, :HWND, :LPSTR, :int
191
- attach :int, :GetWindowTextW, :HWND, :LPWSTR, :int
192
- attach :int, :GetWindowTextLengthA, :HWND
193
- attach :int, :GetWindowTextLengthW, :HWND
194
- attach :LRESULT, :SendMessageA, :HWND, :uint, :WPARAM, :LPARAM
195
- attach :LRESULT, :SendMessageW, :HWND, :uint, :WPARAM, :LPARAM
196
- attach :BOOL, :PostMessageA, :HWND, :uint, :WPARAM, :LPARAM
197
- attach :BOOL, :PostMessageW, :HWND, :uint, :WPARAM, :LPARAM
198
- attach :BOOL, :SetWindowTextA, :HWND, :LPCSTR
199
- attach :BOOL, :SetWindowTextW, :HWND, :LPCWSTR
200
- attach :HWND, :GetWindow, :HWND, :uint
201
- attach :HWND, :GetAncestor, :HWND, :uint
202
- attach :HWND, :GetLastActivePopup, :HWND
203
- attach :HWND, :GetTopWindow, :HWND
204
- attach :HWND, :GetParent, :HWND
205
- attach :HWND, :SetParent, :HWND, :HWND
206
- attach :BOOL, :IsChild, :HWND, :HWND
207
- attach :BOOL, :IsHungAppWindow, :HWND
208
- attach :BOOL, :IsWindow, :HWND
209
- attach :BOOL, :IsWindowVisible, :HWND
210
- attach :BOOL, :IsIconic, :HWND
211
- attach :BOOL, :SetForegroundWindow, :HWND
212
- attach :BOOL, :BringWindowToTop, :HWND
213
- attach :BOOL, :CloseWindow, :HWND
214
- attach :BOOL, :DestroyWindow, :HWND
215
- attach :int, :GetClassNameA, :HWND, :LPSTR, :int
216
- attach :int, :GetClassNameW, :HWND, :LPWSTR, :int
217
- attach :uint, :RealGetWindowClassA, :HWND, :LPSTR, :uint
218
- attach :uint, :RealGetWindowClassW, :HWND, :LPWSTR, :uint
219
- attach :DWORD, :GetWindowThreadProcessId, :HWND, :LPDWORD
220
- attach :void, :SwitchToThisWindow, :HWND, :BOOL
221
- attach :BOOL, :LockSetForegroundWindow, :uint
222
- attach :uint, :MapVirtualKeyA, :uint, :uint
223
- attach :uint, :MapVirtualKeyW, :uint, :uint
224
- attach :void, :keybd_event, :BYTE, :BYTE, :DWORD, :pointer
225
- attach :BOOL, :ShowWindow, :HWND, :int
226
- attach :BOOL, :EndTask, :HWND, :BOOL, :BOOL
227
- attach :HWND, :GetForegroundWindow
228
- attach :HWND, :GetDesktopWindow
229
- attach :HDC, :GetDC, :HWND
230
- attach :HDC, :GetWindowDC, :HWND
231
- attach :int, :ReleaseDC, :HWND, :HDC
232
-
233
- class Rect < FFI::Struct
234
- layout :left, :long,
235
- :top, :long,
236
- :right, :long,
237
- :bottom, :long
238
- end
239
- attach :BOOL, :GetWindowRect, :HWND, :LPRECT
240
- attach :BOOL, :GetClientRect, :HWND, :LPRECT
241
-
242
- callback :WNDENUMPROC, :BOOL, :window_enum_callback, :HWND, :LPARAM
243
- attach :BOOL, :EnumWindows, :WNDENUMPROC, :LPARAM
244
- attach :BOOL, :EnumChildWindows, :HWND, :WNDENUMPROC, :LPARAM
245
- end
246
- AttachLib.add_type :SIZE_T => (AttachLib::IsWin64 ? :uint64 : :ulong)
247
-
248
- module WinKernel
249
- extend AttachLib
250
- use_lib 'kernel32'
251
- attach :DWORD, :GetLastError
252
- attach :DWORD, :FormatMessageA, :DWORD, :pointer, :DWORD, :DWORD, :LPSTR, :DWORD
253
- attach :DWORD, :FormatMessageW, :DWORD, :pointer, :DWORD, :DWORD, :LPWSTR, :DWORD
254
-
255
- attach :pointer, :GlobalAlloc, :uint, :SIZE_T
256
- attach :pointer, :GlobalFree, :pointer
257
- attach :pointer, :GlobalLock, :pointer
258
- attach :pointer, :GlobalUnlock, :pointer
259
- end
260
- AttachLib.add_type :HGDIOBJ => :pointer
261
- AttachLib.add_type :HBITMAP => :pointer
262
- AttachLib.add_type :LPBITMAPINFO => :pointer
263
- module WinGDI
264
- extend AttachLib
265
- use_lib 'gdi32'
266
- attach :HDC, :CreateCompatibleDC, :HDC
267
- attach :BOOL, :DeleteDC, :HDC
268
- attach :int, :GetDeviceCaps, :HDC, :int
269
- attach :HBITMAP, :CreateCompatibleBitmap, :HDC, :int, :int
270
- attach :HGDIOBJ, :SelectObject, :HDC, :HGDIOBJ
271
- attach :BOOL, :DeleteObject, :HGDIOBJ
272
- attach :BOOL, :BitBlt, :HDC, :int, :int, :int, :int, :HDC, :int, :int, :DWORD
273
- attach :int, :GetDIBits, :HDC, :HBITMAP, :uint, :uint, :pointer, :LPBITMAPINFO, :uint
274
-
275
- class BITMAPINFOHEADER < FFI::Struct
276
- layout(
277
- :Size, :int32,
278
- :Width, :int32,
279
- :Height, :int32,
280
- :Planes, :int16,
281
- :BitCount, :int16,
282
- :Compression, :int32,
283
- :SizeImage, :int32,
284
- :XPelsPerMeter, :int32,
285
- :YPelsPerMeter, :int32,
286
- :ClrUsed, :int32,
287
- :ClrImportant, :int32
288
- )
289
- end
290
- class BITMAPFILEHEADER < FFI::Struct
291
- layout(
292
- :Type, :int16, 0,
293
- :Size, :int32, 2,
294
- :Reserved1, :int16, 6,
295
- :Reserved2, :int16, 8,
296
- :OffBits, :int32, 10
297
- )
298
- end
299
- # for some reason size is returned as 16; should be 14
300
- class << FFI::Struct
301
- def real_size
302
- layout.fields.inject(0) do |sum, field|
303
- sum+field.size
304
- end
305
- end
306
- end
307
- end
308
-
309
- WM_CLOSE = 0x0010
310
- WM_KEYDOWN = 0x0100
311
- WM_KEYUP = 0x0101
312
- WM_CHAR = 0x0102
313
- BM_CLICK = 0x00F5
314
- WM_COMMAND = 0x0111
315
- WM_SETTEXT = 0x000C
316
- WM_GETTEXT = 0x000D
317
- WM_GETTEXTLENGTH = 0xE
318
-
319
- #--
320
- # GetWindows constants
321
- GW_HWNDFIRST = 0
322
- GW_HWNDLAST = 1
323
- GW_HWNDNEXT = 2
324
- GW_HWNDPREV = 3
325
- GW_OWNER = 4
326
- GW_CHILD = 5
327
- GW_ENABLEDPOPUP = 6
328
- GW_MAX = 6
329
-
330
- #--
331
- # GetAncestor constants
332
- GA_PARENT = 1
333
- GA_ROOT = 2
334
- GA_ROOTOWNER = 3
335
-
336
- #--
337
- # ShowWindow constants - http://msdn.microsoft.com/en-us/library/ms633548(VS.85).aspx
338
- SW_HIDE = 0 # Hides the window and activates another window.
339
- SW_SHOWNORMAL = 1 # Activates and displays a window. If the window is minimized or maximized, the system restores it to its original size and position. An application should specify this flag when displaying the window for the first time.
340
- SW_SHOWMINIMIZED = 2 # Activates the window and displays it as a minimized window.
341
- SW_SHOWMAXIMIZED = 3 # Activates the window and displays it as a maximized window.
342
- SW_MAXIMIZE = 3 # Maximizes the specified window.
343
- #--
344
- # there seems to be no distinct SW_MAXIMIZE (but there is a distinct SW_MINIMIZE), just the same as SW_SHOWMAXIMIZED
345
- # some references define SW_MAXIMIZE as 11, which seems to just be wrong; that is correctly SW_FORCEMINIMIZE
346
- SW_SHOWNOACTIVATE = 4 # Displays a window in its most recent size and position. This value is similar to SW_SHOWNORMAL, except the window is not actived.
347
- SW_SHOW = 5 # Activates the window and displays it in its current size and position.
348
- SW_MINIMIZE = 6 # Minimizes the specified window and activates the next top-level window in the Z order.
349
- SW_SHOWMINNOACTIVE = 7 # Displays the window as a minimized window. This value is similar to SW_SHOWMINIMIZED, except the window is not activated.
350
- SW_SHOWNA = 8 # Displays the window in its current size and position. This value is similar to SW_SHOW, except the window is not activated.
351
- SW_RESTORE = 9 # Activates and displays the window. If the window is minimized or maximized, the system restores it to its original size and position. An application should specify this flag when restoring a minimized window.
352
- SW_SHOWDEFAULT = 10 # Sets the show state based on the SW_ value specified in the STARTUPINFO structure passed to the CreateProcess function by the program that started the application.
353
- SW_FORCEMINIMIZE = 11 # Windows 2000/XP: Minimizes a window, even if the thread that owns the window is not responding. This flag should only be used when minimizing windows from a different thread.
354
-
355
- WIN_TRUE=-1
356
- WIN_FALSE=0
357
-
358
- attr_reader :hwnd
359
-
360
- # creates a WinWindow from a given hWnd handle (integer)
361
- #
362
- # raises ArgumentError if the hWnd is not a Fixnum greater than 0
363
- def initialize(hwnd)
364
- raise ArgumentError, "hwnd must be an integer greater than 0; got #{hwnd.inspect} (#{hwnd.class})" unless hwnd.is_a?(Integer) && hwnd > 0
365
- @hwnd=hwnd
366
- end
367
-
368
- def inspect
369
- retrieve_text
370
- class_name
371
- Object.instance_method(:inspect).bind(self).call
372
- end
373
-
374
- def pretty_print(pp)
375
- retrieve_text
376
- class_name
377
- pp.pp_object(self)
378
- end
379
-
380
- # retrieves the text of this window's title bar (if it has one). If this is a control, the text of the control is retrieved.
381
- # However, #text cannot retrieve the text of a control in another application (see #retrieve_text)
382
- #
383
- # http://msdn.microsoft.com/en-us/library/ms633520(VS.85).aspx
384
- def text
385
- buff_size=text_length+1
386
- buff="\001"*buff_size
387
- len= WinUser.GetWindowTextA(hwnd, buff, buff_size)
388
- @text=buff[0...len]
389
- end
390
-
391
- # length of the window text, see #text
392
- # similar to #text, cannot retrieve the text of a control in another application - see #retrieve_text, #retrieve_text_length
393
- #
394
- # http://msdn.microsoft.com/en-us/library/ms633521(VS.85).aspx
395
- def text_length
396
- len= WinUser.GetWindowTextLengthA(hwnd)
397
- len
398
- end
399
-
400
- # This is similar to #text
401
- # that one is GetWindowText(hwnd)
402
- # this one is SendMessage(hwnd, WM_GETTEXT)
403
- # differences are documented here: http://msdn.microsoft.com/en-us/magazine/cc301438.aspx
404
- # and here: http://blogs.msdn.com/oldnewthing/archive/2003/08/21/54675.aspx
405
- def retrieve_text
406
- buff_size=retrieve_text_length+1
407
- buff=" "*buff_size
408
- len= WinUser.SendMessageA(hwnd, WM_GETTEXT, buff_size, buff)
409
- @text=buff[0...len]
410
- end
411
-
412
- # similar to #text_length; differences between that and this are the same as between #text and #retrieve_text
413
- def retrieve_text_length
414
- len= WinUser.SendMessageA(hwnd, WM_GETTEXTLENGTH, 0, nil)
415
- len
416
- end
417
-
418
- # changes the text of the specified window's title bar (if it has one). If the specified window is a control, the text of the control is changed.
419
- # However, #set_text! cannot change the text of a control in another application (see #send_set_text!)
420
- #
421
- # http://msdn.microsoft.com/en-us/library/ms633546(VS.85).aspx
422
- def set_text!(text)
423
- set=WinUser.SetWindowTextA(hwnd, text)
424
- set != WIN_FALSE
425
- end
426
-
427
- # sets text by sending WM_SETTEXT message. this different than #set_text! in the same way that
428
- # #retrieve_text is different than #text
429
- def send_set_text!(text)
430
- ret=WinUser.SendMessageA(hwnd, WM_SETTEXT, 0, text.dup)
431
- nil
432
- end
433
-
434
- # The retrieved handle identifies the enabled popup window owned by the specified window
435
- # (the search uses the first such window found using GW_HWNDNEXT); otherwise, if there
436
- # are no enabled popup windows, nil is returned.
437
- #
438
- # http://msdn.microsoft.com/en-us/library/ms633515(VS.85).aspx
439
- def enabled_popup
440
- popup_hwnd=WinUser.GetWindow(hwnd, GW_ENABLEDPOPUP)
441
- @enabled_popup= popup_hwnd > 0 && popup_hwnd != self.hwnd ? self.class.new(popup_hwnd) : nil
442
- end
443
-
444
- # The retrieved handle identifies the specified window's owner window, if any.
445
- #
446
- # http://msdn.microsoft.com/en-us/library/ms633515(VS.85).aspx
447
- def owner
448
- owner_hwnd=WinUser.GetWindow(hwnd, GW_OWNER)
449
- @owner= owner_hwnd > 0 ? self.class.new(owner_hwnd) : nil
450
- end
451
-
452
- # Retrieves the parent window. This does not include the owner, as it does with #parent
453
- #
454
- # http://msdn.microsoft.com/en-us/library/ms633502(VS.85).aspx
455
- def ancestor_parent
456
- ret_hwnd=WinUser.GetAncestor(hwnd, GA_PARENT)
457
- @ancestor_parent= ret_hwnd > 0 ? self.class.new(ret_hwnd) : nil
458
- end
459
-
460
- # Retrieves the root window by walking the chain of parent windows.
461
- #
462
- # http://msdn.microsoft.com/en-us/library/ms633502(VS.85).aspx
463
- def ancestor_root
464
- ret_hwnd=WinUser.GetAncestor(hwnd, GA_ROOT)
465
- @ancestor_root= ret_hwnd > 0 ? self.class.new(ret_hwnd) : nil
466
- end
467
-
468
- # Retrieves the owned root window by walking the chain of parent and owner windows returned by GetParent.
469
- #
470
- # http://msdn.microsoft.com/en-us/library/ms633502(VS.85).aspx
471
- def ancestor_root_owner
472
- ret_hwnd=WinUser.GetAncestor(hwnd, GA_ROOTOWNER)
473
- @ancestor_root_owner= ret_hwnd > 0 ? self.class.new(ret_hwnd) : nil
474
- end
475
-
476
- # determines which pop-up window owned by this window was most recently active
477
- #
478
- # http://msdn.microsoft.com/en-us/library/ms633507(VS.85).aspx
479
- def last_active_popup
480
- ret_hwnd=WinUser.GetLastActivePopup(hwnd)
481
- @last_active_popup= ret_hwnd > 0 ? self.class.new(ret_hwnd) : nil
482
- end
483
-
484
- # examines the Z order of the child windows associated with self and retrieves a handle to the child window at the top of the Z order
485
- #
486
- # http://msdn.microsoft.com/en-us/library/ms633514(VS.85).aspx
487
- def top_window
488
- ret_hwnd= WinUser.GetTopWindow(hwnd)
489
- @top_window= ret_hwnd > 0 ? self.class.new(ret_hwnd) : nil
490
- end
491
-
492
- # retrieves a handle to this window's parent or owner
493
- #
494
- # http://msdn.microsoft.com/en-us/library/ms633510(VS.85).aspx
495
- def parent
496
- parent_hwnd=WinUser.GetParent(hwnd)
497
- @parent= parent_hwnd > 0 ? self.class.new(parent_hwnd) : nil
498
- end
499
-
500
- # changes the parent window of this child window
501
- #
502
- # http://msdn.microsoft.com/en-us/library/ms633541(VS.85).aspx
503
- def set_parent!(parent)
504
- parent_hwnd= parent.is_a?(self.class) ? parent.hwnd : parent
505
- new_parent=WinUser.SetParent(hwnd, parent_hwnd)
506
- new_parent > 0 ? self.class.new(new_parent) : nil
507
- end
508
-
509
- # tests whether a window is a child window or descendant window of a specified parent window. A child window is the direct descendant of a specified parent window if that parent window is in the chain of parent windows; the chain of parent windows leads from the original overlapped or pop-up window to the child window.
510
- #
511
- # http://msdn.microsoft.com/en-us/library/ms633524(VS.85).aspx
512
- def child_of?(parent)
513
- parent_hwnd= parent.is_a?(self.class) ? parent.hwnd : parent
514
- child=WinUser.IsChild(parent_hwnd, hwnd)
515
- child!=WIN_FALSE
516
- end
517
-
518
- # determine if Microsoft Windows considers that a specified application is not responding. An application is considered to be not responding if it is not waiting for input, is not in startup processing, and has not called PeekMessage within the internal timeout period of 5 seconds.
519
- #
520
- # http://msdn.microsoft.com/en-us/library/ms633526.aspx
521
- def hung_app?
522
- hung=WinUser.IsHungAppWindow(hwnd)
523
- hung != WIN_FALSE
524
- end
525
-
526
- # retrieves the name of the class to which this window belongs
527
- #
528
- # http://msdn.microsoft.com/en-us/library/ms633582(VS.85).aspx
529
- def class_name
530
- buff_size=256
531
- buff=" "*buff_size
532
- len=WinUser.GetClassNameA(hwnd, buff, buff_size)
533
- @class_name=buff.to_s[0...len]
534
- end
535
-
536
- # retrieves a string that specifies the window type
537
- #
538
- # http://msdn.microsoft.com/en-us/library/ms633538(VS.85).aspx
539
- def real_class_name
540
- buff_size=256
541
- buff=" "*buff_size
542
- len=WinUser.RealGetWindowClassA(hwnd, buff, buff_size)
543
- @real_class_name=buff.to_s[0...len]
544
- end
545
-
546
- # returns the identifier of the thread that created the window
547
- #
548
- # http://msdn.microsoft.com/en-us/library/ms633522%28VS.85%29.aspx
549
- def thread_id
550
- WinUser.GetWindowThreadProcessId(hwnd, nil)
551
- end
552
-
553
- # returns the process identifier that created this window
554
- #
555
- # http://msdn.microsoft.com/en-us/library/ms633522%28VS.85%29.aspx
556
- def process_id
557
- lpdwProcessId=FFI::MemoryPointer.new(Types[:LPDWORD])
558
- WinUser.GetWindowThreadProcessId(hwnd, lpdwProcessId)
559
- lpdwProcessId.get_ulong(0)
560
- end
561
-
562
- # determines whether the specified window handle identifies an existing window
563
- #
564
- # http://msdn.microsoft.com/en-us/library/ms633528(VS.85).aspx
565
- def exists?
566
- ret=WinUser.IsWindow(hwnd)
567
- ret != WIN_FALSE
568
- end
569
-
570
- # visibility state of the specified window
571
- #
572
- # http://msdn.microsoft.com/en-us/library/ms633530(VS.85).aspx
573
- def visible?
574
- ret=WinUser.IsWindowVisible(hwnd)
575
- ret != WIN_FALSE
576
- end
577
-
578
- # whether the window is minimized (iconic).
579
- #
580
- # http://msdn.microsoft.com/en-us/library/ms633527(VS.85).aspx
581
- def iconic?
582
- ret=WinUser.IsIconic(hwnd)
583
- ret != WIN_FALSE
584
- end
585
- alias minimized? iconic?
586
-
587
- # switch focus and bring to the foreground
588
- # the argument alt_tab, if true, indicates that the window is being switched to using the Alt/Ctl+Tab key sequence. This argument should be false otherwise.
589
- #
590
- # http://msdn.microsoft.com/en-us/library/ms633553(VS.85).aspx
591
- def switch_to!(alt_tab=false)
592
- WinUser.SwitchToThisWindow(hwnd, alt_tab ? WIN_TRUE : WIN_FALSE)
593
- end
594
-
595
- # puts the thread that created the specified window into the foreground and activates the window. Keyboard input is directed to the window, and various visual cues are changed for the user. The system assigns a slightly higher priority to the thread that created the foreground window than it does to other threads.
596
- # If the window was brought to the foreground, the return value is true.
597
- # If the window was not brought to the foreground, the return value is false.
598
- #
599
- # http://msdn.microsoft.com/en-us/library/ms633539(VS.85).aspx
600
- def set_foreground!
601
- ret= WinUser.SetForegroundWindow(hwnd)
602
- ret != WIN_FALSE
603
- end
604
-
605
- def foreground?
606
- self==self.class.foreground_window
607
- end
608
-
609
-
610
- LSFW_LOCK = 1
611
- LSFW_UNLOCK = 2
612
- def self.lock_set_foreground_window
613
- ret= WinUser.LockSetForegroundWindow(LSFW_LOCK)
614
- ret != WIN_FALSE
615
- end
616
- def self.unlock_set_foreground_window
617
- ret= WinUser.LockSetForegroundWindow(LSFW_UNLOCK)
618
- ret != WIN_FALSE
619
- end
620
-
621
- VK_MENU=0x12
622
- KEYEVENTF_KEYDOWN=0x0
623
- KEYEVENTF_KEYUP=0x2
624
- # really sets this to be the foreground window.
625
- # restores the window if it's iconic.
626
- # attempts to circumvent a lock disabling calls made by set_foreground!
627
- # then calls set_foreground!, which should then work with that lock disabled.
628
- # tries this for a few seconds, checking if it was successful.
629
- #
630
- # if you want it to raise an exception if it can't set the foreground window,
631
- # pass :error => true (default is false)
632
- def really_set_foreground!(options={})
633
- options=handle_options(options, :error => false)
634
- try_harder=false
635
- mapped_vk_menu=WinUser.MapVirtualKeyA(VK_MENU, 0)
636
- ::Waiter.try_for(2, :exception => (options[:error] && WinWindow::Error.new("Failed to set foreground window"))) do
637
- if iconic?
638
- restore!
639
- end
640
- if try_harder
641
- # Simulate two single ALT keystrokes in order to deactivate lock on SetForeGroundWindow before we call it.
642
- # See LockSetForegroundWindow, http://msdn.microsoft.com/en-us/library/ms633532(VS.85).aspx
643
- # also keybd_event, see http://msdn.microsoft.com/en-us/library/ms646304(VS.85).aspx
644
- #
645
- # this idea is taken from AutoIt's setforegroundwinex.cpp in SetForegroundWinEx::Activate(HWND hWnd)
646
- # keybd_event((BYTE)VK_MENU, MapVirtualKey(VK_MENU, 0), 0, 0);
647
- # keybd_event((BYTE)VK_MENU, MapVirtualKey(VK_MENU, 0), KEYEVENTF_KEYUP, 0);
648
- 2.times do
649
- ret=WinUser.keybd_event(VK_MENU, mapped_vk_menu, KEYEVENTF_KEYDOWN, nil)
650
- ret=WinUser.keybd_event(VK_MENU, mapped_vk_menu, KEYEVENTF_KEYUP, nil)
651
- end
652
- else
653
- try_harder=true
654
- end
655
- set_foreground!
656
- foreground?
657
- end
658
- end
659
-
660
- # brings the window to the top of the Z order. If the window is a top-level window, it is activated. If the window is a child window, the top-level parent window associated with the child window is activated.
661
- #
662
- # http://msdn.microsoft.com/en-us/library/ms632673(VS.85).aspx
663
- def bring_to_top!
664
- ret=WinUser.BringWindowToTop(hwnd)
665
- ret != WIN_FALSE
666
- end
667
-
668
- # minimizes this window (but does not destroy it)
669
- # (why is it called close? I don't know)
670
- #
671
- # http://msdn.microsoft.com/en-us/library/ms632678(VS.85).aspx
672
- def close!
673
- ret=WinUser.CloseWindow(hwnd)
674
- ret != WIN_FALSE
675
- end
676
-
677
- # Hides the window and activates another window.
678
- #
679
- # http://msdn.microsoft.com/en-us/library/ms633548(VS.85).aspx
680
- def hide!
681
- ret=WinUser.ShowWindow(hwnd, SW_HIDE)
682
- ret != WIN_FALSE
683
- end
684
-
685
- # Activates and displays a window. If the window is minimized or maximized, the system restores it to its original size and position. An application should specify this flag when displaying the window for the first time.
686
- #
687
- # http://msdn.microsoft.com/en-us/library/ms633548(VS.85).aspx
688
- def show_normal!
689
- ret=WinUser.ShowWindow(hwnd, SW_SHOWNORMAL)
690
- ret != WIN_FALSE
691
- end
692
-
693
- # Activates the window and displays it as a minimized window.
694
- #
695
- # http://msdn.microsoft.com/en-us/library/ms633548(VS.85).aspx
696
- def show_minimized!
697
- ret=WinUser.ShowWindow(hwnd, SW_SHOWMINIMIZED)
698
- ret != WIN_FALSE
699
- end
700
-
701
- # Activates the window and displays it as a maximized window. (note: exact same as maximize!)
702
- #
703
- # http://msdn.microsoft.com/en-us/library/ms633548(VS.85).aspx
704
- def show_maximized!
705
- ret=WinUser.ShowWindow(hwnd, SW_SHOWMAXIMIZED)
706
- ret != WIN_FALSE
707
- end
708
-
709
- # Maximizes this window. (note: exact same as show_maximized!)
710
- #
711
- # http://msdn.microsoft.com/en-us/library/ms633548(VS.85).aspx
712
- def maximize!
713
- ret=WinUser.ShowWindow(hwnd, SW_MAXIMIZE)
714
- ret != WIN_FALSE
715
- end
716
-
717
- # Displays the window in its most recent size and position. This is similar to show_normal!, except the window is not actived.
718
- #
719
- # http://msdn.microsoft.com/en-us/library/ms633548(VS.85).aspx
720
- def show_no_activate!
721
- ret=WinUser.ShowWindow(hwnd, SW_SHOWNOACTIVATE)
722
- ret != WIN_FALSE
723
- end
724
-
725
- # Activates the window and displays it in its current size and position.
726
- #
727
- # http://msdn.microsoft.com/en-us/library/ms633548(VS.85).aspx
728
- def show!
729
- ret=WinUser.ShowWindow(hwnd, SW_SHOW)
730
- ret != WIN_FALSE
731
- end
732
-
733
- # Minimizes this window and activates the next top-level window in the Z order.
734
- #
735
- # http://msdn.microsoft.com/en-us/library/ms633548(VS.85).aspx
736
- def minimize!
737
- ret=WinUser.ShowWindow(hwnd, SW_MINIMIZE)
738
- ret != WIN_FALSE
739
- end
740
-
741
- # Displays the window as a minimized window. This is similar to show_minimized!, except the window is not activated.
742
- #
743
- # http://msdn.microsoft.com/en-us/library/ms633548(VS.85).aspx
744
- def show_min_no_active!
745
- ret=WinUser.ShowWindow(hwnd, SW_SHOWMINNOACTIVE)
746
- ret != WIN_FALSE
747
- end
748
-
749
- # Displays the window in its current size and position. This is similar to show!, except the window is not activated.
750
- #
751
- # http://msdn.microsoft.com/en-us/library/ms633548(VS.85).aspx
752
- def show_na!
753
- ret=WinUser.ShowWindow(hwnd, SW_SHOWNA)
754
- ret != WIN_FALSE
755
- end
756
-
757
- # Activates and displays the window. If the window is minimized or maximized, the system restores it to its original size and position. An application should use this when restoring a minimized window.
758
- #
759
- # http://msdn.microsoft.com/en-us/library/ms633548(VS.85).aspx
760
- def restore!
761
- ret=WinUser.ShowWindow(hwnd, SW_RESTORE)
762
- ret != WIN_FALSE
763
- end
764
-
765
- # Sets the show state based on the SW_ value specified in the STARTUPINFO structure passed to the CreateProcess function by the program that started the application.
766
- #
767
- # http://msdn.microsoft.com/en-us/library/ms633548(VS.85).aspx
768
- def show_default!
769
- ret=WinUser.ShowWindow(hwnd, SW_SHOWDEFAULT)
770
- ret != WIN_FALSE
771
- end
772
-
773
- # Windows 2000/XP: Minimizes the window, even if the thread that owns the window is not responding. This should only be used when minimizing windows from a different thread.
774
- #
775
- # http://msdn.microsoft.com/en-us/library/ms633548(VS.85).aspx
776
- def force_minimize!
777
- ret=WinUser.ShowWindow(hwnd, SW_FORCEMINIMIZE)
778
- ret != WIN_FALSE
779
- end
780
-
781
- # destroy! destroys the window. #destroy! sends WM_DESTROY and WM_NCDESTROY messages to the window to deactivate it and remove the keyboard focus from it. #destroy! also destroys the window's menu, flushes the thread message queue, destroys timers, removes clipboard ownership, and breaks the clipboard viewer chain (if the window is at the top of the viewer chain).
782
- # If the specified window is a parent or owner window, #destroy! automatically destroys the associated child or owned windows when it destroys the parent or owner window. #destroy! first destroys child or owned windows, and then it destroys the parent or owner window.
783
- # #destroy! also destroys modeless dialog boxes.
784
- #
785
- # http://msdn.microsoft.com/en-us/library/ms632682(VS.85).aspx
786
- def destroy!
787
- ret=WinUser.DestroyWindow(hwnd)
788
- ret != WIN_FALSE
789
- end
790
-
791
- # called to forcibly close the window.
792
- # the argument force, if true, will force the destruction of the window if an initial attempt fails to gently close the window using WM_CLOSE.
793
- # if false, only the close with WM_CLOSE is attempted
794
- #
795
- # http://msdn.microsoft.com/en-us/library/ms633492(VS.85).aspx
796
- def end_task!(force=false)
797
- ret=WinUser.EndTask(hwnd, 0, force ? WIN_TRUE : WIN_FALSE)
798
- ret != WIN_FALSE
799
- end
800
-
801
- # sends notification that the window should close.
802
- # returns nil (we get no indication of success or failure).
803
- #
804
- # http://msdn.microsoft.com/en-us/library/ms632617%28VS.85%29.aspx
805
- def send_close!
806
- buff_size=0
807
- buff=""
808
- len=WinUser.SendMessageA(hwnd, WM_CLOSE, buff_size, buff)
809
- nil
810
- end
811
-
812
- # tries to click on this Window
813
- # Clicking might not always work! Especially if the window is not focused (frontmost application).
814
- # The BM_CLICK message might just be ignored, or maybe it will just focus the hwnd but not really click.
815
- def click!
816
- WinUser.PostMessageA(hwnd, BM_CLICK, 0, nil)
817
- end
818
-
819
- # Returns a Rect struct with members left, top, right, and bottom indicating the dimensions of the bounding rectangle of the specified window. The dimensions are given in screen coordinates that are relative to the upper-left corner of the screen.
820
- #
821
- # http://msdn.microsoft.com/en-us/library/ms633519%28VS.85%29.aspx
822
- def window_rect
823
- rect=WinUser::Rect.new
824
- ret=WinUser.GetWindowRect(hwnd, rect)
825
- if ret==WIN_FALSE
826
- self.class.system_error "GetWindowRect"
827
- else
828
- rect
829
- end
830
- end
831
- # Returns a Rect struct with members left, top, right, and bottom indicating the coordinates of a window's client area. The client coordinates specify the upper-left and lower-right corners of the client area. Because client coordinates are relative to the upper-left corner of a window's client area, the coordinates of the upper-left corner are (0,0).
832
- #
833
- # http://msdn.microsoft.com/en-us/library/ms633503%28VS.85%29.aspx
834
- def client_rect
835
- rect=WinUser::Rect.new
836
- ret=WinUser.GetClientRect(hwnd, rect)
837
- if ret==WIN_FALSE
838
- self.class.system_error "GetClientRect"
839
- else
840
- rect
841
- end
842
- end
843
-
844
- SRCCOPY = 0xCC0020
845
- DIB_RGB_COLORS = 0x0
846
- GMEM_FIXED = 0x0
847
-
848
- # Creates a bitmap image of this window (a screenshot).
849
- # Returns the bitmap as represented by three FFI objects: a BITMAPFILEHEADER, a BITMAPINFOHEADER, and a
850
- # pointer to actual bitmap data.
851
- # See also #capture_to_bmp_blob and #capture_to_bmp_file - probably more useful to the user than this method.
852
- #
853
- # takes an options hash:
854
- # - :dc => what device context to use
855
- # :client - captures the client area, which excludes window trimmings like title bar, resize bars, etc.
856
- # :window (default) - capturse the window area, including window trimmings.
857
- # - :set_foreground => whether to try to set this to be the foreground
858
- # true - calls to #set_foreground
859
- # false - doesn't call to any functions to set this to be the foreground
860
- # :really (default) - calls to #really_set_foreground. this is the default because really being
861
- # in the foreground is rather important when taking a screenshot.
862
- def capture_to_bmp_structs(options={})
863
- options=handle_options(options, :dc => :window, :set_foreground => :really)
864
- case options[:set_foreground]
865
- when :really
866
- really_set_foreground!
867
- when true
868
- set_foreground!
869
- when false,nil
870
- else
871
- raise ArgumentError, ":set_foreground option is invalid. expected values are :really, true, or false/nil. received #{options[:set_foreground]} (#{options[:set_foreground].class})"
872
- end
873
- if options[:set_foreground]
874
- sleep 0.2 # if setting foreground, sleep a tick - sometimes it still hasn't show up even when it is the foreground window; sometimes it's still only partway-drawn
875
- end
876
- case options[:dc]
877
- when :client
878
- rect=self.client_rect
879
- dc=WinUser.GetDC(hwnd) || system_error("GetDC")
880
- when :window
881
- rect=self.window_rect
882
- dc=WinUser.GetWindowDC(hwnd) || system_error("GetWindowDC")
883
- else
884
- raise ArgumentError, ":dc option is invalid. expected values are :client or :window; received #{options[:dc]} (#{options[:dc].class})"
885
- end
886
- width=rect[:right]-rect[:left]
887
- height=rect[:bottom]-rect[:top]
888
- begin
889
- dc_mem = WinGDI.CreateCompatibleDC(dc) || system_error("CreateCompatibleDC")
890
- begin
891
- bmp = WinGDI.CreateCompatibleBitmap(dc, width, height) || system_error("CreateCompatibleBitmap")
892
- begin
893
- WinGDI.SelectObject(dc_mem, bmp) || system_error("SelectObject")
894
- WinGDI.BitBlt(dc_mem, 0, 0, width, height, dc, 0, 0, SRCCOPY) || system_error("BitBlt")
895
-
896
- bytes_per_pixel=3
897
-
898
- bmp_info=WinGDI::BITMAPINFOHEADER.new
899
- { :Size => WinGDI::BITMAPINFOHEADER.real_size, # 40
900
- :Width => width,
901
- :Height => height,
902
- :Planes => 1,
903
- :BitCount => bytes_per_pixel*8,
904
- :Compression => 0,
905
- :SizeImage => 0,
906
- :XPelsPerMeter => 0,
907
- :YPelsPerMeter => 0,
908
- :ClrUsed => 0,
909
- :ClrImportant => 0,
910
- }.each_pair do |key,val|
911
- bmp_info[key]=val
912
- end
913
- bmp_row_size=width*bytes_per_pixel
914
- bmp_row_size+=bmp_row_size%4 # row size must be a multiple of 4 (size of a dword)
915
- bmp_size=bmp_row_size*height
916
-
917
- bits=FFI::MemoryPointer.new(1, bmp_size)
918
-
919
- WinGDI.GetDIBits(dc_mem, bmp, 0, height, bits, bmp_info, DIB_RGB_COLORS) || system_error("GetDIBits")
920
-
921
- bmp_file_header=WinGDI::BITMAPFILEHEADER.new
922
- { :Type => 'BM'.unpack('S').first, # must be 'BM'
923
- :Size => WinGDI::BITMAPFILEHEADER.real_size + WinGDI::BITMAPINFOHEADER.real_size + bmp_size,
924
- :Reserved1 => 0,
925
- :Reserved2 => 0,
926
- :OffBits => WinGDI::BITMAPFILEHEADER.real_size + WinGDI::BITMAPINFOHEADER.real_size
927
- }.each_pair do |key,val|
928
- bmp_file_header[key]=val
929
- end
930
- return [bmp_file_header, bmp_info, bits]
931
- ensure
932
- WinGDI.DeleteObject(bmp)
933
- end
934
- ensure
935
- WinGDI.DeleteDC(dc_mem)
936
- end
937
- ensure
938
- WinUser.ReleaseDC(hwnd, dc)
939
- end
940
- end
941
- # captures this window to a bitmap image (a screenshot).
942
- # Returns the bitmap as represented by a blob (a string) of bitmap data, including the BITMAPFILEHEADER,
943
- # BITMAPINFOHEADER, and data. This can be written directly to a file (though if you want that,
944
- # #capture_to_bmp_file is probably what you want), or passed to ImageMagick, or whatever you like.
945
- #
946
- # takes an options hash. see the documentation on #capture_to_bmp_structs for what options are accepted.
947
- def capture_to_bmp_blob(options={})
948
- capture_to_bmp_structs(options).map do |struct|
949
- if struct.is_a?(FFI::Pointer)
950
- ptr=struct
951
- size=ptr.size
952
- else
953
- ptr=struct.to_ptr
954
- size=struct.class.real_size
955
- end
956
- ptr.get_bytes(0, size)
957
- end.join("")
958
- end
959
-
960
- # captures this window to a bitmap image (a screenshot).
961
- # stores the bitmap to a filename specified in the first argument.
962
- #
963
- # takes an options hash. see the documentation on #capture_to_bmp_structs for what options are accepted.
964
- def capture_to_bmp_file(filename, options={})
965
- File.open(filename, 'wb') do |file|
966
- file.write(capture_to_bmp_blob(options))
967
- end
968
- end
969
-
970
- private
971
- FORMAT_MESSAGE_FROM_SYSTEM=0x00001000
972
- # get the last error from GetLastError, format an error message with FormatMessage, and raise a WinWindow::SystemError
973
- def self.system_error(function)
974
- code=WinKernel.GetLastError
975
-
976
- dwFlags=FORMAT_MESSAGE_FROM_SYSTEM
977
- buff_size=65535
978
- buff="\1"*buff_size
979
- len=WinKernel.FormatMessageA(dwFlags, nil, code, 0, buff, buff_size)
980
- system_error_message=buff[0...len]
981
- raise WinWindow::SystemError, "#{function} encountered an error\nSystem Error Code #{code}\n"+system_error_message
982
- end
983
- public
984
-
985
- # iterates over each child, yielding a WinWindow object.
986
- # raises a WinWindow::NotExistsError if the window does not exist, or a WinWindow::SystemError if a System Error errors.
987
- # use #children to get an Enumerable object.
988
- #
989
- # http://msdn.microsoft.com/en-us/library/ms633494(VS.85).aspx
990
- #
991
- # For System Error Codes see http://msdn.microsoft.com/en-us/library/ms681381(VS.85).aspx
992
- def each_child
993
- raise WinWindow::NotExistsError, "Window does not exist! Cannot enumerate children." unless exists?
994
- enum_child_windows_callback= WinUser.window_enum_callback do |chwnd, lparam|
995
- yield WinWindow.new(chwnd)
996
- WIN_TRUE
997
- end
998
- begin
999
- ret=WinUser.EnumChildWindows(hwnd, enum_child_windows_callback, nil)
1000
- ensure
1001
- WinUser.remove_window_enum_callback(enum_child_windows_callback)
1002
- end
1003
- if ret==0
1004
- self.class.system_error("EnumChildWindows")
1005
- # actually, EnumChildWindows doesn't say anything about return value indicating error encountered.
1006
- # Although EnumWindows does, so it seems sort of safe to assume that would apply here too.
1007
- # but, maybe not - so, should we raise an error here?
1008
- end
1009
- nil
1010
- end
1011
-
1012
- # returns an Enumerable object that can iterate over each child of this window,
1013
- # yielding a WinWindow object
1014
- #
1015
- # may raise a WinWindow::SystemError from #each_child
1016
- def children
1017
- Children.new self
1018
- end
1019
-
1020
- # true if comparing an object of the same class with the same hwnd (integer)
1021
- def eql?(oth)
1022
- oth.class==self.class && oth.hwnd==self.hwnd
1023
- end
1024
- alias == eql?
1025
- # def ==(oth)
1026
- # self.eql?(oth)
1027
- # end
1028
-
1029
- def hash
1030
- [self.class, self.hwnd].hash
1031
- end
1032
-
1033
-
1034
-
1035
- # more specialized methods
1036
-
1037
- # Give the name of a button, or a Regexp to match it (see #child_button).
1038
- # keeps clicking the button until the button no longer exists, or until
1039
- # the given block is true (ie, not false or nil)
1040
- # Options:
1041
- # * :interval is the length of time in seconds between each attempt (default 0.05)
1042
- # * :set_foreground is whether the window should be activated first, since button-clicking is much more likely to fail if the window isn't focused (default true)
1043
- # * :exception is the exception class or instance that will be raised if we can't click the button (default nil, no exception is raised, the return value indicates success/failure)
1044
- #
1045
- # Raises ArgumentError if invalid options are given.
1046
- # Raises a WinWindow::NotExistsError if the button doesn't exist, or if this window doesn't exist, or a WinWindow::SystemError if a System Error occurs (from #each_child)
1047
- def click_child_button_try_for!(button_text, time, options={})
1048
- options=handle_options(options, {:set_foreground => true, :exception => nil, :interval => 0.05})
1049
- button=child_button(button_text) || (raise WinWindow::NotExistsError, "Button #{button_text.inspect} not found")
1050
- waiter_options={}
1051
- waiter_options[:condition]=proc{!button.exists? || (block_given? && yield)}
1052
- waiter_options.merge!(options.reject{|k,v| ![:exception, :interval].include?(k)})
1053
- Waiter.try_for(time, waiter_options) do
1054
- if options[:set_foreground]
1055
- show_normal!
1056
- really_set_foreground!
1057
- end
1058
- button.click!
1059
- end
1060
- return waiter_options[:condition].call
1061
- end
1062
-
1063
- # returns a WinWindow that is a child of this that matches the given button_text (Regexp or #to_s-able)
1064
- # or nil if no such child exists.
1065
- # & is stripped when matching so don't include it. String comparison is case-insensitive.
1066
- #
1067
- # May raise a WinWindow::SystemError from #each_child
1068
- def child_button(button_text)
1069
- children.detect do |child|
1070
- child.class_name=='Button' && button_text.is_a?(Regexp) ? child.text.tr('&', '') =~ button_text : child.text.tr('&', '').downcase==button_text.to_s.tr('&', '').downcase
1071
- end
1072
- end
1073
-
1074
- # Finds a child of this window which follows a label with the given text.
1075
- #
1076
- # Options:
1077
- # - :control_class_name is the class name of the control you are looking for. Defaults to nil, which accepts any class name.
1078
- # - :label_class_name is the class name of the label preceding the control you are looking for. Defaults to 'Static'
1079
- def child_control_with_preceding_label(preceding_label_text, options={})
1080
- options=handle_options(options, :control_class_name => nil, :label_class_name => "Static")
1081
-
1082
- prev_was_label=false
1083
- control=self.children.detect do |child|
1084
- ret=prev_was_label && (!options[:control_class_name] || child.class_name==options[:control_class_name])
1085
- prev_was_label= child.class_name==options[:label_class_name] && preceding_label_text===child.text
1086
- ret
1087
- end
1088
- end
1089
-
1090
-
1091
-
1092
- # Class methods:
1093
-
1094
- # Iterates over every window yielding a WinWindow object.
1095
- # use WinWindow::All if you want an Enumerable object.
1096
- #
1097
- # Raises a WinWindow::SystemError if a System Error occurs.
1098
- #
1099
- # http://msdn.microsoft.com/en-us/library/ms633497.aspx
1100
- #
1101
- # For System Error Codes see http://msdn.microsoft.com/en-us/library/ms681381(VS.85).aspx
1102
- def self.each_window
1103
- enum_windows_callback= WinUser.window_enum_callback do |hwnd,lparam|
1104
- yield WinWindow.new(hwnd)
1105
- WIN_TRUE
1106
- end
1107
- begin
1108
- ret=WinUser.EnumWindows(enum_windows_callback, nil)
1109
- ensure
1110
- WinUser.remove_window_enum_callback(enum_windows_callback)
1111
- end
1112
- if ret==WIN_FALSE
1113
- system_error "EnumWindows"
1114
- end
1115
- nil
1116
- end
1117
-
1118
-
1119
- # returns the first window found whose text matches what is given
1120
- #
1121
- # May raise a WinWindow::SystemError from WinWindow.each_window
1122
- def self.find_first_by_text(text)
1123
- WinWindow::All.detect do |window|
1124
- text===window.text # use triple-equals so regexps try to match, strings see if equal
1125
- end
1126
- end
1127
-
1128
- # returns all WinWindow objects found whose text matches what is given
1129
- #
1130
- # May raise a WinWindow::SystemError from WinWindow.each_hwnd
1131
- def self.find_all_by_text(text)
1132
- WinWindow::All.select do |window|
1133
- text===window.text # use triple-equals so regexps try to match, strings see if equal
1134
- end
1135
- end
1136
-
1137
- # raises a WinWindow::MatchError if more than one window matching given text is found,
1138
- # so that you can be sure you are attaching to the right one (because it's the only one)
1139
- #
1140
- # May also raise a WinWindow::SystemError from WinWindow.each_window
1141
- def self.find_only_by_text(text)
1142
- matched=WinWindow::All.select do |window|
1143
- text===window.text
1144
- end
1145
- # matched.reject! do |win| # reject win where
1146
- # matched.any? do |other_win| # exists any other_win
1147
- # parent=other_win.parent
1148
- # win_is_parent=false
1149
- # while parent && !win_is_parent
1150
- # win_is_parent ||= win==parent
1151
- # parent=parent.parent
1152
- # end
1153
- # win_is_parent # such that win is parent of other_win
1154
- # end
1155
- # end
1156
- matched.reject! do |win| # reject any win where
1157
- matched.any? do |other_win| # any other_win
1158
- parent=win.parent
1159
- other_is_parent=false
1160
- while parent && !other_is_parent
1161
- other_is_parent ||= other_win==parent
1162
- parent=parent.parent
1163
- end
1164
- other_is_parent # is its parent
1165
- end
1166
- end
1167
-
1168
- if matched.size != 1
1169
- raise MatchError, "Found #{matched.size} windows matching #{text.inspect}; there should be one"
1170
- else
1171
- return matched.first
1172
- end
1173
- end
1174
-
1175
- # Returns a WinWindow representing the current foreground window (the window with which the user is currently working).
1176
- #
1177
- # http://msdn.microsoft.com/en-us/library/ms633505%28VS.85%29.aspx
1178
- def self.foreground_window
1179
- hwnd=WinUser.GetForegroundWindow
1180
- if hwnd == 0
1181
- nil
1182
- else
1183
- self.new(hwnd)
1184
- end
1185
- end
1186
-
1187
- # Returns a WinWindow representing the desktop window. The desktop window covers the entire screen. The desktop window is the area on top of which other windows are painted.
1188
- #
1189
- # http://msdn.microsoft.com/en-us/library/ms633504%28VS.85%29.aspx
1190
- def self.desktop_window
1191
- hwnd=WinUser.GetDesktopWindow
1192
- if hwnd == 0
1193
- nil
1194
- else
1195
- self.new(hwnd)
1196
- end
1197
- end
1198
-
1199
- # Enumerable object that iterates over every available window
1200
- #
1201
- # May raise a WinWindow::SystemError from WinWindow.each_window
1202
- module All
1203
- def self.each
1204
- WinWindow.each_window do |window|
1205
- yield window
1206
- end
1207
- end
1208
- extend Enumerable
1209
- end
1210
-
1211
- # instantiates Enumerable objects that iterate over a WinWindow's children.
1212
- #
1213
- # May raise a WinWindow::SystemError from WinWindow#each_child
1214
- class Children
1215
- attr_reader :parentwindow
1216
- def initialize(parentwindow)
1217
- @parentwindow=parentwindow
1218
- end
1219
- def each
1220
- parentwindow.each_child do |cwindow|
1221
- yield cwindow
1222
- end
1223
- end
1224
- include Enumerable
1225
- end
1226
-
1227
- end