win_gui 0.2.5 → 0.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/HISTORY +16 -0
- data/VERSION +1 -1
- data/lib/extension.rb +4 -0
- data/lib/win_gui/convenience.rb +20 -25
- data/lib/win_gui/window.rb +59 -18
- data/spec/extension_spec.rb +6 -2
- data/spec/win_gui/window_spec.rb +30 -10
- metadata +2 -2
data/HISTORY
CHANGED
@@ -25,3 +25,19 @@
|
|
25
25
|
== 0.2.5 / 2010-06-04
|
26
26
|
|
27
27
|
* Code brought to about Book-level quality
|
28
|
+
|
29
|
+
== 0.2.6 / 2010-06-04
|
30
|
+
|
31
|
+
* Docs improved
|
32
|
+
|
33
|
+
== 0.2.7 / 2010-06-04
|
34
|
+
|
35
|
+
* more punctuation added to String#to_key
|
36
|
+
|
37
|
+
== 0.2.8 / 2010-06-04
|
38
|
+
|
39
|
+
* Window#click api improved with position and mouse_button type
|
40
|
+
|
41
|
+
== 0.2.9 / 2010-06-04
|
42
|
+
|
43
|
+
* Window: Full feature parity with Book code
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.2.
|
1
|
+
0.2.9
|
data/lib/extension.rb
CHANGED
@@ -16,10 +16,14 @@ class String
|
|
16
16
|
[WinGui.const_get(:VK_OEM_COMMA)]
|
17
17
|
when '.'
|
18
18
|
[WinGui.const_get(:VK_OEM_PERIOD)]
|
19
|
+
when ';'
|
20
|
+
[WinGui.const_get(:VK_OEM_1)]
|
19
21
|
when ':'
|
20
22
|
[:VK_SHIFT, :VK_OEM_1].map {|s| WinGui.const_get s}
|
21
23
|
when "\\"
|
22
24
|
[WinGui.const_get(:VK_OEM_102)]
|
25
|
+
when "\n"
|
26
|
+
[WinGui.const_get(:VK_RETURN)]
|
23
27
|
else
|
24
28
|
raise "Can't convert unknown character: #{self}"
|
25
29
|
end
|
data/lib/win_gui/convenience.rb
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
require 'win/gui'
|
2
2
|
|
3
|
+
# Module contains Win32 API gui-related functions as both module and instance methods.
|
4
|
+
# See documentation of Win::Gui module of *win* gem for a full scope of available functions.
|
5
|
+
# In addition, module defines several higher-level convenience methods that can be useful
|
6
|
+
# when dealing with GUI-related tasks under windows (such as testing automation).
|
7
|
+
#
|
3
8
|
module WinGui
|
4
9
|
include Win::Gui
|
5
10
|
extend Win::Gui
|
@@ -13,16 +18,22 @@ module WinGui
|
|
13
18
|
# Default timeout for dialog operations (in sec)
|
14
19
|
LOOKUP_TIMEOUT = 3
|
15
20
|
|
16
|
-
#
|
21
|
+
# Windows class identifying standard modal dialog window
|
17
22
|
DIALOG_WINDOW_CLASS = '#32770'
|
18
23
|
|
19
24
|
# Module defines convenience methods on top of straightforward Win32 API functions:
|
20
25
|
|
21
26
|
# Finds top-level dialog window by title and yields found dialog window to attached block.
|
22
27
|
# We work with dialog window in a block, and then we wait for it to close before proceeding.
|
23
|
-
# That is, unless your block returns nil, in which case dialog is ignored and method immediately returns nil
|
24
|
-
# If no block is given, method just returns found dialog window (or nil if dialog is not found)
|
25
|
-
|
28
|
+
# That is, unless your block returns nil, in which case dialog is ignored and method immediately returns nil.
|
29
|
+
# If no block is given, method just returns found dialog window (or nil if dialog is not found).
|
30
|
+
# Options:
|
31
|
+
# :title:: dialog title
|
32
|
+
# :class:: dialog class - default DIALOG_WINDOW_CLASS
|
33
|
+
# :timeout:: timeout (seconds) - default LOOKUP_TIMEOUT (3)
|
34
|
+
# :raise:: raise this exception instead of returning nil if nothing found
|
35
|
+
#
|
36
|
+
def dialog(opts={}) # :yields: dialog_window
|
26
37
|
dialog = Window.top_level( {class: DIALOG_WINDOW_CLASS, timeout: LOOKUP_TIMEOUT}.merge opts )
|
27
38
|
dialog.set_foreground_window if dialog
|
28
39
|
wait = block_given? ? yield(dialog) : false
|
@@ -30,10 +41,11 @@ module WinGui
|
|
30
41
|
dialog
|
31
42
|
end
|
32
43
|
|
33
|
-
# Emulates combinations of (any amount of) keys pressed one after another (Ctrl+Alt+P) and then released
|
34
|
-
# *keys should be a sequence of a virtual-key codes
|
44
|
+
# Emulates combinations of (any amount of) keys pressed one after another (Ctrl+Alt+P) and then released.
|
45
|
+
# *keys should be a sequence of a virtual-key codes (value in the range 1 to 254).
|
35
46
|
# For a complete list, see msdn:Virtual Key Codes.
|
36
47
|
# If alphanumerical char is given instead of virtual key code, only lowercase letters result (no VK_SHIFT!).
|
48
|
+
#
|
37
49
|
def keystroke(*keys)
|
38
50
|
return if keys.empty?
|
39
51
|
key = String === keys.first ? keys.first.upcase.ord : keys.first.to_i
|
@@ -44,28 +56,11 @@ module WinGui
|
|
44
56
|
keybd_event key, 0, KEYEVENTF_KEYUP, 0
|
45
57
|
end
|
46
58
|
|
47
|
-
#
|
59
|
+
# Types text message into a window currently holding the focus
|
60
|
+
#
|
48
61
|
def type_in(message)
|
49
62
|
message.scan(/./m) do |char|
|
50
63
|
keystroke(*char.to_key)
|
51
64
|
end
|
52
65
|
end
|
53
|
-
|
54
|
-
|
55
|
-
# DialogWndClass = '#32770'
|
56
|
-
# def dialog(title, seconds=5)
|
57
|
-
# close, dlg = begin
|
58
|
-
# sleep 0.25
|
59
|
-
# w = Gui.top_level(title, seconds, DialogWndClass)
|
60
|
-
# Gui.set_foreground_window w.handle
|
61
|
-
# sleep 0.25
|
62
|
-
#
|
63
|
-
# [yield(w), w]
|
64
|
-
# rescue TimeoutError
|
65
|
-
# end
|
66
|
-
#
|
67
|
-
# dlg.wait_for_close if dlg && close
|
68
|
-
# return dlg
|
69
|
-
# end
|
70
|
-
|
71
66
|
end
|
data/lib/win_gui/window.rb
CHANGED
@@ -11,12 +11,11 @@ module WinGui
|
|
11
11
|
|
12
12
|
attr_reader :handle
|
13
13
|
|
14
|
-
# Looks up window handle using code specified in attached block (either with or without :timeout)
|
15
|
-
# Returns either Window instance (for a found handle) or nil if nothing found
|
14
|
+
# Looks up window handle using code specified in attached block (either with or without :timeout).
|
15
|
+
# Returns either Window instance (for a found handle) or nil if nothing found.
|
16
16
|
# Private method to dry up other window lookup methods
|
17
|
-
# :yields:
|
18
17
|
#
|
19
|
-
def self.lookup_window(opts)
|
18
|
+
def self.lookup_window(opts) # :yields: index, position
|
20
19
|
# Need this to avoid handle considered local in begin..end block
|
21
20
|
handle = yield
|
22
21
|
if opts[:timeout]
|
@@ -32,60 +31,97 @@ module WinGui
|
|
32
31
|
Window.new(handle) if handle
|
33
32
|
end
|
34
33
|
|
35
|
-
#
|
36
|
-
# If timeout option given, waits for window to appear within timeout, returns nil if it didn't
|
34
|
+
# Finds top level window by title/class, returns wrapped Window object or nil (raises exception if asked to).
|
35
|
+
# If timeout option given, waits for window to appear within timeout, returns nil if it didn't.
|
37
36
|
# Options:
|
38
37
|
# :title:: window title
|
39
38
|
# :class:: window class
|
40
39
|
# :timeout:: timeout (seconds)
|
40
|
+
# :raise:: raise this exception instead of returning nil if nothing found
|
41
|
+
#
|
41
42
|
def self.top_level(opts={})
|
42
43
|
lookup_window(opts) { WinGui.find_window opts[:class], opts[:title] }
|
43
44
|
end
|
44
45
|
|
45
|
-
#
|
46
|
+
# Finds DIRECT child window (control) by either control ID or window class/title.
|
47
|
+
# Options:
|
48
|
+
# :id:: integer control id (such as IDOK, IDCANCEL, etc)
|
49
|
+
# :title:: window title
|
50
|
+
# :class:: window class
|
51
|
+
# :timeout:: timeout (seconds)
|
52
|
+
# :raise:: raise this exception instead of returning nil if nothing found
|
53
|
+
#
|
46
54
|
def child(opts={})
|
47
55
|
self.class.lookup_window(opts) do
|
48
56
|
opts[:id] ? get_dlg_item(opts[:id]) : find_window_ex(0, opts[:class], opts[:title])
|
49
57
|
end
|
50
58
|
end
|
51
59
|
|
52
|
-
#
|
60
|
+
# Returns array of Windows that are descendants (not only DIRECT children) of a given Window
|
61
|
+
#
|
53
62
|
def children
|
54
63
|
enum_child_windows.map{|child_handle| Window.new child_handle}
|
55
64
|
end
|
56
65
|
|
57
|
-
#
|
58
|
-
#
|
66
|
+
# Emulates click of the control identified by opts (:id, :title, :class).
|
67
|
+
# Beware of keyboard shortcuts in button titles! So, use "&Yes" instead of just "Yes".
|
68
|
+
# Returns screen coordinates of click point if successful, nil if control was not found
|
69
|
+
# :id:: integer control id (such as IDOK, IDCANCEL, etc)
|
70
|
+
# :title:: window title
|
71
|
+
# :class:: window class
|
72
|
+
# :raise:: raise this exception instead of returning nil if nothing found
|
73
|
+
# :position/point/where:: location where the click is to be applied - default :center
|
74
|
+
# :mouse_button/button/which:: mouse button which to click - default :right
|
75
|
+
#
|
59
76
|
def click(opts={})
|
60
77
|
control = child(opts)
|
61
78
|
if control
|
62
79
|
left, top, right, bottom = control.get_window_rect
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
80
|
+
|
81
|
+
where = opts[:point] || opts[:where] || opts[:position]
|
82
|
+
point = case where
|
83
|
+
when Array
|
84
|
+
where # Explicit screen coords
|
85
|
+
when :random
|
86
|
+
[left + rand(right - left), top + rand(bottom - top)] # Random point within control window
|
87
|
+
else
|
88
|
+
[(left + right) / 2, (top + bottom) / 2] # Center of a control window
|
89
|
+
end
|
90
|
+
|
91
|
+
WinGui.set_cursor_pos *point
|
92
|
+
|
93
|
+
button = opts[:mouse_button] || opts[:mouse] || opts[:which]
|
94
|
+
down, up = (button == :right) ?
|
95
|
+
[WinGui::MOUSEEVENTF_RIGHTDOWN, WinGui::MOUSEEVENTF_RIGHTUP] :
|
96
|
+
[WinGui::MOUSEEVENTF_LEFTDOWN, WinGui::MOUSEEVENTF_LEFTUP]
|
97
|
+
|
98
|
+
WinGui.mouse_event down, 0, 0, 0, 0
|
99
|
+
WinGui.mouse_event up, 0, 0, 0, 0
|
100
|
+
point
|
68
101
|
else
|
69
|
-
|
102
|
+
nil
|
70
103
|
end
|
71
104
|
end
|
72
105
|
|
106
|
+
# Waits for this window to close with timeout (default CLOSE_TIMEOUT).
|
107
|
+
#
|
73
108
|
def wait_for_close(timeout=CLOSE_TIMEOUT )
|
74
109
|
timeout(timeout) do
|
75
110
|
sleep SLEEP_DELAY while window_visible?
|
76
111
|
end
|
77
112
|
end
|
78
113
|
|
79
|
-
# We alias convenience method shut_window (from Win::Gui::
|
114
|
+
# We alias convenience method shut_window (from Win::Gui::Window) with even more convenient
|
80
115
|
# window.close
|
81
116
|
# Please keep in mind that Win32 API has another function CloseWindow that merely MINIMIZES window.
|
82
117
|
# If you want to invoke this function, you can do it like this:
|
83
118
|
# window.close_window
|
119
|
+
#
|
84
120
|
def close
|
85
121
|
shut_window
|
86
122
|
end
|
87
123
|
|
88
|
-
# Since Window instances wrap actual window handles, they should support
|
124
|
+
# Since Window instances wrap actual window handles, they should directly support Win32 API functions
|
89
125
|
# manipulating these handles. Therefore, when unsupported instance method is invoked, we check if
|
90
126
|
# WinGui responds to such method, and if yes, call it with our window handle as a first argument.
|
91
127
|
# This gives us all handle-related WinGui functions as instance methods for Window instances, like so:
|
@@ -95,6 +131,11 @@ module WinGui
|
|
95
131
|
# Of course, if we unvoke WinGui function that DOESN'T accept handle as a first arg this way, we are screwed.
|
96
132
|
# Call such functions only like this:
|
97
133
|
# WinGui.function(*args)
|
134
|
+
# TODO: Such setup is problematic if WinGui is included into Window ancestor chain.
|
135
|
+
# TODO: in this case, all WinGui functions become available as instance methods, and method_missing never fires.
|
136
|
+
# TODO: it may be a better solution to explicitly define all needed instance methods.
|
137
|
+
# TODO: instead of showing off cool meta-programming skillz.
|
138
|
+
#
|
98
139
|
def method_missing(name, *args, &block)
|
99
140
|
if WinGui.respond_to? name
|
100
141
|
# puts "Window #{@handle} calling: #{name} #{@handle} #{args} &#{block}"
|
data/spec/extension_spec.rb
CHANGED
@@ -21,16 +21,20 @@ module WinGuiTest
|
|
21
21
|
' '.to_key.should == " ".unpack('C')
|
22
22
|
end
|
23
23
|
|
24
|
-
it 'transforms
|
24
|
+
it 'transforms \n into [VK_RETURN]' do
|
25
|
+
"\n".to_key.should == [VK_RETURN]
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'transforms .,:;\\ into [equivalent key code]' do
|
25
29
|
','.to_key.should == [VK_OEM_COMMA]
|
26
30
|
'.'.to_key.should == [VK_OEM_PERIOD]
|
27
31
|
':'.to_key.should == [VK_SHIFT, VK_OEM_1]
|
32
|
+
';'.to_key.should == [VK_OEM_1]
|
28
33
|
"\\".to_key.should == [VK_OEM_102]
|
29
34
|
end
|
30
35
|
|
31
36
|
it 'raises error if char is not implemented punctuation' do
|
32
37
|
('!'..'+').each {|char| lambda {char.to_key}.should raise_error ERROR_CONVERSION }
|
33
|
-
(';'..'@').each {|char| lambda {char.to_key}.should raise_error ERROR_CONVERSION }
|
34
38
|
(']'..'`').each {|char| lambda {char.to_key}.should raise_error ERROR_CONVERSION }
|
35
39
|
('{'..'~').each {|char| lambda {char.to_key}.should raise_error ERROR_CONVERSION }
|
36
40
|
['-', '/', '['].each {|char| lambda {char.to_key}.should raise_error ERROR_CONVERSION }
|
data/spec/win_gui/window_spec.rb
CHANGED
@@ -169,27 +169,47 @@ module WinGuiTest
|
|
169
169
|
end # describe #children
|
170
170
|
|
171
171
|
describe '#click' do
|
172
|
-
it 'emulates
|
172
|
+
it 'emulates left click of the control identified by id, returns click coords' do
|
173
173
|
with_dialog(:save) do |dialog|
|
174
|
-
dialog.click(id: IDCANCEL)
|
175
|
-
|
174
|
+
point = dialog.click(id: IDCANCEL)
|
175
|
+
point.should be_an Array
|
176
|
+
sleep 0.3
|
176
177
|
dialog.window?.should == false
|
177
178
|
end
|
178
179
|
end
|
179
180
|
|
180
|
-
it 'emulates
|
181
|
+
it 'emulates left click of the control identified by title, returns click coords' do
|
181
182
|
with_dialog(:save) do |dialog|
|
182
|
-
dialog.click(title: "Cancel")
|
183
|
-
|
183
|
+
point = dialog.click(title: "Cancel")
|
184
|
+
point.should be_an Array
|
185
|
+
sleep 0.3
|
184
186
|
dialog.window?.should == false
|
185
187
|
end
|
186
188
|
end
|
187
189
|
|
188
|
-
it '
|
190
|
+
it 'emulates right click of the control identified by id, returns click coords' do
|
189
191
|
with_dialog(:save) do |dialog|
|
190
|
-
dialog.click(
|
191
|
-
|
192
|
-
sleep 0.
|
192
|
+
point = dialog.click(id: IDCANCEL, mouse_button: :right)
|
193
|
+
point.should be_an Array
|
194
|
+
sleep 0.3
|
195
|
+
dialog.window?.should == true
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
it 'emulates right click of the control identified by title, returns click coords' do
|
200
|
+
with_dialog(:save) do |dialog|
|
201
|
+
point = dialog.click(title: "Cancel", mouse_button: :right)
|
202
|
+
point.should be_an Array
|
203
|
+
sleep 0.3
|
204
|
+
dialog.window?.should == true
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
it 'returns nil if the specified control was not found' do
|
209
|
+
with_dialog(:save) do |dialog|
|
210
|
+
dialog.click(title: "Shpancel").should == nil
|
211
|
+
dialog.click(id: 66).should == nil
|
212
|
+
sleep 0.3
|
193
213
|
dialog.window?.should == true
|
194
214
|
end
|
195
215
|
end
|