win_gui 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|