rdp-rautomation 0.6.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +5 -0
- data/.rspec +2 -0
- data/.yardopts +6 -0
- data/History.rdoc +103 -0
- data/LICENSE +20 -0
- data/README.rdoc +114 -0
- data/Rakefile +43 -0
- data/VERSION +1 -0
- data/ext/AutoItX/AutoItX.chm +0 -0
- data/ext/AutoItX/AutoItX3.dll +0 -0
- data/ext/IAccessibleDLL/IAccessibleDLL.sln +20 -0
- data/ext/IAccessibleDLL/IAccessibleDLL.suo +0 -0
- data/ext/IAccessibleDLL/IAccessibleDLL/IAccessibleDLL.cpp +30 -0
- data/ext/IAccessibleDLL/IAccessibleDLL/IAccessibleDLL.vcxproj +102 -0
- data/ext/IAccessibleDLL/IAccessibleDLL/IAccessibleDLL.vcxproj.filters +42 -0
- data/ext/IAccessibleDLL/IAccessibleDLL/IAccessibleDLL.vcxproj.user +3 -0
- data/ext/IAccessibleDLL/IAccessibleDLL/ReadMe.txt +48 -0
- data/ext/IAccessibleDLL/IAccessibleDLL/dllmain.cpp +19 -0
- data/ext/IAccessibleDLL/IAccessibleDLL/stdafx.cpp +8 -0
- data/ext/IAccessibleDLL/IAccessibleDLL/stdafx.h +22 -0
- data/ext/IAccessibleDLL/IAccessibleDLL/table_support.cpp +282 -0
- data/ext/IAccessibleDLL/IAccessibleDLL/targetver.h +8 -0
- data/ext/IAccessibleDLL/Release/IAccessibleDLL.dll +0 -0
- data/ext/ListViewExplorer/ListViewExplorer.sln +20 -0
- data/ext/ListViewExplorer/ListViewExplorer.suo +0 -0
- data/ext/ListViewExplorer/ListViewExplorer/ListViewExplorer.cpp +174 -0
- data/ext/ListViewExplorer/ListViewExplorer/ListViewExplorer.vcxproj +95 -0
- data/ext/ListViewExplorer/ListViewExplorer/ListViewExplorer.vcxproj.filters +42 -0
- data/ext/ListViewExplorer/ListViewExplorer/ListViewExplorer.vcxproj.user +3 -0
- data/ext/ListViewExplorer/ListViewExplorer/ReadMe.txt +40 -0
- data/ext/ListViewExplorer/ListViewExplorer/stdafx.cpp +8 -0
- data/ext/ListViewExplorer/ListViewExplorer/stdafx.h +17 -0
- data/ext/ListViewExplorer/ListViewExplorer/table_support.cpp +250 -0
- data/ext/ListViewExplorer/ListViewExplorer/table_support.h +2 -0
- data/ext/ListViewExplorer/ListViewExplorer/targetver.h +8 -0
- data/ext/UiaDll/Release/UiaDll.dll +0 -0
- data/ext/UiaDll/UiaDll.sln +20 -0
- data/ext/UiaDll/UiaDll.suo +0 -0
- data/ext/UiaDll/UiaDll/ReadMe.txt +48 -0
- data/ext/UiaDll/UiaDll/UiaDll.cpp +205 -0
- data/ext/UiaDll/UiaDll/UiaDll.vcxproj +104 -0
- data/ext/UiaDll/UiaDll/UiaDll.vcxproj.filters +42 -0
- data/ext/UiaDll/UiaDll/dllmain.cpp +39 -0
- data/ext/UiaDll/UiaDll/globals.h +3 -0
- data/ext/UiaDll/UiaDll/stdafx.cpp +8 -0
- data/ext/UiaDll/UiaDll/stdafx.h +19 -0
- data/ext/UiaDll/UiaDll/targetver.h +8 -0
- data/ext/WindowsForms/bin/WindowsForms.exe +0 -0
- data/ext/WindowsForms/src/WindowsForms/WindowsForms.sln +20 -0
- data/ext/WindowsForms/src/WindowsForms/WindowsForms.suo +0 -0
- data/ext/WindowsForms/src/WindowsForms/WindowsForms/AboutBox.Designer.cs +80 -0
- data/ext/WindowsForms/src/WindowsForms/WindowsForms/AboutBox.cs +103 -0
- data/ext/WindowsForms/src/WindowsForms/WindowsForms/AboutBox.resx +120 -0
- data/ext/WindowsForms/src/WindowsForms/WindowsForms/DataEntryForm.Designer.cs +187 -0
- data/ext/WindowsForms/src/WindowsForms/WindowsForms/DataEntryForm.cs +46 -0
- data/ext/WindowsForms/src/WindowsForms/WindowsForms/DataEntryForm.resx +120 -0
- data/ext/WindowsForms/src/WindowsForms/WindowsForms/MainFormWindow.Designer.cs +377 -0
- data/ext/WindowsForms/src/WindowsForms/WindowsForms/MainFormWindow.cs +78 -0
- data/ext/WindowsForms/src/WindowsForms/WindowsForms/MainFormWindow.resx +120 -0
- data/ext/WindowsForms/src/WindowsForms/WindowsForms/PersonForm.Designer.cs +119 -0
- data/ext/WindowsForms/src/WindowsForms/WindowsForms/PersonForm.cs +34 -0
- data/ext/WindowsForms/src/WindowsForms/WindowsForms/PersonForm.resx +120 -0
- data/ext/WindowsForms/src/WindowsForms/WindowsForms/Program.cs +21 -0
- data/ext/WindowsForms/src/WindowsForms/WindowsForms/Properties/AssemblyInfo.cs +36 -0
- data/ext/WindowsForms/src/WindowsForms/WindowsForms/Properties/Resources.Designer.cs +71 -0
- data/ext/WindowsForms/src/WindowsForms/WindowsForms/Properties/Resources.resx +117 -0
- data/ext/WindowsForms/src/WindowsForms/WindowsForms/Properties/Settings.Designer.cs +30 -0
- data/ext/WindowsForms/src/WindowsForms/WindowsForms/Properties/Settings.settings +7 -0
- data/ext/WindowsForms/src/WindowsForms/WindowsForms/SimpleElementsForm.Designer.cs +93 -0
- data/ext/WindowsForms/src/WindowsForms/WindowsForms/SimpleElementsForm.cs +19 -0
- data/ext/WindowsForms/src/WindowsForms/WindowsForms/SimpleElementsForm.resx +120 -0
- data/ext/WindowsForms/src/WindowsForms/WindowsForms/WindowsForms.csproj +123 -0
- data/ext/WindowsForms/src/WindowsForms/WindowsForms/bin/Release/WindowsForms.exe +0 -0
- data/lib/rautomation.rb +6 -0
- data/lib/rautomation/adapter/autoit.rb +5 -0
- data/lib/rautomation/adapter/autoit/button.rb +59 -0
- data/lib/rautomation/adapter/autoit/locators.rb +22 -0
- data/lib/rautomation/adapter/autoit/text_field.rb +61 -0
- data/lib/rautomation/adapter/autoit/window.rb +184 -0
- data/lib/rautomation/adapter/helper.rb +20 -0
- data/lib/rautomation/adapter/win_ffi.rb +21 -0
- data/lib/rautomation/adapter/win_ffi/button.rb +25 -0
- data/lib/rautomation/adapter/win_ffi/button_helper.rb +24 -0
- data/lib/rautomation/adapter/win_ffi/checkbox.rb +19 -0
- data/lib/rautomation/adapter/win_ffi/constants.rb +94 -0
- data/lib/rautomation/adapter/win_ffi/control.rb +79 -0
- data/lib/rautomation/adapter/win_ffi/functions.rb +333 -0
- data/lib/rautomation/adapter/win_ffi/keystroke_converter.rb +67 -0
- data/lib/rautomation/adapter/win_ffi/label.rb +21 -0
- data/lib/rautomation/adapter/win_ffi/list_box.rb +60 -0
- data/lib/rautomation/adapter/win_ffi/locators.rb +22 -0
- data/lib/rautomation/adapter/win_ffi/ms_uia/uia_dll.rb +36 -0
- data/lib/rautomation/adapter/win_ffi/radio.rb +19 -0
- data/lib/rautomation/adapter/win_ffi/select_list.rb +87 -0
- data/lib/rautomation/adapter/win_ffi/table.rb +57 -0
- data/lib/rautomation/adapter/win_ffi/text_field.rb +52 -0
- data/lib/rautomation/adapter/win_ffi/window.rb +226 -0
- data/lib/rautomation/button.rb +55 -0
- data/lib/rautomation/element_collections.rb +47 -0
- data/lib/rautomation/text_field.rb +60 -0
- data/lib/rautomation/wait_helper.rb +23 -0
- data/lib/rautomation/window.rb +234 -0
- data/spec/adapter/win_ffi/button_spec.rb +41 -0
- data/spec/adapter/win_ffi/checkbox_spec.rb +48 -0
- data/spec/adapter/win_ffi/keystroke_converter_spec.rb +47 -0
- data/spec/adapter/win_ffi/label_spec.rb +21 -0
- data/spec/adapter/win_ffi/listbox_spec.rb +52 -0
- data/spec/adapter/win_ffi/radio_spec.rb +37 -0
- data/spec/adapter/win_ffi/select_list_spec.rb +66 -0
- data/spec/adapter/win_ffi/table_spec.rb +39 -0
- data/spec/adapter/win_ffi/text_field_spec.rb +23 -0
- data/spec/adapter/win_ffi/window_spec.rb +43 -0
- data/spec/button_spec.rb +68 -0
- data/spec/buttons_spec.rb +21 -0
- data/spec/spec_helper.rb +96 -0
- data/spec/text_field_spec.rb +65 -0
- data/spec/text_fields_spec.rb +22 -0
- data/spec/window_spec.rb +122 -0
- data/spec/windows_spec.rb +55 -0
- metadata +207 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module RAutomation
|
|
2
|
+
module Adapter
|
|
3
|
+
autoload :Autoit, File.dirname(__FILE__) + "/autoit.rb"
|
|
4
|
+
autoload :WinFfi, File.dirname(__FILE__) + "/win_ffi.rb"
|
|
5
|
+
|
|
6
|
+
module Helper
|
|
7
|
+
extend self
|
|
8
|
+
|
|
9
|
+
# @private
|
|
10
|
+
# Retrieves default {Adapter} for the current platform.
|
|
11
|
+
def default_adapter
|
|
12
|
+
if ENV['OS'] == 'Windows_NT'
|
|
13
|
+
:win_ffi
|
|
14
|
+
else
|
|
15
|
+
raise "unsupported platform for RAutomation: #{RUBY_PLATFORM}"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -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_ffi/constants"
|
|
8
|
+
require File.dirname(__FILE__) + "/win_ffi/keystroke_converter"
|
|
9
|
+
require File.dirname(__FILE__) + "/win_ffi/functions"
|
|
10
|
+
require File.dirname(__FILE__) + "/win_ffi/locators"
|
|
11
|
+
require File.dirname(__FILE__) + "/win_ffi/window"
|
|
12
|
+
require File.dirname(__FILE__) + "/win_ffi/button_helper"
|
|
13
|
+
require File.dirname(__FILE__) + "/win_ffi/control"
|
|
14
|
+
require File.dirname(__FILE__) + "/win_ffi/button"
|
|
15
|
+
require File.dirname(__FILE__) + "/win_ffi/checkbox"
|
|
16
|
+
require File.dirname(__FILE__) + "/win_ffi/radio"
|
|
17
|
+
require File.dirname(__FILE__) + "/win_ffi/text_field"
|
|
18
|
+
require File.dirname(__FILE__) + "/win_ffi/select_list"
|
|
19
|
+
require File.dirname(__FILE__) + "/win_ffi/table"
|
|
20
|
+
require File.dirname(__FILE__) + "/win_ffi/label"
|
|
21
|
+
require File.dirname(__FILE__) + "/win_ffi/list_box"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module RAutomation
|
|
2
|
+
module Adapter
|
|
3
|
+
module WinFfi
|
|
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
|
+
# @see RAutomation::Button#value
|
|
12
|
+
def value
|
|
13
|
+
Functions.control_value(Functions.control_hwnd(@window.hwnd, @locators))
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def exist?
|
|
17
|
+
@locators[:id].nil? ? super : super && matches_type(Constants::UIA_BUTTON_CONTROL_TYPE)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
alias_method :exists?, :exist?
|
|
21
|
+
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module RAutomation
|
|
2
|
+
module Adapter
|
|
3
|
+
module WinFfi
|
|
4
|
+
module ButtonHelper
|
|
5
|
+
|
|
6
|
+
def set?
|
|
7
|
+
control_hwnd = Functions.control_hwnd(@window.hwnd, @locators)
|
|
8
|
+
Functions.control_set? control_hwnd
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# @todo call a windows function to do this without clicking
|
|
12
|
+
def clear
|
|
13
|
+
click {!set?} if set?
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @todo call a windows function to do this without clicking
|
|
17
|
+
def set
|
|
18
|
+
click {set?} unless set?
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module RAutomation
|
|
2
|
+
module Adapter
|
|
3
|
+
module WinFfi
|
|
4
|
+
class Checkbox < Control
|
|
5
|
+
include WaitHelper
|
|
6
|
+
include Locators
|
|
7
|
+
include ButtonHelper
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def exist?
|
|
11
|
+
@locators[:id].nil? ? super : super && matches_type(Constants::UIA_CHECKBOX_CONTROL_TYPE)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
alias_method :exists?, :exist?
|
|
15
|
+
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
module RAutomation
|
|
2
|
+
module Adapter
|
|
3
|
+
module WinFfi
|
|
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
|
+
VK_BACK = 0x08
|
|
30
|
+
VK_TAB = 0x09
|
|
31
|
+
VK_RETURN = 0x0D
|
|
32
|
+
VK_SPACE = 0x20
|
|
33
|
+
VK_CAPITAL = 0x14
|
|
34
|
+
VK_LEFT = 0x25
|
|
35
|
+
VK_UP = 0x26
|
|
36
|
+
VK_RIGHT = 0x27
|
|
37
|
+
VK_DOWN = 0x28
|
|
38
|
+
VK_SHIFT = 0x10
|
|
39
|
+
VK_LSHIFT = 0xA0
|
|
40
|
+
VK_RSHIFT = 0xA1
|
|
41
|
+
VK_MENU = 0x12
|
|
42
|
+
VK_LMENU = 0xA4
|
|
43
|
+
VK_RMENU = 0xA5
|
|
44
|
+
VK_CONTROL = 0x11
|
|
45
|
+
VK_LCONTROL = 0xA2
|
|
46
|
+
VK_RCONTROL = 0xA3
|
|
47
|
+
VK_ESCAPE = 0x1B
|
|
48
|
+
VK_END = 0x23
|
|
49
|
+
VK_HOME = 0x24
|
|
50
|
+
VK_NUMLOCK = 0x90
|
|
51
|
+
VK_DELETE = 0x2E
|
|
52
|
+
VK_INSERT = 0x2D
|
|
53
|
+
|
|
54
|
+
# GetWindow constants
|
|
55
|
+
GW_ENABLEDPOPUP = 6
|
|
56
|
+
|
|
57
|
+
# HRESULT
|
|
58
|
+
S_OK = 0
|
|
59
|
+
|
|
60
|
+
# IAccessible Button States
|
|
61
|
+
STATE_SYSTEM_UNAVAILABLE = 0x00000001
|
|
62
|
+
STATE_SYSTEM_SELECTED = 0x00000002
|
|
63
|
+
STATE_SYSTEM_FOCUSED = 0x00000004
|
|
64
|
+
STATE_SYSTEM_CHECKED = 0x00000010
|
|
65
|
+
|
|
66
|
+
# Combobox
|
|
67
|
+
CB_GETCOUNT = 0x0146
|
|
68
|
+
CB_GETLBTEXTLEN = 0x0149
|
|
69
|
+
CB_GETLBTEXT = 0x0148
|
|
70
|
+
CB_GETCURSEL = 0x0147
|
|
71
|
+
CB_ERR = -1
|
|
72
|
+
CB_SETCURSEL = 0x14E
|
|
73
|
+
|
|
74
|
+
# listview
|
|
75
|
+
LVM_FIRST = 0x1000
|
|
76
|
+
LVM_GETITEMCOUNT = LVM_FIRST + 4
|
|
77
|
+
|
|
78
|
+
# UI Automation control type IDs
|
|
79
|
+
UIA_LIST_CONTROL_TYPE = 50008
|
|
80
|
+
UIA_LIST_ITEM_CONTROL_TYPE = 50007
|
|
81
|
+
UIA_CHECKBOX_CONTROL_TYPE = 50002
|
|
82
|
+
UIA_BUTTON_CONTROL_TYPE = 50000
|
|
83
|
+
UIA_LABEL_CONTROL_TYPE = 50020
|
|
84
|
+
UIA_RADIO_BUTTON_CONTROL_TYPE = 50013
|
|
85
|
+
UIA_COMBOBOX_CONTROL_TYPE = 50003
|
|
86
|
+
UIA_EDIT_CONTROL_TYPE = 50004
|
|
87
|
+
UIA_HEADER_CONTROL_TYPE = 50034
|
|
88
|
+
UIA_HEADER_ITEM_CONTROL_TYPE = 50035
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
module RAutomation
|
|
2
|
+
module Adapter
|
|
3
|
+
module WinFfi
|
|
4
|
+
class Control
|
|
5
|
+
include WaitHelper
|
|
6
|
+
include Locators
|
|
7
|
+
|
|
8
|
+
# Creates the control object.
|
|
9
|
+
# @note this method is not meant to be accessed directly
|
|
10
|
+
# @param [RAutomation::Window] window this button belongs to.
|
|
11
|
+
# @param [Hash] locators for searching the button.
|
|
12
|
+
# @option locators [String, Regexp] :value Value (text) of the button
|
|
13
|
+
# @option locators [String, Regexp] :class Internal class name of the button
|
|
14
|
+
# @option locators [String, Fixnum] :id Internal ID of the button
|
|
15
|
+
# @option locators [String, Fixnum] :index 0-based index to specify n-th button if all other criteria match
|
|
16
|
+
# @see RAutomation::Window#button
|
|
17
|
+
def initialize(window, locators)
|
|
18
|
+
@window = window
|
|
19
|
+
extract(locators)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def click
|
|
23
|
+
assert_enabled
|
|
24
|
+
clicked = false
|
|
25
|
+
wait_until do
|
|
26
|
+
hwnd = Functions.control_hwnd(@window.hwnd, @locators)
|
|
27
|
+
|
|
28
|
+
@window.activate
|
|
29
|
+
@window.active? &&
|
|
30
|
+
Functions.set_control_focus(hwnd) &&
|
|
31
|
+
Functions.control_click(hwnd) &&
|
|
32
|
+
clicked = true # is clicked at least once
|
|
33
|
+
|
|
34
|
+
block_given? ? yield : clicked && !exist?
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def exist?
|
|
39
|
+
!!Functions.control_hwnd(@window.hwnd, @locators)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def enabled?
|
|
43
|
+
!disabled?
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def disabled?
|
|
47
|
+
Functions.unavailable?(Functions.control_hwnd(@window.hwnd, @locators))
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def has_focus?
|
|
51
|
+
Functions.has_focus?(Functions.control_hwnd(@window.hwnd, @locators))
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def set_focus
|
|
55
|
+
assert_enabled
|
|
56
|
+
uia_control = UiaDll::element_from_handle(Functions.control_hwnd(@window.hwnd, @locators))
|
|
57
|
+
UiaDll::set_focus(uia_control)
|
|
58
|
+
end
|
|
59
|
+
|
|
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
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def matches_type(clazz)
|
|
68
|
+
UiaDll::current_control_type(uia_control(@locators[:id])) == clazz
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
alias_method :exists?, :exist?
|
|
72
|
+
|
|
73
|
+
def assert_enabled
|
|
74
|
+
raise "Cannot interact with disabled control #{@locators.inspect} on window #{@window.locators.inspect}!" if disabled?
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
module RAutomation
|
|
2
|
+
module Adapter
|
|
3
|
+
module WinFfi
|
|
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 :open_process, :OpenProcess,
|
|
66
|
+
[:int, :bool, :int], :long
|
|
67
|
+
attach_function :terminate_process, :TerminateProcess,
|
|
68
|
+
[:long, :uint], :bool
|
|
69
|
+
attach_function :close_handle, :CloseHandle,
|
|
70
|
+
[:long], :bool
|
|
71
|
+
attach_function :load_library, :LoadLibraryA,
|
|
72
|
+
[:string], :long
|
|
73
|
+
|
|
74
|
+
# ole32
|
|
75
|
+
attach_function :co_initialize, :CoInitialize,
|
|
76
|
+
[:pointer], :uint16
|
|
77
|
+
|
|
78
|
+
# iaccessible
|
|
79
|
+
attach_function :get_button_state, :get_button_state,
|
|
80
|
+
[:long], :long
|
|
81
|
+
attach_function :get_table_row_strings, :get_table_row_strings,
|
|
82
|
+
[:long, :long, :pointer, :long, :pointer], :void
|
|
83
|
+
attach_function :select_table_row, :select_table_row,
|
|
84
|
+
[:long, :long, :long], :void
|
|
85
|
+
attach_function :get_table_row_state, :get_table_row_state,
|
|
86
|
+
[:long, :long, :long], :long
|
|
87
|
+
|
|
88
|
+
class << self
|
|
89
|
+
|
|
90
|
+
def window_title(hwnd)
|
|
91
|
+
title_length = window_title_length(hwnd) + 1
|
|
92
|
+
title = FFI::MemoryPointer.new :char, title_length
|
|
93
|
+
_window_title(hwnd, title, title_length)
|
|
94
|
+
title.read_string
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
alias_method :control_title, :window_title
|
|
98
|
+
|
|
99
|
+
def window_text(hwnd)
|
|
100
|
+
found_text = ""
|
|
101
|
+
window_callback = FFI::Function.new(:bool, [:long, :pointer], {:convention => :stdcall}) do |child_hwnd, _|
|
|
102
|
+
found_text << text_for(child_hwnd)
|
|
103
|
+
true
|
|
104
|
+
end
|
|
105
|
+
enum_child_windows(hwnd, window_callback, nil)
|
|
106
|
+
found_text
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
alias_method :control_text, :window_text
|
|
110
|
+
|
|
111
|
+
def window_hwnd(locators)
|
|
112
|
+
find_hwnd(locators) do |hwnd|
|
|
113
|
+
window_visible(hwnd) && locators_match?(locators, window_properties(hwnd, locators))
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def child_window_locators(parent_hwnd, locators)
|
|
118
|
+
child_hwnd = locators[:hwnd] || child_hwnd(parent_hwnd, locators)
|
|
119
|
+
if child_hwnd
|
|
120
|
+
locators.merge!(:hwnd => child_hwnd)
|
|
121
|
+
else
|
|
122
|
+
popup_hwnd = get_window(parent_hwnd, Constants::GW_ENABLEDPOPUP)
|
|
123
|
+
if popup_hwnd != parent_hwnd
|
|
124
|
+
popup_properties = window_properties(popup_hwnd, locators)
|
|
125
|
+
locators.merge!(:hwnd => popup_hwnd) if locators_match?(locators, popup_properties)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
locators
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def child_window_locators(parent_hwnd, locators)
|
|
132
|
+
child_hwnd = locators[:hwnd] || child_hwnd(parent_hwnd, locators)
|
|
133
|
+
if child_hwnd
|
|
134
|
+
locators.merge!(:hwnd => child_hwnd)
|
|
135
|
+
else
|
|
136
|
+
popup_hwnd = get_window(parent_hwnd, Constants::GW_ENABLEDPOPUP)
|
|
137
|
+
if popup_hwnd != parent_hwnd
|
|
138
|
+
popup_properties = window_properties(popup_hwnd, locators)
|
|
139
|
+
locators.merge!(:hwnd => popup_hwnd) if locators_match?(locators, popup_properties)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
locators
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def window_pid(hwnd)
|
|
146
|
+
pid = FFI::MemoryPointer.new :int
|
|
147
|
+
window_thread_process_id(hwnd, pid)
|
|
148
|
+
pid.read_int
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def window_class(hwnd)
|
|
152
|
+
class_name = FFI::MemoryPointer.new :char, 512
|
|
153
|
+
_window_class(hwnd, class_name, 512)
|
|
154
|
+
class_name.read_string
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
alias_method :control_class, :window_class
|
|
158
|
+
|
|
159
|
+
def close_window(hwnd)
|
|
160
|
+
_close_window(hwnd)
|
|
161
|
+
closed = send_message_timeout(hwnd, Constants::WM_CLOSE,
|
|
162
|
+
0, nil, Constants::SMTO_ABORTIFHUNG, 1000, nil)
|
|
163
|
+
# force it to close
|
|
164
|
+
unless closed
|
|
165
|
+
process_hwnd = open_process(Constants::PROCESS_ALL_ACCESS, false, window_pid(hwnd))
|
|
166
|
+
terminate_process(process_hwnd, 0)
|
|
167
|
+
close_handle(process_hwnd)
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def activate_window(hwnd)
|
|
172
|
+
set_foreground_window(hwnd)
|
|
173
|
+
set_active_window(hwnd)
|
|
174
|
+
bring_window_to_top(hwnd)
|
|
175
|
+
within_foreground_thread(hwnd) do
|
|
176
|
+
set_foreground_window(hwnd)
|
|
177
|
+
set_active_window(hwnd)
|
|
178
|
+
bring_window_to_top(hwnd)
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def control_hwnd(window_hwnd, locators)
|
|
183
|
+
if locators[:id].nil?
|
|
184
|
+
find_hwnd(locators, window_hwnd) do |hwnd|
|
|
185
|
+
locators_match?(locators, control_properties(hwnd, locators))
|
|
186
|
+
end
|
|
187
|
+
else
|
|
188
|
+
uia_window = UiaDll::element_from_handle(window_hwnd) # finds IUIAutomationElement for given parent window
|
|
189
|
+
uia_control = UiaDll::find_child_by_id(uia_window, locators[:id].to_s)
|
|
190
|
+
hwnd = UiaDll::current_native_window_handle(uia_control) # return HWND of UIA element
|
|
191
|
+
raise UnknownElementException, "#{locators[:id]} does not exist" if hwnd == 0
|
|
192
|
+
hwnd
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
alias_method :child_hwnd, :control_hwnd
|
|
197
|
+
|
|
198
|
+
def control_value(control_hwnd)
|
|
199
|
+
text_for(control_hwnd)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def control_click(control_hwnd)
|
|
203
|
+
post_message(control_hwnd, Constants::BM_CLICK, 0, nil)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def set_control_focus(control_hwnd)
|
|
207
|
+
within_foreground_thread control_hwnd do
|
|
208
|
+
_set_control_focus(control_hwnd)
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def set_control_text(control_hwnd, text)
|
|
213
|
+
send_message(control_hwnd, Constants::WM_SETTEXT, 0, text)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def control_set?(control_hwnd)
|
|
217
|
+
get_button_state(control_hwnd) & Constants::STATE_SYSTEM_CHECKED != 0
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def has_focus?(control_hwnd)
|
|
221
|
+
get_button_state(control_hwnd) & Constants::STATE_SYSTEM_FOCUSED != 0
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def unavailable?(control_hwnd)
|
|
225
|
+
get_button_state(control_hwnd) & Constants::STATE_SYSTEM_UNAVAILABLE != 0
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def retrieve_combobox_item_text(control_hwnd, item_no)
|
|
229
|
+
text_len = send_message(control_hwnd, Constants::CB_GETLBTEXTLEN, item_no, nil)
|
|
230
|
+
|
|
231
|
+
string_buffer = FFI::MemoryPointer.new :char, text_len
|
|
232
|
+
send_message(control_hwnd, Constants::CB_GETLBTEXT, item_no, string_buffer)
|
|
233
|
+
string_buffer.read_string
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def control_name(control_hwnd)
|
|
237
|
+
string_buffer = FFI::MemoryPointer.new :char, 255
|
|
238
|
+
if (get_control_name(control_hwnd, string_buffer) == Constants::S_OK)
|
|
239
|
+
string_buffer.read_string
|
|
240
|
+
else
|
|
241
|
+
fail "Cannot get name for control with HWND 0x" + control_hwnd.to_s(16)
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def retrieve_table_strings_for_row(control_hwnd, row)
|
|
246
|
+
hModule = load_library("oleacc.dll") # TODO should be done only one time
|
|
247
|
+
|
|
248
|
+
strings_ptr = FFI::MemoryPointer.new :pointer
|
|
249
|
+
columns_ptr = FFI::MemoryPointer.new :pointer
|
|
250
|
+
|
|
251
|
+
get_table_row_strings(hModule, control_hwnd, strings_ptr, row, columns_ptr)
|
|
252
|
+
str_ptr = strings_ptr.read_pointer
|
|
253
|
+
columns = columns_ptr.read_long
|
|
254
|
+
|
|
255
|
+
str_ptr.get_array_of_string(0, columns)
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
private
|
|
259
|
+
|
|
260
|
+
def within_foreground_thread(hwnd)
|
|
261
|
+
foreground_thread = window_thread_process_id(foreground_window, nil)
|
|
262
|
+
other_thread = window_thread_process_id(hwnd, nil)
|
|
263
|
+
attach_thread_input(foreground_thread, other_thread, true) unless other_thread == foreground_thread
|
|
264
|
+
yield
|
|
265
|
+
ensure
|
|
266
|
+
attach_thread_input(foreground_thread, other_thread, false) unless other_thread == foreground_thread
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def window_properties(hwnd, locators)
|
|
270
|
+
element_properties(:window, hwnd, locators)
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def control_properties(hwnd, locators)
|
|
274
|
+
element_properties(:control, hwnd, locators)
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def element_properties(type, hwnd, locators)
|
|
278
|
+
locators.inject({}) do |properties, locator|
|
|
279
|
+
properties[locator[0]] = self.send("#{type}_#{locator[0]}", hwnd) unless locator[0] == :index
|
|
280
|
+
properties
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def locators_match?(locators, properties)
|
|
285
|
+
locators.all? do |locator, value|
|
|
286
|
+
if locator == :index
|
|
287
|
+
true
|
|
288
|
+
elsif value.is_a?(Regexp)
|
|
289
|
+
properties[locator] =~ value
|
|
290
|
+
else
|
|
291
|
+
properties[locator] == value
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def find_hwnd(locators, window_hwnd = nil)
|
|
297
|
+
found_hwnd = nil
|
|
298
|
+
found_index = -1
|
|
299
|
+
window_callback = FFI::Function.new(:bool, [:long, :pointer], {:convention => :stdcall}) do |hwnd, _|
|
|
300
|
+
if yield(hwnd)
|
|
301
|
+
found_index += 1
|
|
302
|
+
|
|
303
|
+
if locators[:index]
|
|
304
|
+
found_hwnd = hwnd if locators[:index] == found_index
|
|
305
|
+
else
|
|
306
|
+
found_hwnd = hwnd
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
!found_hwnd
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
unless window_hwnd
|
|
313
|
+
enum_windows(window_callback, nil)
|
|
314
|
+
else
|
|
315
|
+
enum_child_windows(window_hwnd, window_callback, nil)
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
found_hwnd
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def text_for(hwnd)
|
|
322
|
+
text_length = send_message(hwnd, Constants::WM_GETTEXTLENGTH, 0, nil) + 1
|
|
323
|
+
text = FFI::MemoryPointer.new :char, text_length
|
|
324
|
+
send_message(hwnd, Constants::WM_GETTEXT, text_length, text)
|
|
325
|
+
text.read_string
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|