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.
- data/History.txt +0 -5
- data/lib/vapir-common.rb +16 -4
- data/lib/vapir-common/browser.rb +189 -144
- data/lib/vapir-common/browsers.rb +21 -9
- data/lib/vapir-common/config.rb +341 -0
- data/lib/vapir-common/container.rb +160 -30
- data/lib/vapir-common/element.rb +65 -555
- data/lib/vapir-common/element_class_and_module.rb +378 -0
- data/lib/vapir-common/element_collection.rb +108 -20
- data/lib/vapir-common/elements/elements.rb +243 -67
- data/lib/vapir-common/external/core_extensions.rb +62 -0
- data/lib/vapir-common/handle_options.rb +1 -1
- data/lib/vapir-common/keycodes.rb +135 -0
- data/lib/vapir-common/options.rb +5 -38
- data/lib/vapir-common/page_container.rb +26 -21
- data/lib/vapir-common/specifier.rb +2 -2
- data/lib/vapir-common/version.rb +5 -0
- data/lib/vapir-common/waiter.rb +44 -90
- data/lib/vapir.rb +7 -0
- metadata +12 -27
- data/lib/vapir-common/testcase.rb +0 -89
- data/lib/vapir-common/win_window.rb +0 -1227
data/lib/vapir.rb
CHANGED
@@ -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
|
-
-
|
9
|
-
-
|
10
|
-
version: 1.
|
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-
|
17
|
+
date: 2011-04-19 00:00:00 -04:00
|
19
18
|
default_executable:
|
20
|
-
dependencies:
|
21
|
-
|
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/
|
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.
|
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
|