win_gui 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (164) hide show
  1. data/.document +5 -0
  2. data/.gitignore +21 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +43 -0
  5. data/Rakefile +58 -0
  6. data/VERSION +1 -0
  7. data/book_code/early_success/bundle.rb +34 -0
  8. data/book_code/early_success/english.txt +1 -0
  9. data/book_code/early_success/jruby_basics.rb +47 -0
  10. data/book_code/early_success/windows_basics.rb +97 -0
  11. data/book_code/guessing/locknote.rb +379 -0
  12. data/book_code/guessing/monkeyshines.rb +14 -0
  13. data/book_code/guessing/note.rb +120 -0
  14. data/book_code/guessing/note_spec.rb +175 -0
  15. data/book_code/guessing/replay.rb +21 -0
  16. data/book_code/guessing/seed.rb +9 -0
  17. data/book_code/guessing/spec_helper.rb +69 -0
  18. data/book_code/guessing/windows_gui.rb +247 -0
  19. data/book_code/home_stretch/junquenote.rb +151 -0
  20. data/book_code/home_stretch/locknote.rb +180 -0
  21. data/book_code/home_stretch/note.rb +144 -0
  22. data/book_code/home_stretch/note_spec.rb +191 -0
  23. data/book_code/home_stretch/spec_helper.rb +55 -0
  24. data/book_code/home_stretch/swing_gui.rb +50 -0
  25. data/book_code/home_stretch/windows_gui.rb +232 -0
  26. data/book_code/junquenote/exports.sh +10 -0
  27. data/book_code/junquenote/jruby_mac.sh +10 -0
  28. data/book_code/junquenote/junquenote_app.rb +262 -0
  29. data/book_code/novite/Rakefile +10 -0
  30. data/book_code/novite/app/controllers/application.rb +18 -0
  31. data/book_code/novite/app/controllers/guests_controller.rb +28 -0
  32. data/book_code/novite/app/controllers/parties_controller.rb +77 -0
  33. data/book_code/novite/app/helpers/application_helper.rb +11 -0
  34. data/book_code/novite/app/helpers/guests_helper.rb +10 -0
  35. data/book_code/novite/app/helpers/parties_helper.rb +10 -0
  36. data/book_code/novite/app/models/guest.rb +11 -0
  37. data/book_code/novite/app/models/party.rb +32 -0
  38. data/book_code/novite/app/models/party_mailer.rb +19 -0
  39. data/book_code/novite/app/views/layouts/application.rhtml +44 -0
  40. data/book_code/novite/app/views/parties/new.html.erb +42 -0
  41. data/book_code/novite/app/views/parties/show.html.erb +43 -0
  42. data/book_code/novite/app/views/party_mailer/invite.erb +17 -0
  43. data/book_code/novite/config/boot.rb +117 -0
  44. data/book_code/novite/config/database.yml +19 -0
  45. data/book_code/novite/config/environment.rb +67 -0
  46. data/book_code/novite/config/environments/development.rb +29 -0
  47. data/book_code/novite/config/environments/production.rb +27 -0
  48. data/book_code/novite/config/environments/test.rb +30 -0
  49. data/book_code/novite/config/initializers/inflections.rb +18 -0
  50. data/book_code/novite/config/initializers/mime_types.rb +13 -0
  51. data/book_code/novite/config/routes.rb +47 -0
  52. data/book_code/novite/db/migrate/001_create_parties.rb +26 -0
  53. data/book_code/novite/db/migrate/002_create_guests.rb +23 -0
  54. data/book_code/novite/db/schema.rb +41 -0
  55. data/book_code/novite/log/empty.txt +0 -0
  56. data/book_code/novite/public/.htaccess +40 -0
  57. data/book_code/novite/public/404.html +38 -0
  58. data/book_code/novite/public/422.html +38 -0
  59. data/book_code/novite/public/500.html +38 -0
  60. data/book_code/novite/public/dispatch.cgi +10 -0
  61. data/book_code/novite/public/dispatch.fcgi +24 -0
  62. data/book_code/novite/public/dispatch.rb +18 -0
  63. data/book_code/novite/public/favicon.ico +0 -0
  64. data/book_code/novite/public/images/rails.png +0 -0
  65. data/book_code/novite/public/index.html +285 -0
  66. data/book_code/novite/public/javascripts/application.js +10 -0
  67. data/book_code/novite/public/javascripts/controls.js +971 -0
  68. data/book_code/novite/public/javascripts/dragdrop.js +980 -0
  69. data/book_code/novite/public/javascripts/effects.js +1128 -0
  70. data/book_code/novite/public/javascripts/prototype.js +4233 -0
  71. data/book_code/novite/public/robots.txt +5 -0
  72. data/book_code/novite/script/about +3 -0
  73. data/book_code/novite/script/console +3 -0
  74. data/book_code/novite/script/destroy +3 -0
  75. data/book_code/novite/script/generate +3 -0
  76. data/book_code/novite/script/performance/benchmarker +3 -0
  77. data/book_code/novite/script/performance/profiler +3 -0
  78. data/book_code/novite/script/performance/request +3 -0
  79. data/book_code/novite/script/plugin +3 -0
  80. data/book_code/novite/script/process/inspector +3 -0
  81. data/book_code/novite/script/process/reaper +3 -0
  82. data/book_code/novite/script/process/spawner +3 -0
  83. data/book_code/novite/script/runner +3 -0
  84. data/book_code/novite/script/server +3 -0
  85. data/book_code/novite/test/test_helper.rb +46 -0
  86. data/book_code/one_more_thing/applescript.rb +68 -0
  87. data/book_code/one_more_thing/note_spec.rb +50 -0
  88. data/book_code/one_more_thing/spec_helper.rb +17 -0
  89. data/book_code/one_more_thing/textedit-pure.rb +28 -0
  90. data/book_code/one_more_thing/textedit.applescript +26 -0
  91. data/book_code/one_more_thing/textedit.rb +32 -0
  92. data/book_code/one_more_thing/textnote.rb +87 -0
  93. data/book_code/simplify/junquenote.rb +48 -0
  94. data/book_code/simplify/locknote.rb +46 -0
  95. data/book_code/simplify/note.rb +35 -0
  96. data/book_code/simplify/note_spec.rb +28 -0
  97. data/book_code/simplify/swing_gui.rb +45 -0
  98. data/book_code/simplify/windows_gui.rb +232 -0
  99. data/book_code/simplify/windows_gui_spec.rb +35 -0
  100. data/book_code/story/invite.story +19 -0
  101. data/book_code/story/journal.txt +29 -0
  102. data/book_code/story/novite_stories.rb +156 -0
  103. data/book_code/story/party.rb +149 -0
  104. data/book_code/story/password.rb +61 -0
  105. data/book_code/story/password.story +26 -0
  106. data/book_code/story/rsvp.story +29 -0
  107. data/book_code/tables/TestTime.html +93 -0
  108. data/book_code/tables/TestTimeSample.html +63 -0
  109. data/book_code/tables/calculate_time.rb +39 -0
  110. data/book_code/tables/calculator.rb +108 -0
  111. data/book_code/tables/calculator_actions.rb +27 -0
  112. data/book_code/tables/calculator_spec.rb +47 -0
  113. data/book_code/tables/fit.rb +32 -0
  114. data/book_code/tables/matrix.rb +109 -0
  115. data/book_code/tables/pseudocode.rb +17 -0
  116. data/book_code/tubes/book_selenium.rb +67 -0
  117. data/book_code/tubes/book_watir.rb +60 -0
  118. data/book_code/tubes/dragdrop.html +81 -0
  119. data/book_code/tubes/html_capture.rb +33 -0
  120. data/book_code/tubes/joke_list.rb +67 -0
  121. data/book_code/tubes/list_spec.rb +41 -0
  122. data/book_code/tubes/search_spec.rb +32 -0
  123. data/book_code/tubes/selenium_example.rb +66 -0
  124. data/book_code/tubes/selenium_link.rb +23 -0
  125. data/book_code/tubes/web_server.rb +14 -0
  126. data/book_code/windows/wgui.rb +29 -0
  127. data/book_code/windows/wobj.rb +25 -0
  128. data/book_code/windows/wsh.rb +25 -0
  129. data/book_code/with_rspec/empty_spec.rb +13 -0
  130. data/book_code/with_rspec/junquenote.rb +60 -0
  131. data/book_code/with_rspec/locknote.rb +129 -0
  132. data/book_code/with_rspec/note_spec.rb +32 -0
  133. data/book_code/with_rspec/should_examples.rb +18 -0
  134. data/exp/exp.rb +6 -0
  135. data/exp/exp_encodings.rb +40 -0
  136. data/exp/exp_enum_windows.rb +60 -0
  137. data/exp/exp_quik.rb +38 -0
  138. data/exp/exp_wsh.rb +115 -0
  139. data/exp/old/windows_basics.rb +80 -0
  140. data/exp/old/wnote.rb +80 -0
  141. data/exp/old/wnote_spec.rb +20 -0
  142. data/features/step_definitions/win_gui_steps.rb +0 -0
  143. data/features/support/env.rb +4 -0
  144. data/features/win_gui.feature +9 -0
  145. data/lib/note/java/jemmy.jar +0 -0
  146. data/lib/note/java/jnote.rb +48 -0
  147. data/lib/note/java/jruby_basics.rb +37 -0
  148. data/lib/note/java/junquenote_app.rb +262 -0
  149. data/lib/note/java/note_spec.rb +20 -0
  150. data/lib/note/win/locknote.rb +19 -0
  151. data/lib/note.rb +15 -0
  152. data/lib/win_gui/constants.rb +66 -0
  153. data/lib/win_gui/string_extensions.rb +24 -0
  154. data/lib/win_gui/win_gui.rb +274 -0
  155. data/lib/win_gui/window.rb +70 -0
  156. data/lib/win_gui.rb +3 -0
  157. data/spec/note/win/locknote_spec.rb +7 -0
  158. data/spec/spec.opts +2 -0
  159. data/spec/spec_helper.rb +100 -0
  160. data/spec/test_apps/locknote/LockNote.exe +0 -0
  161. data/spec/win_gui/string_extensions_spec.rb +61 -0
  162. data/spec/win_gui/win_gui_spec.rb +733 -0
  163. data/spec/win_gui/window_spec.rb +124 -0
  164. 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