rautomation 0.6.3 → 0.7.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.
Files changed (88) hide show
  1. data/.gitignore +34 -0
  2. data/Gemfile +3 -0
  3. data/Gemfile.lock +24 -0
  4. data/History.rdoc +21 -0
  5. data/LICENSE +1 -1
  6. data/README.rdoc +8 -7
  7. data/Rakefile +22 -32
  8. data/VERSION +1 -1
  9. data/ext/UiaDll/Release/UiaDll.dll +0 -0
  10. data/ext/UiaDll/UiaDll.suo +0 -0
  11. data/ext/UiaDll/UiaDll/UiaDll.cpp +236 -10
  12. data/lib/rautomation/adapter/autoit/locators.rb +1 -1
  13. data/lib/rautomation/adapter/autoit/text_field.rb +10 -1
  14. data/lib/rautomation/adapter/autoit/window.rb +50 -23
  15. data/lib/rautomation/adapter/helper.rb +6 -6
  16. data/lib/rautomation/adapter/ms_uia.rb +22 -0
  17. data/lib/rautomation/adapter/{win_ffi → ms_uia}/button.rb +3 -2
  18. data/lib/rautomation/adapter/ms_uia/button_helper.rb +25 -0
  19. data/lib/rautomation/adapter/ms_uia/checkbox.rb +27 -0
  20. data/lib/rautomation/adapter/{win_ffi → ms_uia}/constants.rb +20 -5
  21. data/lib/rautomation/adapter/ms_uia/control.rb +180 -0
  22. data/lib/rautomation/adapter/{win_ffi → ms_uia}/functions.rb +24 -26
  23. data/lib/rautomation/adapter/ms_uia/keystroke_converter.rb +122 -0
  24. data/lib/rautomation/adapter/{win_ffi → ms_uia}/label.rb +3 -2
  25. data/lib/rautomation/adapter/ms_uia/list_box.rb +91 -0
  26. data/lib/rautomation/adapter/ms_uia/list_item.rb +49 -0
  27. data/lib/rautomation/adapter/ms_uia/locators.rb +23 -0
  28. data/lib/rautomation/adapter/{win_ffi → ms_uia}/radio.rb +2 -2
  29. data/lib/rautomation/adapter/{win_ffi → ms_uia}/select_list.rb +41 -7
  30. data/lib/rautomation/adapter/ms_uia/table.rb +86 -0
  31. data/lib/rautomation/adapter/{win_ffi → ms_uia}/text_field.rb +5 -4
  32. data/lib/rautomation/adapter/ms_uia/uia_dll.rb +62 -0
  33. data/lib/rautomation/adapter/ms_uia/window.rb +364 -0
  34. data/lib/rautomation/adapter/win_32.rb +21 -0
  35. data/lib/rautomation/adapter/win_32/button.rb +14 -0
  36. data/lib/rautomation/adapter/{win_ffi → win_32}/button_helper.rb +1 -1
  37. data/lib/rautomation/adapter/win_32/checkbox.rb +14 -0
  38. data/lib/rautomation/adapter/win_32/constants.rb +68 -0
  39. data/lib/rautomation/adapter/{win_ffi → win_32}/control.rb +30 -23
  40. data/lib/rautomation/adapter/win_32/functions.rb +313 -0
  41. data/lib/rautomation/adapter/win_32/keys.rb +121 -0
  42. data/lib/rautomation/adapter/win_32/label.rb +10 -0
  43. data/lib/rautomation/adapter/win_32/list_box.rb +40 -0
  44. data/lib/rautomation/adapter/{win_ffi → win_32}/locators.rb +2 -2
  45. data/lib/rautomation/adapter/win_32/radio.rb +11 -0
  46. data/lib/rautomation/adapter/win_32/select_list.rb +97 -0
  47. data/lib/rautomation/adapter/win_32/table.rb +22 -0
  48. data/lib/rautomation/adapter/win_32/text_field.rb +42 -0
  49. data/lib/rautomation/adapter/{win_ffi → win_32}/window.rb +79 -62
  50. data/lib/rautomation/element_collections.rb +9 -1
  51. data/lib/rautomation/window.rb +17 -9
  52. data/rautomation.gemspec +24 -0
  53. data/spec/adapter/autoit/window_spec.rb +71 -0
  54. data/spec/adapter/{win_ffi → ms_uia}/button_spec.rb +1 -1
  55. data/spec/adapter/{win_ffi → ms_uia}/checkbox_spec.rb +9 -3
  56. data/spec/adapter/ms_uia/control_spec.rb +28 -0
  57. data/spec/adapter/ms_uia/keystroke_converter_spec.rb +50 -0
  58. data/spec/adapter/{win_ffi → ms_uia}/label_spec.rb +2 -2
  59. data/spec/adapter/ms_uia/list_item_spec.rb +14 -0
  60. data/spec/adapter/{win_ffi → ms_uia}/listbox_spec.rb +18 -5
  61. data/spec/adapter/{win_ffi → ms_uia}/radio_spec.rb +3 -1
  62. data/spec/adapter/ms_uia/select_list_spec.rb +109 -0
  63. data/spec/adapter/{win_ffi → ms_uia}/table_spec.rb +12 -1
  64. data/spec/adapter/{win_ffi → ms_uia}/text_field_spec.rb +2 -1
  65. data/spec/adapter/ms_uia/window_spec.rb +89 -0
  66. data/spec/adapter/win_32/button_spec.rb +30 -0
  67. data/spec/adapter/win_32/checkbox_spec.rb +48 -0
  68. data/spec/adapter/win_32/label_spec.rb +15 -0
  69. data/spec/adapter/win_32/listbox_spec.rb +42 -0
  70. data/spec/adapter/win_32/radio_spec.rb +32 -0
  71. data/spec/adapter/{win_ffi → win_32}/select_list_spec.rb +16 -16
  72. data/spec/adapter/win_32/table_spec.rb +28 -0
  73. data/spec/adapter/win_32/text_field_spec.rb +24 -0
  74. data/spec/adapter/{win_ffi → win_32}/window_spec.rb +19 -14
  75. data/spec/button_spec.rb +1 -0
  76. data/spec/buttons_spec.rb +4 -4
  77. data/spec/spec_helper.rb +30 -4
  78. data/spec/text_field_spec.rb +6 -7
  79. data/spec/window_spec.rb +12 -0
  80. data/spec/windows_spec.rb +19 -0
  81. metadata +116 -97
  82. data/lib/rautomation/adapter/win_ffi.rb +0 -21
  83. data/lib/rautomation/adapter/win_ffi/checkbox.rb +0 -19
  84. data/lib/rautomation/adapter/win_ffi/keystroke_converter.rb +0 -67
  85. data/lib/rautomation/adapter/win_ffi/list_box.rb +0 -60
  86. data/lib/rautomation/adapter/win_ffi/ms_uia/uia_dll.rb +0 -36
  87. data/lib/rautomation/adapter/win_ffi/table.rb +0 -57
  88. data/spec/adapter/win_ffi/keystroke_converter_spec.rb +0 -47
@@ -0,0 +1,21 @@
1
+ begin
2
+ gem "ffi"
3
+ rescue Gem::LoadError
4
+ raise Gem::LoadError, "Unable to load FFI gem. Install it with:\n\tgem install ffi"
5
+ end
6
+ require "ffi"
7
+ require File.dirname(__FILE__) + "/win_32/constants"
8
+ require File.dirname(__FILE__) + "/win_32/keys"
9
+ require File.dirname(__FILE__) + "/win_32/functions"
10
+ require File.dirname(__FILE__) + "/win_32/locators"
11
+ require File.dirname(__FILE__) + "/win_32/window"
12
+ require File.dirname(__FILE__) + "/win_32/button_helper"
13
+ require File.dirname(__FILE__) + "/win_32/control"
14
+ require File.dirname(__FILE__) + "/win_32/button"
15
+ require File.dirname(__FILE__) + "/win_32/checkbox"
16
+ require File.dirname(__FILE__) + "/win_32/radio"
17
+ require File.dirname(__FILE__) + "/win_32/text_field"
18
+ require File.dirname(__FILE__) + "/win_32/select_list"
19
+ require File.dirname(__FILE__) + "/win_32/table"
20
+ require File.dirname(__FILE__) + "/win_32/label"
21
+ require File.dirname(__FILE__) + "/win_32/list_box"
@@ -0,0 +1,14 @@
1
+ module RAutomation
2
+ module Adapter
3
+ module Win32
4
+ class Button < Control
5
+ include WaitHelper
6
+ include Locators
7
+
8
+ # Default locators used for searching buttons.
9
+ DEFAULT_LOCATORS = {:class => /button/i}
10
+
11
+ end
12
+ end
13
+ end
14
+ end
@@ -1,6 +1,6 @@
1
1
  module RAutomation
2
2
  module Adapter
3
- module WinFfi
3
+ module Win32
4
4
  module ButtonHelper
5
5
 
6
6
  def set?
@@ -0,0 +1,14 @@
1
+ module RAutomation
2
+ module Adapter
3
+ module Win32
4
+ class Checkbox < Control
5
+ include WaitHelper
6
+ include Locators
7
+ include ButtonHelper
8
+
9
+ DEFAULT_LOCATORS = {:class => /button/i}
10
+
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,68 @@
1
+ module RAutomation
2
+ module Adapter
3
+ module Win32
4
+ # @private
5
+ module Constants
6
+ WM_GETTEXT = 0xD
7
+ WM_SETTEXT = 0xC
8
+ WM_GETTEXTLENGTH = 0xE
9
+ WM_CLOSE = 0x10
10
+
11
+ SW_MAXIMIZE = 3
12
+ SW_MINIMIZE = 6
13
+ SW_RESTORE = 9
14
+
15
+ SMTO_ABORTIFHUNG = 0x2
16
+
17
+ STANDARD_RIGHTS_REQUIRED = 0xF0000
18
+ SYNCHRONIZE = 0x100000
19
+ PROCESS_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFF
20
+
21
+ BM_CLICK = 0xF5
22
+ BM_GETSTATE = 0xF2
23
+ BST_CHECKED = 0x1
24
+
25
+ # keybd_event constants
26
+ KEYEVENTF_EXTENDEDKEY = 0x1
27
+ KEYEVENTF_KEYUP = 0x2
28
+
29
+ # GetWindow constants
30
+ GW_ENABLEDPOPUP = 6
31
+
32
+ # HRESULT
33
+ S_OK = 0
34
+
35
+ # IAccessible Button States
36
+ STATE_SYSTEM_UNAVAILABLE = 0x00000001
37
+ STATE_SYSTEM_SELECTED = 0x00000002
38
+ STATE_SYSTEM_FOCUSED = 0x00000004
39
+ STATE_SYSTEM_CHECKED = 0x00000010
40
+
41
+ # Combobox
42
+ CB_GETCOUNT = 0x0146
43
+ CB_GETTOPINDEX = 0x015b
44
+ CB_GETLBTEXTLEN = 0x0149
45
+ CB_GETLBTEXT = 0x0148
46
+ CB_GETCURSEL = 0x0147
47
+ CB_GETDROPPEDCONTROLRECT = 0x0152
48
+ CB_GETITEMHEIGHT = 0x0154
49
+ CB_ERR = -1
50
+ CB_SETCURSEL = 0x14E
51
+ CB_SELECTSTRING = 0x14D
52
+ CB_SETEDITSEL = 0x142
53
+
54
+ # listview
55
+ LVM_FIRST = 0x1000
56
+ LVM_GETITEMCOUNT = LVM_FIRST + 4
57
+
58
+ # listbox
59
+ LB_GETCOUNT = 0x18b
60
+ LB_GETTEXT = 0x189
61
+ LB_GETTEXTLEN = 0x18a
62
+ LB_SETCURSEL = 0x186
63
+ LB_GETSEL = 0x187
64
+
65
+ end
66
+ end
67
+ end
68
+ end
@@ -1,6 +1,6 @@
1
1
  module RAutomation
2
2
  module Adapter
3
- module WinFfi
3
+ module Win32
4
4
  class Control
5
5
  include WaitHelper
6
6
  include Locators
@@ -19,56 +19,63 @@ module RAutomation
19
19
  extract(locators)
20
20
  end
21
21
 
22
+ def hwnd
23
+ Functions.control_hwnd(@window.hwnd, @locators)
24
+ end
25
+
26
+ def class_name
27
+ Functions.control_class(hwnd)
28
+ end
29
+
22
30
  def click
23
31
  assert_enabled
24
32
  clicked = false
25
33
  wait_until do
26
- hwnd = Functions.control_hwnd(@window.hwnd, @locators)
27
-
28
34
  @window.activate
29
35
  @window.active? &&
30
- Functions.set_control_focus(hwnd) &&
31
- Functions.control_click(hwnd) &&
32
- clicked = true # is clicked at least once
36
+ focus &&
37
+ Functions.control_click(hwnd) &&
38
+ clicked = true # is clicked at least once
33
39
 
34
40
  block_given? ? yield : clicked && !exist?
35
41
  end
36
42
  end
37
43
 
44
+ def hwnd
45
+ Functions.control_hwnd(@window.hwnd, @locators)
46
+ end
47
+
38
48
  def exist?
39
- !!Functions.control_hwnd(@window.hwnd, @locators)
49
+ !!hwnd
50
+ rescue UnknownElementException
51
+ false
40
52
  end
41
53
 
54
+ alias_method :exists?, :exist?
55
+
42
56
  def enabled?
43
57
  !disabled?
44
58
  end
45
59
 
46
60
  def disabled?
47
- Functions.unavailable?(Functions.control_hwnd(@window.hwnd, @locators))
61
+ Functions.unavailable? hwnd
48
62
  end
49
63
 
50
- def has_focus?
51
- Functions.has_focus?(Functions.control_hwnd(@window.hwnd, @locators))
52
- end
53
-
54
- def set_focus
64
+ def focus
55
65
  assert_enabled
56
- uia_control = UiaDll::element_from_handle(Functions.control_hwnd(@window.hwnd, @locators))
57
- UiaDll::set_focus(uia_control)
66
+ @window.activate
67
+ Functions.set_control_focus hwnd
58
68
  end
59
69
 
60
- def uia_control(automation_id)
61
- uia_window = UiaDll::element_from_handle(@window.hwnd) # finds IUIAutomationElement for given parent window
62
- uia_element = UiaDll::find_child_by_id(uia_window, automation_id.to_s)
63
- fail "Cannot find UIAutomationElement" if uia_element.nil?
64
- uia_element
70
+ def focused?
71
+ Functions.has_focus?(hwnd)
65
72
  end
66
73
 
67
- def matches_type(clazz)
68
- UiaDll::current_control_type(uia_control(@locators[:id])) == clazz
74
+ def value
75
+ Functions.control_value(hwnd)
69
76
  end
70
77
 
71
- alias_method :exists?, :exist?
78
+ private
72
79
 
73
80
  def assert_enabled
74
81
  raise "Cannot interact with disabled control #{@locators.inspect} on window #{@window.locators.inspect}!" if disabled?
@@ -0,0 +1,313 @@
1
+ module RAutomation
2
+ module Adapter
3
+ module Win32
4
+ # @private
5
+ module Functions
6
+ extend FFI::Library
7
+
8
+ # TODO once done adapt the path to the DLL (somewhere in the packaged gem)
9
+ ffi_lib 'user32', 'kernel32', 'ole32', File.dirname(__FILE__) + '/../../../../ext/IAccessibleDLL/Release/iaccessibleDll.dll'
10
+ ffi_convention :stdcall
11
+
12
+ callback :enum_callback, [:long, :pointer], :bool
13
+
14
+ # user32
15
+ attach_function :enum_windows, :EnumWindows,
16
+ [:enum_callback, :pointer], :long
17
+ attach_function :enum_child_windows, :EnumChildWindows,
18
+ [:long, :enum_callback, :pointer], :long
19
+ attach_function :_close_window, :CloseWindow,
20
+ [:long], :bool
21
+ attach_function :minimized, :IsIconic,
22
+ [:long], :bool
23
+ attach_function :_window_title, :GetWindowTextA,
24
+ [:long, :pointer, :int], :int
25
+ attach_function :window_title_length, :GetWindowTextLengthA,
26
+ [:long], :int
27
+ attach_function :window_exists, :IsWindow,
28
+ [:long], :bool
29
+ attach_function :_window_class, :GetClassNameA,
30
+ [:long, :pointer, :int], :int
31
+ attach_function :window_visible, :IsWindowVisible,
32
+ [:long], :bool
33
+ attach_function :show_window, :ShowWindow,
34
+ [:long, :int], :bool
35
+ attach_function :send_message, :SendMessageA,
36
+ [:long, :uint, :uint, :pointer], :long
37
+ attach_function :send_message_timeout, :SendMessageTimeoutA,
38
+ [:long, :uint, :uint, :pointer, :uint, :uint, :pointer], :bool
39
+ attach_function :post_message, :PostMessageA,
40
+ [:long, :uint, :uint, :pointer], :bool
41
+ attach_function :window_thread_process_id, :GetWindowThreadProcessId,
42
+ [:long, :pointer], :long
43
+ attach_function :attach_thread_input, :AttachThreadInput,
44
+ [:long, :long, :bool], :bool
45
+ attach_function :set_foreground_window, :SetForegroundWindow,
46
+ [:long], :bool
47
+ attach_function :bring_window_to_top, :BringWindowToTop,
48
+ [:long], :bool
49
+ attach_function :set_active_window, :SetActiveWindow,
50
+ [:long], :long
51
+ attach_function :foreground_window, :GetForegroundWindow,
52
+ [], :long
53
+ attach_function :send_key, :keybd_event,
54
+ [:uchar, :uchar, :int, :pointer], :void
55
+ attach_function :control_id, :GetDlgCtrlID,
56
+ [:long], :int
57
+ attach_function :_set_control_focus, :SetFocus,
58
+ [:long], :long
59
+ attach_function :get_window, :GetWindow,
60
+ [:long, :uint], :long
61
+ attach_function :get_last_error, :GetLastError,
62
+ [], :long
63
+
64
+ # kernel32
65
+ attach_function :current_thread_id, :GetCurrentThreadId,
66
+ [], :long
67
+ attach_function :open_process, :OpenProcess,
68
+ [:int, :bool, :int], :long
69
+ attach_function :terminate_process, :TerminateProcess,
70
+ [:long, :uint], :bool
71
+ attach_function :close_handle, :CloseHandle,
72
+ [:long], :bool
73
+ attach_function :load_library, :LoadLibraryA,
74
+ [:string], :long
75
+
76
+ # ole32
77
+ attach_function :co_initialize, :CoInitialize,
78
+ [:pointer], :uint16
79
+
80
+ # iaccessible
81
+ attach_function :get_button_state, :get_button_state,
82
+ [:long], :long
83
+ attach_function :get_table_row_strings, :get_table_row_strings,
84
+ [:long, :long, :pointer, :long, :pointer], :void
85
+ attach_function :select_table_row, :select_table_row,
86
+ [:long, :long, :long], :void
87
+ attach_function :get_table_row_state, :get_table_row_state,
88
+ [:long, :long, :long], :long
89
+
90
+ class << self
91
+
92
+ def window_title(hwnd)
93
+ title_length = window_title_length(hwnd) + 1
94
+ title = FFI::MemoryPointer.new :char, title_length
95
+ _window_title(hwnd, title, title_length)
96
+ title.read_string
97
+ end
98
+
99
+ alias_method :control_title, :window_title
100
+
101
+ def window_text(hwnd)
102
+ found_text = []
103
+ window_callback = FFI::Function.new(:bool, [:long, :pointer], {:convention => :stdcall}) do |child_hwnd, _|
104
+ found_text << text_for(child_hwnd)
105
+ true
106
+ end
107
+ enum_child_windows(hwnd, window_callback, nil)
108
+ found_text.join(" ")
109
+ end
110
+
111
+ alias_method :control_text, :window_text
112
+
113
+ def window_hwnd(locators)
114
+ find_hwnd(locators) do |hwnd|
115
+ window_visible(hwnd) && locators_match?(locators, window_properties(hwnd, locators))
116
+ end
117
+ end
118
+
119
+ def child_window_locators(parent_hwnd, locators)
120
+ child_hwnd = locators[:hwnd] || child_hwnd(parent_hwnd, locators)
121
+ if child_hwnd
122
+ locators.merge!(:hwnd => child_hwnd)
123
+ else
124
+ popup_hwnd = get_window(parent_hwnd, Constants::GW_ENABLEDPOPUP)
125
+ if popup_hwnd != parent_hwnd
126
+ popup_properties = window_properties(popup_hwnd, locators)
127
+ locators.merge!(:hwnd => popup_hwnd) if locators_match?(locators, popup_properties)
128
+ end
129
+ end
130
+ locators
131
+ end
132
+
133
+ def window_pid(hwnd)
134
+ pid = FFI::MemoryPointer.new :int
135
+ window_thread_process_id(hwnd, pid)
136
+ pid.read_int
137
+ end
138
+
139
+ def window_class(hwnd)
140
+ class_name = FFI::MemoryPointer.new :char, 512
141
+ _window_class(hwnd, class_name, 512)
142
+ class_name.read_string
143
+ end
144
+
145
+ alias_method :control_class, :window_class
146
+
147
+ def close_window(hwnd)
148
+ _close_window(hwnd)
149
+ closed = send_message_timeout(hwnd, Constants::WM_CLOSE,
150
+ 0, nil, Constants::SMTO_ABORTIFHUNG, 1000, nil)
151
+ # force it to close
152
+ unless closed
153
+ process_hwnd = open_process(Constants::PROCESS_ALL_ACCESS, false, window_pid(hwnd))
154
+ terminate_process(process_hwnd, 0)
155
+ close_handle(process_hwnd)
156
+ end
157
+ end
158
+
159
+ def activate_window(hwnd)
160
+ set_foreground_window(hwnd)
161
+ set_active_window(hwnd)
162
+ bring_window_to_top(hwnd)
163
+ within_foreground_thread(hwnd) do
164
+ set_foreground_window(hwnd)
165
+ set_active_window(hwnd)
166
+ bring_window_to_top(hwnd)
167
+ end
168
+ end
169
+
170
+ alias_method :activate_control, :activate_window
171
+
172
+ def control_hwnd(window_hwnd, locators)
173
+ hwnd = find_hwnd(locators, window_hwnd) do |hwnd|
174
+ locators_match?(locators, control_properties(hwnd, locators))
175
+ end
176
+
177
+ raise UnknownElementException, "Element with #{locators.inspect} does not exist" if hwnd == 0 || hwnd.nil?
178
+ hwnd
179
+ end
180
+
181
+ alias_method :child_hwnd, :control_hwnd
182
+
183
+ def control_value(control_hwnd)
184
+ text_for(control_hwnd)
185
+ end
186
+
187
+ def control_click(control_hwnd)
188
+ post_message(control_hwnd, Constants::BM_CLICK, 0, nil)
189
+ end
190
+
191
+ def set_control_focus(control_hwnd)
192
+ within_foreground_thread control_hwnd do
193
+ _set_control_focus(control_hwnd)
194
+ end
195
+ end
196
+
197
+ def set_control_text(control_hwnd, text)
198
+ send_message(control_hwnd, Constants::WM_SETTEXT, 0, text)
199
+ end
200
+
201
+ def control_set?(control_hwnd)
202
+ get_button_state(control_hwnd) & Constants::STATE_SYSTEM_CHECKED != 0
203
+ end
204
+
205
+ def has_focus?(control_hwnd)
206
+ get_button_state(control_hwnd) & Constants::STATE_SYSTEM_FOCUSED != 0
207
+ end
208
+
209
+ def unavailable?(control_hwnd)
210
+ get_button_state(control_hwnd) & Constants::STATE_SYSTEM_UNAVAILABLE != 0
211
+ end
212
+
213
+ def retrieve_combobox_item_text(control_hwnd, item_no)
214
+ text_len = send_message(control_hwnd, Constants::CB_GETLBTEXTLEN, item_no, nil)
215
+
216
+ string_buffer = FFI::MemoryPointer.new :char, text_len
217
+ send_message(control_hwnd, Constants::CB_GETLBTEXT, item_no, string_buffer)
218
+ string_buffer.read_string
219
+ end
220
+
221
+ def control_name(control_hwnd)
222
+ string_buffer = FFI::MemoryPointer.new :char, 255
223
+ if (get_control_name(control_hwnd, string_buffer) == Constants::S_OK)
224
+ string_buffer.read_string
225
+ else
226
+ fail "Cannot get name for control with HWND 0x" + control_hwnd.to_s(16)
227
+ end
228
+ end
229
+
230
+ def retrieve_table_strings_for_row(control_hwnd, row)
231
+ hModule = load_library("oleacc.dll") # TODO should be done only one time
232
+
233
+ strings_ptr = FFI::MemoryPointer.new :pointer
234
+ columns_ptr = FFI::MemoryPointer.new :pointer
235
+
236
+ get_table_row_strings(hModule, control_hwnd, strings_ptr, row, columns_ptr)
237
+ str_ptr = strings_ptr.read_pointer
238
+ columns = columns_ptr.read_long
239
+
240
+ str_ptr.get_array_of_string(0, columns)
241
+ end
242
+
243
+ private
244
+
245
+ def within_foreground_thread(hwnd)
246
+ foreground_thread = current_thread_id
247
+ other_thread = window_thread_process_id(hwnd, nil)
248
+ attach_thread_input(foreground_thread, other_thread, true) unless other_thread == foreground_thread
249
+ yield
250
+ ensure
251
+ attach_thread_input(foreground_thread, other_thread, false) unless other_thread == foreground_thread
252
+ end
253
+
254
+ def window_properties(hwnd, locators)
255
+ element_properties(:window, hwnd, locators)
256
+ end
257
+
258
+ def control_properties(hwnd, locators)
259
+ element_properties(:control, hwnd, locators)
260
+ end
261
+
262
+ def element_properties(type, hwnd, locators)
263
+ locators.inject({}) do |properties, locator|
264
+ properties[locator[0]] = self.send("#{type}_#{locator[0]}", hwnd) unless locator[0] == :index
265
+ properties
266
+ end
267
+ end
268
+
269
+ def locators_match?(locators, properties)
270
+ locators.all? do |locator, value|
271
+ locator == :index or
272
+ value.is_a?(Regexp) ? properties[locator] =~ value : properties[locator] == value
273
+ end
274
+ end
275
+
276
+ def find_hwnd(locators, window_hwnd = nil)
277
+ found_hwnd = nil
278
+ found_index = -1
279
+ window_callback = FFI::Function.new(:bool, [:long, :pointer], {:convention => :stdcall}) do |hwnd, _|
280
+ if yield(hwnd)
281
+ found_index += 1
282
+
283
+ if locators[:index]
284
+ found_hwnd = hwnd if locators[:index] == found_index
285
+ else
286
+ found_hwnd = hwnd
287
+ end
288
+ end
289
+ !found_hwnd
290
+ end
291
+
292
+ unless window_hwnd
293
+ enum_windows(window_callback, nil)
294
+ else
295
+ enum_child_windows(window_hwnd, window_callback, nil)
296
+ end
297
+
298
+ found_hwnd
299
+ end
300
+
301
+ def text_for(hwnd)
302
+ text_length = send_message(hwnd, Constants::WM_GETTEXTLENGTH, 0, nil) + 1
303
+ text = FFI::MemoryPointer.new :char, text_length
304
+ send_message(hwnd, Constants::WM_GETTEXT, text_length, text)
305
+ text.read_string
306
+ end
307
+
308
+ end
309
+ end
310
+ end
311
+ end
312
+ end
313
+