win_gui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +43 -0
- data/Rakefile +58 -0
- data/VERSION +1 -0
- data/book_code/early_success/bundle.rb +34 -0
- data/book_code/early_success/english.txt +1 -0
- data/book_code/early_success/jruby_basics.rb +47 -0
- data/book_code/early_success/windows_basics.rb +97 -0
- data/book_code/guessing/locknote.rb +379 -0
- data/book_code/guessing/monkeyshines.rb +14 -0
- data/book_code/guessing/note.rb +120 -0
- data/book_code/guessing/note_spec.rb +175 -0
- data/book_code/guessing/replay.rb +21 -0
- data/book_code/guessing/seed.rb +9 -0
- data/book_code/guessing/spec_helper.rb +69 -0
- data/book_code/guessing/windows_gui.rb +247 -0
- data/book_code/home_stretch/junquenote.rb +151 -0
- data/book_code/home_stretch/locknote.rb +180 -0
- data/book_code/home_stretch/note.rb +144 -0
- data/book_code/home_stretch/note_spec.rb +191 -0
- data/book_code/home_stretch/spec_helper.rb +55 -0
- data/book_code/home_stretch/swing_gui.rb +50 -0
- data/book_code/home_stretch/windows_gui.rb +232 -0
- data/book_code/junquenote/exports.sh +10 -0
- data/book_code/junquenote/jruby_mac.sh +10 -0
- data/book_code/junquenote/junquenote_app.rb +262 -0
- data/book_code/novite/Rakefile +10 -0
- data/book_code/novite/app/controllers/application.rb +18 -0
- data/book_code/novite/app/controllers/guests_controller.rb +28 -0
- data/book_code/novite/app/controllers/parties_controller.rb +77 -0
- data/book_code/novite/app/helpers/application_helper.rb +11 -0
- data/book_code/novite/app/helpers/guests_helper.rb +10 -0
- data/book_code/novite/app/helpers/parties_helper.rb +10 -0
- data/book_code/novite/app/models/guest.rb +11 -0
- data/book_code/novite/app/models/party.rb +32 -0
- data/book_code/novite/app/models/party_mailer.rb +19 -0
- data/book_code/novite/app/views/layouts/application.rhtml +44 -0
- data/book_code/novite/app/views/parties/new.html.erb +42 -0
- data/book_code/novite/app/views/parties/show.html.erb +43 -0
- data/book_code/novite/app/views/party_mailer/invite.erb +17 -0
- data/book_code/novite/config/boot.rb +117 -0
- data/book_code/novite/config/database.yml +19 -0
- data/book_code/novite/config/environment.rb +67 -0
- data/book_code/novite/config/environments/development.rb +29 -0
- data/book_code/novite/config/environments/production.rb +27 -0
- data/book_code/novite/config/environments/test.rb +30 -0
- data/book_code/novite/config/initializers/inflections.rb +18 -0
- data/book_code/novite/config/initializers/mime_types.rb +13 -0
- data/book_code/novite/config/routes.rb +47 -0
- data/book_code/novite/db/migrate/001_create_parties.rb +26 -0
- data/book_code/novite/db/migrate/002_create_guests.rb +23 -0
- data/book_code/novite/db/schema.rb +41 -0
- data/book_code/novite/log/empty.txt +0 -0
- data/book_code/novite/public/.htaccess +40 -0
- data/book_code/novite/public/404.html +38 -0
- data/book_code/novite/public/422.html +38 -0
- data/book_code/novite/public/500.html +38 -0
- data/book_code/novite/public/dispatch.cgi +10 -0
- data/book_code/novite/public/dispatch.fcgi +24 -0
- data/book_code/novite/public/dispatch.rb +18 -0
- data/book_code/novite/public/favicon.ico +0 -0
- data/book_code/novite/public/images/rails.png +0 -0
- data/book_code/novite/public/index.html +285 -0
- data/book_code/novite/public/javascripts/application.js +10 -0
- data/book_code/novite/public/javascripts/controls.js +971 -0
- data/book_code/novite/public/javascripts/dragdrop.js +980 -0
- data/book_code/novite/public/javascripts/effects.js +1128 -0
- data/book_code/novite/public/javascripts/prototype.js +4233 -0
- data/book_code/novite/public/robots.txt +5 -0
- data/book_code/novite/script/about +3 -0
- data/book_code/novite/script/console +3 -0
- data/book_code/novite/script/destroy +3 -0
- data/book_code/novite/script/generate +3 -0
- data/book_code/novite/script/performance/benchmarker +3 -0
- data/book_code/novite/script/performance/profiler +3 -0
- data/book_code/novite/script/performance/request +3 -0
- data/book_code/novite/script/plugin +3 -0
- data/book_code/novite/script/process/inspector +3 -0
- data/book_code/novite/script/process/reaper +3 -0
- data/book_code/novite/script/process/spawner +3 -0
- data/book_code/novite/script/runner +3 -0
- data/book_code/novite/script/server +3 -0
- data/book_code/novite/test/test_helper.rb +46 -0
- data/book_code/one_more_thing/applescript.rb +68 -0
- data/book_code/one_more_thing/note_spec.rb +50 -0
- data/book_code/one_more_thing/spec_helper.rb +17 -0
- data/book_code/one_more_thing/textedit-pure.rb +28 -0
- data/book_code/one_more_thing/textedit.applescript +26 -0
- data/book_code/one_more_thing/textedit.rb +32 -0
- data/book_code/one_more_thing/textnote.rb +87 -0
- data/book_code/simplify/junquenote.rb +48 -0
- data/book_code/simplify/locknote.rb +46 -0
- data/book_code/simplify/note.rb +35 -0
- data/book_code/simplify/note_spec.rb +28 -0
- data/book_code/simplify/swing_gui.rb +45 -0
- data/book_code/simplify/windows_gui.rb +232 -0
- data/book_code/simplify/windows_gui_spec.rb +35 -0
- data/book_code/story/invite.story +19 -0
- data/book_code/story/journal.txt +29 -0
- data/book_code/story/novite_stories.rb +156 -0
- data/book_code/story/party.rb +149 -0
- data/book_code/story/password.rb +61 -0
- data/book_code/story/password.story +26 -0
- data/book_code/story/rsvp.story +29 -0
- data/book_code/tables/TestTime.html +93 -0
- data/book_code/tables/TestTimeSample.html +63 -0
- data/book_code/tables/calculate_time.rb +39 -0
- data/book_code/tables/calculator.rb +108 -0
- data/book_code/tables/calculator_actions.rb +27 -0
- data/book_code/tables/calculator_spec.rb +47 -0
- data/book_code/tables/fit.rb +32 -0
- data/book_code/tables/matrix.rb +109 -0
- data/book_code/tables/pseudocode.rb +17 -0
- data/book_code/tubes/book_selenium.rb +67 -0
- data/book_code/tubes/book_watir.rb +60 -0
- data/book_code/tubes/dragdrop.html +81 -0
- data/book_code/tubes/html_capture.rb +33 -0
- data/book_code/tubes/joke_list.rb +67 -0
- data/book_code/tubes/list_spec.rb +41 -0
- data/book_code/tubes/search_spec.rb +32 -0
- data/book_code/tubes/selenium_example.rb +66 -0
- data/book_code/tubes/selenium_link.rb +23 -0
- data/book_code/tubes/web_server.rb +14 -0
- data/book_code/windows/wgui.rb +29 -0
- data/book_code/windows/wobj.rb +25 -0
- data/book_code/windows/wsh.rb +25 -0
- data/book_code/with_rspec/empty_spec.rb +13 -0
- data/book_code/with_rspec/junquenote.rb +60 -0
- data/book_code/with_rspec/locknote.rb +129 -0
- data/book_code/with_rspec/note_spec.rb +32 -0
- data/book_code/with_rspec/should_examples.rb +18 -0
- data/exp/exp.rb +6 -0
- data/exp/exp_encodings.rb +40 -0
- data/exp/exp_enum_windows.rb +60 -0
- data/exp/exp_quik.rb +38 -0
- data/exp/exp_wsh.rb +115 -0
- data/exp/old/windows_basics.rb +80 -0
- data/exp/old/wnote.rb +80 -0
- data/exp/old/wnote_spec.rb +20 -0
- data/features/step_definitions/win_gui_steps.rb +0 -0
- data/features/support/env.rb +4 -0
- data/features/win_gui.feature +9 -0
- data/lib/note/java/jemmy.jar +0 -0
- data/lib/note/java/jnote.rb +48 -0
- data/lib/note/java/jruby_basics.rb +37 -0
- data/lib/note/java/junquenote_app.rb +262 -0
- data/lib/note/java/note_spec.rb +20 -0
- data/lib/note/win/locknote.rb +19 -0
- data/lib/note.rb +15 -0
- data/lib/win_gui/constants.rb +66 -0
- data/lib/win_gui/string_extensions.rb +24 -0
- data/lib/win_gui/win_gui.rb +274 -0
- data/lib/win_gui/window.rb +70 -0
- data/lib/win_gui.rb +3 -0
- data/spec/note/win/locknote_spec.rb +7 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +100 -0
- data/spec/test_apps/locknote/LockNote.exe +0 -0
- data/spec/win_gui/string_extensions_spec.rb +61 -0
- data/spec/win_gui/win_gui_spec.rb +733 -0
- data/spec/win_gui/window_spec.rb +124 -0
- metadata +251 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
#---
|
|
2
|
+
# Excerpted from "Scripted GUI Testing With Ruby",
|
|
3
|
+
# published by The Pragmatic Bookshelf.
|
|
4
|
+
# Copyrights apply to this code. It may not be used to create training material,
|
|
5
|
+
# courses, books, articles, and the like. Contact us if you are in doubt.
|
|
6
|
+
# We make no guarantees that this code is fit for any purpose.
|
|
7
|
+
# Visit http://www.pragmaticprogrammer.com/titles/idgtr for more book information.
|
|
8
|
+
#---
|
|
9
|
+
require 'java'
|
|
10
|
+
require 'rubygems'
|
|
11
|
+
require 'cheri/swing'
|
|
12
|
+
require 'crypt/gost'
|
|
13
|
+
|
|
14
|
+
include_class javax.swing.JOptionPane
|
|
15
|
+
|
|
16
|
+
class String
|
|
17
|
+
def encrypt(pw)
|
|
18
|
+
padded = pw + "\0" * [0, 8 - pw.length].max
|
|
19
|
+
Crypt::Gost.new(padded).encrypt_string(self)
|
|
20
|
+
rescue
|
|
21
|
+
return '(wrong password)'
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def decrypt(pw)
|
|
25
|
+
padded = pw + "\0" * [0, 8 - pw.length].max
|
|
26
|
+
Crypt::Gost.new(padded).decrypt_string(self)
|
|
27
|
+
rescue
|
|
28
|
+
return '(wrong password)'
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
class JunqueNoteApp
|
|
33
|
+
include Cheri::Swing
|
|
34
|
+
|
|
35
|
+
def initialize
|
|
36
|
+
swing[:auto]
|
|
37
|
+
|
|
38
|
+
@frame = swing.frame('JunqueNote') do |f|
|
|
39
|
+
size 400, 300
|
|
40
|
+
box_layout f, :Y_AXIS
|
|
41
|
+
|
|
42
|
+
menu_bar do
|
|
43
|
+
menu('File') do
|
|
44
|
+
menu_item('Open...') {on_click {open} }
|
|
45
|
+
menu_item('Change Password...') {on_click {change_password} }
|
|
46
|
+
menu_item('Save As...') {on_click {save_as} }
|
|
47
|
+
menu_item('Exit') {on_click {exit_app} }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
menu('Edit') do
|
|
51
|
+
menu_item('Undo') {on_click {@text_area.text = @state.pop} }
|
|
52
|
+
menu_item('Cut') {on_click {@state.push(@text_area.text); @text_area.cut} }
|
|
53
|
+
menu_item('Copy') {on_click {@state.push(@text_area.text); @text_area.copy} }
|
|
54
|
+
menu_item('Paste') {on_click {@state.push(@text_area.text); @text_area.paste} }
|
|
55
|
+
|
|
56
|
+
menu_item('Find...') {on_click {find} }
|
|
57
|
+
menu_item('Find Exact Case...') {on_click {find :case} }
|
|
58
|
+
menu_item('Reverse Find...') {on_click {find :reverse} }
|
|
59
|
+
menu_item('Reverse Find Exact Case...') {on_click {find :case_reverse} }
|
|
60
|
+
menu_item('Find Next') {on_click {find @how, @term} }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
menu('Help') do
|
|
64
|
+
menu_item('About JunqueNote...') {on_click {about} }
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
@text_area = text_area('Welcome to JunqueNote!') do
|
|
69
|
+
on_key_pressed {|event| @state.push(@text_area.text); @dirty = true}
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
@state = [@text_area.text]
|
|
74
|
+
@frame.visible = true
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def request_filename(button = 'Save')
|
|
78
|
+
pane = JOptionPane.new \
|
|
79
|
+
'Please enter a filename',
|
|
80
|
+
JOptionPane::PLAIN_MESSAGE,
|
|
81
|
+
JOptionPane::OK_CANCEL_OPTION,
|
|
82
|
+
nil,
|
|
83
|
+
[button,'Cancel'].to_java,
|
|
84
|
+
button
|
|
85
|
+
pane.wants_input = true
|
|
86
|
+
pane.create_dialog(@frame, 'Input').show
|
|
87
|
+
|
|
88
|
+
button == pane.value ? pane.input_value : nil
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def save_as(filename = nil)
|
|
92
|
+
save_name = filename || request_filename
|
|
93
|
+
return if save_name.nil?
|
|
94
|
+
|
|
95
|
+
@filename = save_name
|
|
96
|
+
|
|
97
|
+
if @password.nil?
|
|
98
|
+
@password = JOptionPane.show_input_dialog "Please assign a password"
|
|
99
|
+
return if @password.nil?
|
|
100
|
+
confirmation = JOptionPane.show_input_dialog "Please confirm the password"
|
|
101
|
+
return if confirmation.nil?
|
|
102
|
+
|
|
103
|
+
if @password != confirmation
|
|
104
|
+
JOptionPane.show_message_dialog(
|
|
105
|
+
@frame,
|
|
106
|
+
"The password and confirmation don't match",
|
|
107
|
+
"Oops",
|
|
108
|
+
JOptionPane::YES_NO_OPTION)
|
|
109
|
+
|
|
110
|
+
return
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
File.delete @filename if File.exists? @filename
|
|
115
|
+
File.open @filename, 'wb' do |f|
|
|
116
|
+
plaintext = "JunqueNote\n#{@text_area.text.length}\n#{@text_area.text}"
|
|
117
|
+
encrypted = plaintext.encrypt(@password)
|
|
118
|
+
f.write encrypted
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
@dirty = false
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def open
|
|
125
|
+
@filename = request_filename 'Open'
|
|
126
|
+
if @filename.nil?
|
|
127
|
+
@frame.dispose
|
|
128
|
+
return
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
@password = JOptionPane.show_input_dialog "Please enter the password"
|
|
132
|
+
if @password.nil?
|
|
133
|
+
@frame.dispose
|
|
134
|
+
return
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
encrypted = File.open(@filename, 'rb') {|f| f.read}
|
|
138
|
+
init, length, contents = encrypted.decrypt(@password).split($;, 3)
|
|
139
|
+
|
|
140
|
+
if init == 'JunqueNote'
|
|
141
|
+
contents = contents[0, length.to_i]
|
|
142
|
+
@text_area.text = contents
|
|
143
|
+
@state = [contents]
|
|
144
|
+
@dirty = false
|
|
145
|
+
else
|
|
146
|
+
JOptionPane.show_message_dialog(
|
|
147
|
+
@frame,
|
|
148
|
+
"The password doesn't match",
|
|
149
|
+
"Oops",
|
|
150
|
+
JOptionPane::YES_NO_OPTION)
|
|
151
|
+
|
|
152
|
+
@frame.dispose
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def change_password
|
|
157
|
+
old_password = JOptionPane.show_input_dialog "Please enter the password"
|
|
158
|
+
return if old_password.nil?
|
|
159
|
+
|
|
160
|
+
if old_password != @password
|
|
161
|
+
JOptionPane.show_message_dialog(
|
|
162
|
+
@frame,
|
|
163
|
+
"The password doesn't match",
|
|
164
|
+
"Oops",
|
|
165
|
+
JOptionPane::YES_NO_OPTION)
|
|
166
|
+
|
|
167
|
+
return
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
new_password = JOptionPane.show_input_dialog "Please enter a password"
|
|
171
|
+
return if new_password.nil?
|
|
172
|
+
confirmation = JOptionPane.show_input_dialog "Please confirm the password"
|
|
173
|
+
return if confirmation.nil?
|
|
174
|
+
|
|
175
|
+
if new_password != confirmation
|
|
176
|
+
JOptionPane.show_message_dialog(
|
|
177
|
+
@frame,
|
|
178
|
+
"The password and confirmation don't match",
|
|
179
|
+
"Oops",
|
|
180
|
+
JOptionPane::YES_NO_OPTION)
|
|
181
|
+
|
|
182
|
+
return
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
@password = new_password
|
|
186
|
+
|
|
187
|
+
File.delete @filename if File.exists? @filename
|
|
188
|
+
File.open @filename, 'wb' do |f|
|
|
189
|
+
plaintext = "JunqueNote\n#{@text_area.text.length}\n#{@text_area.text}"
|
|
190
|
+
encrypted = plaintext.encrypt(@password)
|
|
191
|
+
f.write encrypted
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
@dirty = false
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def exit_app
|
|
198
|
+
should_save = if @dirty
|
|
199
|
+
0 == JOptionPane.show_confirm_dialog(
|
|
200
|
+
nil,
|
|
201
|
+
"Wanna save first?",
|
|
202
|
+
"Quittin' time",
|
|
203
|
+
JOptionPane::YES_NO_OPTION)
|
|
204
|
+
else
|
|
205
|
+
false
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
save_as(@filename) if should_save
|
|
209
|
+
@frame.dispose
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def about
|
|
213
|
+
JOptionPane.show_message_dialog(
|
|
214
|
+
@frame,
|
|
215
|
+
"A hypothetical JRuby port of LockNote",
|
|
216
|
+
"About JunqueNote",
|
|
217
|
+
JOptionPane::INFORMATION_MESSAGE)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def find(how = nil, term = nil)
|
|
221
|
+
term ||= JOptionPane.show_input_dialog "Please enter the search term"
|
|
222
|
+
return unless term
|
|
223
|
+
|
|
224
|
+
@how = how
|
|
225
|
+
@term = term
|
|
226
|
+
|
|
227
|
+
pattern, backwards = case how
|
|
228
|
+
when :case
|
|
229
|
+
[/#{term}/, false]
|
|
230
|
+
when :reverse
|
|
231
|
+
[/#{term.reverse}/i, true]
|
|
232
|
+
when :case_reverse
|
|
233
|
+
[/#{term.reverse}/, true]
|
|
234
|
+
else
|
|
235
|
+
[/#{term}/i, false]
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
position = @text_area.get_selection_start || 0
|
|
239
|
+
contents = @text_area.text
|
|
240
|
+
|
|
241
|
+
if backwards
|
|
242
|
+
contents.reverse!
|
|
243
|
+
position = contents.length - position
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
next_position = contents.index pattern, position + 1
|
|
247
|
+
|
|
248
|
+
if next_position
|
|
249
|
+
next_position =
|
|
250
|
+
contents.length -
|
|
251
|
+
next_position -
|
|
252
|
+
term.length if backwards
|
|
253
|
+
|
|
254
|
+
@text_area.set_selection_start next_position
|
|
255
|
+
@text_area.set_selection_end next_position + term.length
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
if __FILE__ == $0
|
|
261
|
+
JunqueNoteApp.new
|
|
262
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
describe 'The main window' do
|
|
2
|
+
it 'launches with a welcome message' do
|
|
3
|
+
note = Note.new
|
|
4
|
+
note.text.should include('Welcome' )
|
|
5
|
+
note.exit!
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
it 'exits without a prompt if nothing has changed' do
|
|
9
|
+
note = Note.new
|
|
10
|
+
note.exit!
|
|
11
|
+
note.should_not have_prompted
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it 'prompts before exiting if the document has changed' do
|
|
15
|
+
note = Note.new
|
|
16
|
+
note.type_in "changed"
|
|
17
|
+
note.exit!
|
|
18
|
+
note.should have_prompted
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#require File.join(File.dirname(__FILE__), ".." , "..", "note" )
|
|
2
|
+
require 'win_gui/win_gui'
|
|
3
|
+
|
|
4
|
+
class LockNote < Note
|
|
5
|
+
include WinGui
|
|
6
|
+
|
|
7
|
+
APP_PATH = File.join(File.dirname(__FILE__),".." ,"test_apps/locknote/LockNote.exe" )
|
|
8
|
+
APP_START = 'start "" "' + APP_PATH + '"'
|
|
9
|
+
|
|
10
|
+
@@app = LockNote
|
|
11
|
+
@@titles[:save] = 'Steganos LockNote'
|
|
12
|
+
|
|
13
|
+
def initialize
|
|
14
|
+
system APP_START
|
|
15
|
+
@main_window = Window.top_level 'LockNote - Steganos LockNote'
|
|
16
|
+
@edit_window = @main_window.child 'ATL:00434310'
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
end
|
data/lib/note.rb
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
libdir = File.dirname(__FILE__)
|
|
2
|
+
$LOAD_PATH.unshift libdir unless $LOAD_PATH.include?(libdir)
|
|
3
|
+
|
|
4
|
+
# Abstract Note class (to be subclassed)
|
|
5
|
+
class Note
|
|
6
|
+
@@app = nil
|
|
7
|
+
@@titles = {}
|
|
8
|
+
|
|
9
|
+
def self.open
|
|
10
|
+
@@app.new
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
require 'note/win/locknote'
|
|
15
|
+
#require 'note/java/junquenote'
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
module WinGui
|
|
2
|
+
|
|
3
|
+
# WinGui Module internal Constants:
|
|
4
|
+
WG_DLL_DEFAULT = 'user32'
|
|
5
|
+
WG_KEY_DELAY = 0.00001
|
|
6
|
+
WG_SLEEP_DELAY = 0.001
|
|
7
|
+
WG_CLOSE_TIMEOUT = 1
|
|
8
|
+
#WG_TEXT_BUFFER = '\0' * 2048
|
|
9
|
+
|
|
10
|
+
# Windows keyboard-related Constants:
|
|
11
|
+
# Virtual key codes:
|
|
12
|
+
VK_CANCEL = 0x03 # Control-break processing
|
|
13
|
+
VK_BACK = 0x08
|
|
14
|
+
VK_TAB = 0x09
|
|
15
|
+
VK_SHIFT = 0x10
|
|
16
|
+
VK_CONTROL = 0x11
|
|
17
|
+
VK_RETURN = 0x0D # ENTER key
|
|
18
|
+
VK_ALT = 0x12 # ALT key
|
|
19
|
+
VK_MENU = 0x12 # ALT key alias
|
|
20
|
+
VK_PAUSE = 0x13 # PAUSE key
|
|
21
|
+
VK_CAPITAL = 0x14 # CAPS LOCK key
|
|
22
|
+
VK_ESCAPE = 0x1B # ESC key
|
|
23
|
+
VK_SPACE = 0x20 # SPACEBAR
|
|
24
|
+
VK_PRIOR = 0x21 # PAGE UP key
|
|
25
|
+
VK_NEXT = 0x22 # PAGE DOWN key
|
|
26
|
+
VK_END = 0x23 # END key
|
|
27
|
+
VK_HOME = 0x24 # HOME key
|
|
28
|
+
VK_LEFT = 0x25 # LEFT ARROW key
|
|
29
|
+
VK_UP = 0x26 # UP ARROW key
|
|
30
|
+
VK_RIGHT = 0x27 # RIGHT ARROW key
|
|
31
|
+
VK_DOWN = 0x28 # DOWN ARROW key
|
|
32
|
+
VK_SELECT = 0x29 # SELECT key
|
|
33
|
+
VK_PRINT = 0x2A # PRINT key
|
|
34
|
+
VK_EXECUTE = 0x2B # EXECUTE key
|
|
35
|
+
VK_SNAPSHOT = 0x2C # PRINT SCREEN key
|
|
36
|
+
VK_INSERT = 0x2D # INS key
|
|
37
|
+
VK_DELETE = 0x2E # DEL key
|
|
38
|
+
VK_HELP = 0x2F # HELP key
|
|
39
|
+
# Key events:
|
|
40
|
+
KEYEVENTF_KEYDOWN = 0
|
|
41
|
+
KEYEVENTF_KEYUP = 2
|
|
42
|
+
|
|
43
|
+
# Show Window Commands:
|
|
44
|
+
SW_HIDE = 0
|
|
45
|
+
SW_NORMAL = 1
|
|
46
|
+
SW_SHOWNORMAL = 1
|
|
47
|
+
SW_SHOWMINIMIZED = 2
|
|
48
|
+
SW_SHOWMAXIMIZED = 3
|
|
49
|
+
SW_MAXIMIZE = 3
|
|
50
|
+
SW_SHOWNOACTIVATE = 4
|
|
51
|
+
SW_SHOW = 5
|
|
52
|
+
SW_MINIMIZE = 6
|
|
53
|
+
SW_SHOWMINNOACTIVE= 7
|
|
54
|
+
SW_SHOWNA = 8
|
|
55
|
+
SW_RESTORE = 9
|
|
56
|
+
SW_SHOWDEFAULT = 10
|
|
57
|
+
SW_FORCEMINIMIZE = 11
|
|
58
|
+
|
|
59
|
+
# Windows Messages Constants:
|
|
60
|
+
WM_GETTEXT = 0x000D
|
|
61
|
+
WM_SYSCOMMAND = 0x0112
|
|
62
|
+
SC_CLOSE = 0xF060
|
|
63
|
+
|
|
64
|
+
# Other Windows Constants:
|
|
65
|
+
end
|
|
66
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
class String
|
|
2
|
+
def snake_case
|
|
3
|
+
gsub(/([a-z])([A-Z0-9])/, '\1_\2' ).downcase
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def to_w
|
|
7
|
+
(self+"\x00").encode('utf-16LE')
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def to_vkeys
|
|
11
|
+
unless size == 1
|
|
12
|
+
raise "Can't convert but a single character: #{self}"
|
|
13
|
+
end
|
|
14
|
+
ascii = upcase.unpack('C')[0]
|
|
15
|
+
case self
|
|
16
|
+
when 'a'..'z', '0'..'9', ' '
|
|
17
|
+
[ascii]
|
|
18
|
+
when 'A'..'Z'
|
|
19
|
+
[WinGui.const_get(:VK_SHIFT), ascii]
|
|
20
|
+
else
|
|
21
|
+
raise "Can't convert unknown character: #{self}"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
require 'Win32/api'
|
|
2
|
+
require 'string_extensions'
|
|
3
|
+
require 'constants'
|
|
4
|
+
require 'window'
|
|
5
|
+
|
|
6
|
+
#TODO - When calling API functions, win_handle arg should default to instance var @handle of the host class
|
|
7
|
+
#TODO - Giving a hash of "named args" to def_api, like this:
|
|
8
|
+
#TODO def_api 'ShowWindow', 'LI' , 'I', :args=>{1=>:handle=>, 2=>[:cmd, :command]}
|
|
9
|
+
#TODO - Giving a hash of "defaults" to def_api, like this:
|
|
10
|
+
#TODO def_api 'ShowWindow', 'LI' , 'I', :defaults=>{1=>1234, 2=>'String2'}
|
|
11
|
+
#TODO - Option :class_method should define CLASS method instead of instance
|
|
12
|
+
|
|
13
|
+
module WinGui
|
|
14
|
+
|
|
15
|
+
# Class meta-method used to define API wrappers
|
|
16
|
+
def self.def_api(function, params, returns, options={}, &define_block)
|
|
17
|
+
name = function.snake_case
|
|
18
|
+
name.sub!(/^is_/, '') << '?' if name =~ /^is_/
|
|
19
|
+
boolean = options[:boolean] || name =~ /\?$/ # Boolean function returns true/false instead of nonzero/zero
|
|
20
|
+
proto = params.respond_to?(:join) ? params.join : params # Converts params into prototype string
|
|
21
|
+
api = Win32::API.new(function, proto.upcase, returns.upcase, options[:dll] || WG_DLL_DEFAULT)
|
|
22
|
+
|
|
23
|
+
define_method(options[:rename] || name) do |*args, &runtime_block|
|
|
24
|
+
return api if args == [:api]
|
|
25
|
+
return define_block.call(api, *args, &runtime_block) if define_block
|
|
26
|
+
raise 'Invalid args count' unless args.size == params.size
|
|
27
|
+
result = api.call(*args)
|
|
28
|
+
yield result if runtime_block
|
|
29
|
+
return result != 0 if boolean
|
|
30
|
+
return nil if options[:zeronil] && result == 0
|
|
31
|
+
result
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Converts block into API::Callback object that can be used as API callback argument
|
|
36
|
+
def self.callback(params, returns, &block)
|
|
37
|
+
Win32::API::Callback.new(params, returns, &block)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Helper methods:
|
|
41
|
+
# returns string buffer - used to supply string pointer reference to API functions
|
|
42
|
+
def self.buffer(size = 1024, code = "\x00")
|
|
43
|
+
code * size
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
return_string_proc = lambda do |api, *args|
|
|
47
|
+
raise 'Invalid args count' unless args.size == api.prototype.size-2
|
|
48
|
+
args += [string = buffer, string.length]
|
|
49
|
+
num_chars = api.call(*args) # num_chars not used
|
|
50
|
+
string.rstrip
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
return_utf_string_proc = lambda do |api, *args|
|
|
54
|
+
raise 'Invalid args count' unless args.size == api.prototype.size-2
|
|
55
|
+
args += [string = buffer, string.length]
|
|
56
|
+
num_chars = api.call(*args) # num_chars not used
|
|
57
|
+
string.force_encoding('utf-16LE').encode('utf-8').rstrip
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
return_enum_proc = Proc.new do |api, *args, &block|
|
|
61
|
+
raise 'Invalid args count' unless args.size == api.prototype.size-1
|
|
62
|
+
handles = []
|
|
63
|
+
cb = if block
|
|
64
|
+
callback('LP', 'I', &block)
|
|
65
|
+
else
|
|
66
|
+
callback('LP', 'I') do |handle, message|
|
|
67
|
+
handles << handle
|
|
68
|
+
true
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
api.call *(args.size == 1 ? [cb, args.first] : [args.first, cb, args.last])
|
|
72
|
+
handles
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Windows API definitions:
|
|
76
|
+
def_api 'IsWindow', 'L', 'L'
|
|
77
|
+
# Tests whether the specified window handle identifies an existing window.
|
|
78
|
+
# A thread should not use IsWindow for a window that it did not create because the window could be destroyed after this
|
|
79
|
+
# function was called. Further, because window handles are recycled the handle could even point to a different window.
|
|
80
|
+
|
|
81
|
+
def_api 'IsWindowVisible', 'L', 'L'
|
|
82
|
+
alias visible? window_visible?
|
|
83
|
+
# Tests if the specified window, its parent window, its parent's parent window, and so forth, have the WS_VISIBLE style.
|
|
84
|
+
# Because the return value specifies whether the window has the WS_VISIBLE style, it may be true even if the window is totally obscured by other windows.
|
|
85
|
+
|
|
86
|
+
def_api 'IsZoomed', 'L', 'L'
|
|
87
|
+
alias maximized? zoomed?
|
|
88
|
+
# Tests whether the specified window is maximized.
|
|
89
|
+
|
|
90
|
+
def_api 'IsIconic', 'L', 'L'
|
|
91
|
+
alias minimized? iconic?
|
|
92
|
+
# Tests whether the specified window is maximized.
|
|
93
|
+
|
|
94
|
+
def_api 'IsChild', 'LL', 'L'
|
|
95
|
+
# Tests whether a window is a child (or descendant) window of a specified parent window. A child window is the direct descendant
|
|
96
|
+
# of a specified parent window if that parent window is in the chain of parent windows; the chain of parent windows leads from
|
|
97
|
+
# the original overlapped or pop-up window to the child window.
|
|
98
|
+
|
|
99
|
+
def_api 'FindWindow', 'PP', 'L', :zeronil => true
|
|
100
|
+
# Retrieves a handle to the top-level window whose class name and window name match the specified strings.
|
|
101
|
+
# This function does not search child windows. This function does not perform a case-sensitive search.
|
|
102
|
+
# class_name (P) - String that specifies (window) class name or a class atom created by a previous call to the RegisterClass(Ex) function.
|
|
103
|
+
# The atom must be in the low-order word of class_name; the high-order word must be zero.
|
|
104
|
+
# The class name can be any name registered with RegisterClass(Ex), or any of the predefined control-class names.
|
|
105
|
+
# If this parameter is nil, it finds any window whose title matches the win_title parameter.
|
|
106
|
+
# win_name (P) - String that specifies the window name (title). If this parameter is nil, all window names match.
|
|
107
|
+
# returns (L) found window handle or NIL if nothing found
|
|
108
|
+
|
|
109
|
+
def_api 'FindWindowW', 'PP', 'L', :zeronil => true
|
|
110
|
+
# Unicode version of find_window (strings must be encoded as utf-16LE AND terminate with "\x00\x00")
|
|
111
|
+
|
|
112
|
+
def_api 'FindWindowEx', 'LLPP', 'L', :zeronil => true
|
|
113
|
+
# Retrieves a handle to a CHILD window whose class name and window name match the specified strings. The function searches child windows,
|
|
114
|
+
# beginning with the one following the specified child window. This function does NOT perform a case-sensitive search.
|
|
115
|
+
# parent (L) - Handle to the parent window whose child windows are to be searched.
|
|
116
|
+
# If nil, the function uses the desktop window as the parent window.
|
|
117
|
+
# The function searches among windows that are child windows of the desktop.
|
|
118
|
+
# after_child (L) - Handle to a child window. The search begins with the NEXT child window in the Z order.
|
|
119
|
+
# The child window must be a direct child window of parent, not just a descendant window.
|
|
120
|
+
# If after_child is nil, the search begins with the first child window of parent.
|
|
121
|
+
# win_class (P), win_title (P) - Strings that specify window class and name(title). If parameter is nil, anything matches.
|
|
122
|
+
# Returns (L) - found child window (control) handle or NIL if nothing found
|
|
123
|
+
|
|
124
|
+
def_api 'GetWindowText', 'LPI', 'L', &return_string_proc
|
|
125
|
+
# Returns 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 copied.
|
|
126
|
+
# However, GetWindowText cannot retrieve the text of a control in another application.
|
|
127
|
+
# API improved to require only win_handle and return rstripped string
|
|
128
|
+
# win_handle (L) - Handle to the window and, indirectly, the class to which the window belongs.
|
|
129
|
+
# buffer (P) - Pointer to the buffer that will receive the text. If the string is as long or longer than the buffer,
|
|
130
|
+
# the string is truncated and terminated with a NULL character.
|
|
131
|
+
# count (L) Specifies the maximum number of characters to copy to the buffer, including the NULL character. If the text exceeds this limit, it is truncated.
|
|
132
|
+
# Returns (L) length, in characters, of the copied string, not including the terminating NULL character.
|
|
133
|
+
# If the window has no title bar or text, if the title bar is empty, or if the window or control handle is invalid, the return value is zero.
|
|
134
|
+
# To get extended error information, call GetLastError.
|
|
135
|
+
# Remarks: This function CANNOT retrieve the text of an edit control in ANOTHER app.
|
|
136
|
+
# If the target window is owned by the current process, GetWindowText causes a WM_GETTEXT message to be sent to the specified window or control.
|
|
137
|
+
# If the target window is owned by another process and has a caption, GetWindowText retrieves the window caption text. If the window does not have a caption,
|
|
138
|
+
# the return value is a null string. This allows to call GetWindowText without becoming unresponsive if the target window owner process is not responding.
|
|
139
|
+
# However, if the unresponsive target window belongs to the calling app, GetWindowText will cause the calling app to become unresponsive.
|
|
140
|
+
# To retrieve the text of a control in another process, send a WM_GETTEXT message directly instead of calling GetWindowText.
|
|
141
|
+
|
|
142
|
+
def_api 'GetWindowTextW', 'LPI', 'L', &return_utf_string_proc
|
|
143
|
+
# Unicode version of get_window_text (returns rstripped utf-8 string)
|
|
144
|
+
# API improved to require only win_handle and return rstripped string
|
|
145
|
+
|
|
146
|
+
def_api 'GetClassName', 'LPI', 'I', &return_string_proc
|
|
147
|
+
# Retrieves the name of the class to which the specified window belongs.
|
|
148
|
+
# API improved to require only win_handle and return rstripped string
|
|
149
|
+
# win_handle (L) - Handle to the window and, indirectly, the class to which the window belongs.
|
|
150
|
+
# class_name (P) - Pointer to the buffer that is to receive the class name string.
|
|
151
|
+
# max_count (I) - Specifies the length, in TCHAR, of the buffer pointed to by the lpClassName parameter.
|
|
152
|
+
# The class name string is truncated if it is longer than the buffer and is always null-terminated.
|
|
153
|
+
# Returns (I) - number of TCHAR copied to the specified buffer, if the function succeeds.
|
|
154
|
+
# Returns zero if function fails. To get extended error information, call GetLastError.
|
|
155
|
+
|
|
156
|
+
def_api 'GetClassNameW', 'LPI', 'I', &return_utf_string_proc
|
|
157
|
+
# Unicode version of get_class_name (returns rstripped utf-8 string)
|
|
158
|
+
# API improved to require only win_handle and return rstripped string
|
|
159
|
+
|
|
160
|
+
def_api 'GetWindowThreadProcessId', 'LP', 'L' do |api, *args|
|
|
161
|
+
# Retrieves the identifier of the thread that created the specified window and, optionally, the identifier of the process that created the window.
|
|
162
|
+
# API improved to accept window handle as a single arg and return a pair of [thread, process] ids
|
|
163
|
+
# handle (L) - Handle to the window.
|
|
164
|
+
# process (P) - A POINTER to a (Long) variable that receives the process identifier. If it is nil, nothing happens.
|
|
165
|
+
# Otherwise, GetWindowThreadProcessId copies the identifier of the process to the variable.
|
|
166
|
+
# Returns (L) - Identifier of the thread that created the window.
|
|
167
|
+
raise 'Invalid args count' unless args.size == api.prototype.size-1
|
|
168
|
+
thread = api.call(args.first, process = [1].pack('L'))
|
|
169
|
+
[thread] + process.unpack('L')
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def_api 'ShowWindow', 'LI', 'I', :boolean => true
|
|
173
|
+
# handle (L) - Handle to the window.
|
|
174
|
+
# cmd (I) - Specifies how the window is to be shown. This parameter is ignored the first time an application calls ShowWindow,
|
|
175
|
+
# if the program that launched the application provides a STARTUPINFO structure. Otherwise, the first time ShowWindow is called,
|
|
176
|
+
# the value should be the value obtained by the WinMain function in its nCmdShow parameter. In subsequent calls, cmd may be:
|
|
177
|
+
# SW_HIDE - Hides the window and activates another window.
|
|
178
|
+
# SW_MAXIMIZE - Maximizes the specified window.
|
|
179
|
+
# SW_MINIMIZE - Minimizes the specified window and activates the next top-level window in the Z order.
|
|
180
|
+
# SW_SHOW - Activates the window and displays it in its current size and position.
|
|
181
|
+
# SW_SHOWMAXIMIZED - Activates the window and displays it as a maximized window.
|
|
182
|
+
# SW_SHOWMINIMIZED - Activates the window and displays it as a minimized window.
|
|
183
|
+
# SW_SHOWMINNOACTIVE Displays the window as a minimized window. This value is similar to SW_SHOWMINIMIZED, except the window is not activated.
|
|
184
|
+
# SW_SHOWNA - Displays the window in its current size and position. This value is similar to SW_SHOW, except the window is not activated.
|
|
185
|
+
# SW_SHOWNOACTIVATE- Displays a window in its most recent size and position. This value is similar to SW_SHOWNORMAL, except the window is not actived.
|
|
186
|
+
# SW_SHOWNORMAL - Activates and displays a window. If the window is minimized or maximized, the system restores it to its original size and position.
|
|
187
|
+
# An application should specify this flag when displaying the window for the first time.
|
|
188
|
+
# SW_RESTORE - Activates and displays the window. If the window is minimized or maximized, the system restores it to
|
|
189
|
+
# its original size and position. An application should specify this flag when restoring a minimized window.
|
|
190
|
+
# SW_SHOWDEFAULT - Sets the show state based on the SW_ value specified in the STARTUPINFO structure passed to the
|
|
191
|
+
# CreateProcess function by the program that started the application.
|
|
192
|
+
# SW_FORCEMINIMIZE - Windows 2000/XP: Minimizes a window, even if the thread that owns the window is not responding.
|
|
193
|
+
# This flag should only be used when minimizing windows from a different thread.
|
|
194
|
+
# Returns (I) - True if the window was PREVIOUSLY visible, otherwise false
|
|
195
|
+
def hide_window(handle)
|
|
196
|
+
show_window(handle, SW_HIDE)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def_api 'GetWindowRect', 'LP', 'I' do |api, *args|
|
|
200
|
+
# Retrieves the dimensions of the specified window bounding rectangle. Dimensions are given relative to the upper-left corner of the screen.
|
|
201
|
+
# API improved to accept only window handle and return 4-member dimensions array (left, top, right, bottom)
|
|
202
|
+
# handle (L) - Handle to the window, rectangle - pointer to 4-long array for coordinates
|
|
203
|
+
# Remarks: In conformance with conventions for the RECT structure, the bottom-right coordinates of the returned rectangle are exclusive.
|
|
204
|
+
# In other words, the pixel at (right, bottom) lies immediately outside the rectangle.
|
|
205
|
+
raise 'Invalid args count' unless args.size == api.prototype.size-1
|
|
206
|
+
rectangle = [0, 0, 0, 0].pack 'L*'
|
|
207
|
+
api.call args.first, rectangle
|
|
208
|
+
rectangle.unpack 'l*'
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def_api 'keybd_event', 'IILL', 'V'
|
|
212
|
+
def_api 'PostMessage', 'LLLL', 'L'
|
|
213
|
+
def_api 'SendMessage', 'LLLP', 'L'
|
|
214
|
+
def_api 'GetDlgItem', 'LL', 'L'
|
|
215
|
+
def_api 'EnumWindows', 'KP', 'L', &return_enum_proc
|
|
216
|
+
# The EnumWindows function enumerates all top-level windows on the screen by passing the handle to each window,
|
|
217
|
+
# in turn, to an application-defined callback function. EnumWindows continues until the last top-level window is
|
|
218
|
+
# enumerated or the callback function returns FALSE.
|
|
219
|
+
# API improved to accept blocks (instead of callback objects) and message as a single arg
|
|
220
|
+
# callback [K] - Pointer to an application-defined callback function. For more information, see EnumWindowsProc.
|
|
221
|
+
# message [P] - Specifies an application-defined value(message) to be passed to the callback function.
|
|
222
|
+
# Returns: Nonzero if the function succeeds, zero if the function fails. For extended error info, call GetLastError.
|
|
223
|
+
# If callback returns zero, the return value is also zero. In this case, the callback function should call
|
|
224
|
+
# SetLastError to obtain a meaningful error code to be returned to the caller of EnumWindows.
|
|
225
|
+
# Remarks: The EnumWindows function does not enumerate child windows, with the exception of a few top-level windows
|
|
226
|
+
# owned by the system that have the WS_CHILD style. This function is more reliable than calling the GetWindow function
|
|
227
|
+
# in a loop. An application that calls GetWindow to perform this task risks being caught in an infinite loop or
|
|
228
|
+
# referencing a handle to a window that has been destroyed.
|
|
229
|
+
|
|
230
|
+
def_api 'EnumChildWindows', 'LKP', 'L', &return_enum_proc
|
|
231
|
+
# parent (L) - Handle to the parent window whose child windows are to be enumerated.
|
|
232
|
+
# If it is nil, this function is equivalent to EnumWindows. Windows 95/98/Me: parent cannot be NULL.
|
|
233
|
+
# API improved to accept blocks (instead of callback objects) and two args: parent handle and message
|
|
234
|
+
# callback (K) - Pointer to an application-defined callback function. For more information, see EnumChildProc.
|
|
235
|
+
# message (P) - Specifies an application-defined value to be passed to the callback function.
|
|
236
|
+
# Returns (I) - Not used (?!)
|
|
237
|
+
# If a child window has created child windows of its own, EnumChildWindows enumerates those windows as well.
|
|
238
|
+
# A child window that is moved or repositioned in the Z order during the enumeration process will be properly enumerated.
|
|
239
|
+
# The function does not enumerate a child window that is destroyed before being enumerated or that is created during the enumeration process.
|
|
240
|
+
|
|
241
|
+
def_api 'GetForegroundWindow', 'V', 'L'
|
|
242
|
+
def_api 'GetActiveWindow', 'V', 'L'
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
# Convenience wrapper methods:
|
|
246
|
+
|
|
247
|
+
# emulates combinations of keys pressed (Ctrl+Alt+P+M, etc)
|
|
248
|
+
def keystroke(*keys)
|
|
249
|
+
return if keys.empty?
|
|
250
|
+
keybd_event keys.first, 0, KEYEVENTF_KEYDOWN, 0
|
|
251
|
+
sleep WG_KEY_DELAY
|
|
252
|
+
keystroke *keys[1..-1]
|
|
253
|
+
sleep WG_KEY_DELAY
|
|
254
|
+
keybd_event keys.first, 0, KEYEVENTF_KEYUP, 0
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# types text message into window holding the focus
|
|
258
|
+
def type_in(message)
|
|
259
|
+
message.scan(/./m) do |char|
|
|
260
|
+
keystroke(*char.to_vkeys)
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# finds top-level dialog window by title and yields it to given block
|
|
265
|
+
def dialog(title, seconds=3)
|
|
266
|
+
d = begin
|
|
267
|
+
win = Window.top_level(title, seconds)
|
|
268
|
+
yield(win) ? win : nil
|
|
269
|
+
rescue TimeoutError
|
|
270
|
+
end
|
|
271
|
+
d.wait_for_close if d
|
|
272
|
+
return d
|
|
273
|
+
end
|
|
274
|
+
end
|