AXElements 0.6.0beta2 → 0.7.5
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/.yardopts +1 -2
- data/README.markdown +152 -88
- data/Rakefile +8 -103
- data/docs/Debugging.markdown +9 -2
- data/docs/KeyboardEvents.markdown +114 -49
- data/docs/Setting.markdown +1 -0
- data/docs/images/next_version.png +0 -0
- data/ext/accessibility/key_coder/extconf.rb +22 -0
- data/ext/accessibility/key_coder/key_coder.c +113 -0
- data/lib/AXElements.rb +2 -0
- data/lib/accessibility/core.rb +897 -0
- data/lib/accessibility/debug.rb +168 -0
- data/lib/accessibility/dsl.rb +697 -0
- data/lib/accessibility/enumerators.rb +104 -0
- data/lib/accessibility/errors.rb +32 -0
- data/lib/accessibility/factory.rb +153 -0
- data/lib/accessibility/graph.rb +150 -0
- data/lib/{ax_elements/inspector.rb → accessibility/pp_inspector.rb} +39 -28
- data/lib/accessibility/qualifier.rb +158 -0
- data/lib/accessibility/string.rb +494 -0
- data/lib/accessibility/translator.rb +178 -0
- data/lib/accessibility/version.rb +7 -0
- data/lib/accessibility.rb +79 -0
- data/lib/ax/application.rb +234 -0
- data/lib/{ax_elements/elements → ax}/button.rb +2 -0
- data/lib/ax/element.rb +518 -0
- data/lib/{ax_elements/elements → ax}/radio_button.rb +2 -0
- data/lib/ax/row.rb +37 -0
- data/lib/{ax_elements/elements → ax}/static_text.rb +2 -0
- data/lib/ax/systemwide.rb +86 -0
- data/lib/ax_elements/awesome_print.rb +25 -0
- data/lib/ax_elements/exception_workaround.rb +8 -0
- data/lib/ax_elements/nsarray_compat.rb +64 -0
- data/lib/ax_elements/vendor/inflection_data.rb +65 -0
- data/lib/ax_elements/vendor/inflections.rb +172 -0
- data/lib/ax_elements/vendor/inflector.rb +306 -0
- data/lib/ax_elements.rb +14 -25
- data/lib/minitest/ax_elements.rb +112 -12
- data/lib/mouse.rb +72 -46
- data/lib/rspec/expectations/ax_elements.rb +133 -6
- data/rakelib/doc.rake +13 -0
- data/rakelib/ext.rake +61 -0
- data/rakelib/gem.rake +28 -0
- data/rakelib/test.rake +53 -0
- data/test/helper.rb +11 -97
- data/test/integration/accessibility/test_core.rb +18 -0
- data/test/integration/accessibility/test_debug.rb +44 -0
- data/test/integration/accessibility/test_dsl.rb +225 -0
- data/test/integration/accessibility/test_enumerators.rb +122 -0
- data/test/integration/accessibility/test_errors.rb +38 -0
- data/test/integration/accessibility/test_notifications.rb +22 -0
- data/test/integration/accessibility/test_qualifier.rb +148 -0
- data/test/integration/ax/test_application.rb +56 -0
- data/test/integration/ax/test_element.rb +46 -0
- data/test/integration/ax/test_row.rb +23 -0
- data/test/integration/ax_elements/test_nsarray_compat.rb +43 -0
- data/test/integration/minitest/test_ax_elements.rb +98 -0
- data/test/integration/rspec/expectations/test_ax_elements.rb +58 -0
- data/test/integration/test_mouse.rb +35 -0
- data/test/sanity/accessibility/test_core.rb +553 -0
- data/test/sanity/accessibility/test_debug.rb +63 -0
- data/test/sanity/accessibility/test_dsl.rb +75 -0
- data/test/sanity/accessibility/test_errors.rb +10 -0
- data/test/sanity/accessibility/test_factory.rb +88 -0
- data/test/sanity/accessibility/test_pp_inspector.rb +110 -0
- data/test/sanity/accessibility/test_qualifier.rb +13 -0
- data/test/sanity/accessibility/test_string.rb +238 -0
- data/test/sanity/accessibility/test_translator.rb +145 -0
- data/test/sanity/ax/test_application.rb +90 -0
- data/test/sanity/ax/test_element.rb +80 -0
- data/test/sanity/ax/test_systemwide.rb +66 -0
- data/test/sanity/ax_elements/test_nsarray_compat.rb +16 -0
- data/test/sanity/ax_elements/test_nsobject_inspect.rb +11 -0
- data/test/sanity/minitest/test_ax_elements.rb +15 -0
- data/test/sanity/rspec/expectations/test_ax_elements.rb +12 -0
- data/test/sanity/test_ax_elements.rb +10 -0
- data/test/sanity/test_mouse.rb +19 -0
- metadata +111 -93
- data/LICENSE.txt +0 -25
- data/ext/key_coder/extconf.rb +0 -6
- data/ext/key_coder/key_coder.m +0 -77
- data/lib/ax_elements/accessibility/enumerators.rb +0 -104
- data/lib/ax_elements/accessibility/graph.rb +0 -118
- data/lib/ax_elements/accessibility/language.rb +0 -347
- data/lib/ax_elements/accessibility/qualifier.rb +0 -73
- data/lib/ax_elements/accessibility.rb +0 -166
- data/lib/ax_elements/core.rb +0 -541
- data/lib/ax_elements/element.rb +0 -593
- data/lib/ax_elements/elements/application.rb +0 -88
- data/lib/ax_elements/elements/row.rb +0 -30
- data/lib/ax_elements/elements/systemwide.rb +0 -46
- data/lib/ax_elements/macruby_extensions.rb +0 -255
- data/lib/ax_elements/notification.rb +0 -37
- data/lib/ax_elements/version.rb +0 -9
- data/test/elements/test_application.rb +0 -72
- data/test/elements/test_row.rb +0 -27
- data/test/elements/test_systemwide.rb +0 -38
- data/test/test_accessibility.rb +0 -127
- data/test/test_blankness.rb +0 -26
- data/test/test_core.rb +0 -448
- data/test/test_element.rb +0 -939
- data/test/test_enumerators.rb +0 -81
- data/test/test_inspector.rb +0 -130
- data/test/test_language.rb +0 -157
- data/test/test_macruby_extensions.rb +0 -303
- data/test/test_mouse.rb +0 -5
- data/test/test_search_semantics.rb +0 -143
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
require 'accessibility/version'
|
|
2
|
+
require 'ax_elements/vendor/inflector'
|
|
3
|
+
|
|
4
|
+
framework 'ApplicationServices'
|
|
5
|
+
|
|
6
|
+
unless Object.const_defined? :KAXIdentifierAttribute
|
|
7
|
+
##
|
|
8
|
+
# Added for backwards compatability with Snow Leopard.
|
|
9
|
+
# This attribute is standard with Lion and newer. AXElements depends
|
|
10
|
+
# on it being defined.
|
|
11
|
+
#
|
|
12
|
+
# @return [String]
|
|
13
|
+
KAXIdentifierAttribute = 'AXIdentifier'
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
##
|
|
18
|
+
# Maintain all the rules for transforming Cocoa constants into something
|
|
19
|
+
# a little more Rubyish.
|
|
20
|
+
class Accessibility::Translator
|
|
21
|
+
|
|
22
|
+
def self.instance
|
|
23
|
+
@instance ||= new
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
##
|
|
27
|
+
# Initialize the caches.
|
|
28
|
+
def initialize
|
|
29
|
+
init_unprefixes
|
|
30
|
+
init_normalizations
|
|
31
|
+
init_rubyisms
|
|
32
|
+
init_classifications
|
|
33
|
+
init_singularizations
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
##
|
|
37
|
+
# @note In the case of a predicate name, this will strip the 'Is'
|
|
38
|
+
# part of the name if it is present
|
|
39
|
+
#
|
|
40
|
+
# Takes an accessibility constant and returns a new string with the
|
|
41
|
+
# namespace prefix removed.
|
|
42
|
+
#
|
|
43
|
+
# @example
|
|
44
|
+
#
|
|
45
|
+
# unprefix 'AXTitle' # => 'Title'
|
|
46
|
+
# unprefix 'AXIsApplicationEnabled' # => 'ApplicationEnabled'
|
|
47
|
+
# unprefix 'MCAXEnabled' # => 'Enabled'
|
|
48
|
+
# unprefix KAXWindowCreatedNotification # => 'WindowCreated'
|
|
49
|
+
# unprefix NSAccessibilityButtonRole # => 'Button'
|
|
50
|
+
#
|
|
51
|
+
# @param [String]
|
|
52
|
+
# @return [String]
|
|
53
|
+
def unprefix key
|
|
54
|
+
@unprefixes[key]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
##
|
|
58
|
+
# Given a symbol, return the equivalent accessibility constant.
|
|
59
|
+
#
|
|
60
|
+
# @param [#to_sym]
|
|
61
|
+
# @param [Array<String>]
|
|
62
|
+
# @return [String]
|
|
63
|
+
def lookup key, values
|
|
64
|
+
@values = values
|
|
65
|
+
@rubyisms[key.to_sym]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# @return [Array<Symbol>]
|
|
69
|
+
def rubyize keys
|
|
70
|
+
keys.map { |x| @normalizations[x] }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
##
|
|
74
|
+
# Try to turn an arbitrary symbol into a notification constant, and
|
|
75
|
+
# then get the value of the constant.
|
|
76
|
+
#
|
|
77
|
+
# @param [#to_s]
|
|
78
|
+
# @return [String]
|
|
79
|
+
def guess_notification_for name
|
|
80
|
+
name = name.to_s.gsub /(?:^|_)(.)/ do $1.upcase! || $1 end
|
|
81
|
+
const = "KAX#{name}Notification"
|
|
82
|
+
Object.const_defined?(const) ? Object.const_get(const) : name
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
##
|
|
86
|
+
# Get the class name equivalent for a given symbol or string. This
|
|
87
|
+
# is just a caching front end to the `#classify` method from the
|
|
88
|
+
# ActiveSupport inflector.
|
|
89
|
+
#
|
|
90
|
+
# @example
|
|
91
|
+
#
|
|
92
|
+
# classify 'text_field' # => "TextField"
|
|
93
|
+
# classify 'buttons' # => "Button"
|
|
94
|
+
#
|
|
95
|
+
# @param [String]
|
|
96
|
+
# @return [String]
|
|
97
|
+
def classify klass
|
|
98
|
+
@classifications[klass]
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
##
|
|
102
|
+
# Get the singularized version of the word passed in. This is just
|
|
103
|
+
# a caching front end to the `#singularize` method from the
|
|
104
|
+
# ActiveSupport inflector.
|
|
105
|
+
#
|
|
106
|
+
# @example
|
|
107
|
+
#
|
|
108
|
+
# singularize 'buttons' # => 'button'
|
|
109
|
+
# singularize 'check_boxes' # => 'check_box'
|
|
110
|
+
#
|
|
111
|
+
# @param [String]
|
|
112
|
+
# @return [String]
|
|
113
|
+
def singularize klass
|
|
114
|
+
@singularizations[klass]
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
private
|
|
119
|
+
|
|
120
|
+
# @return [Hash{String=>String}]
|
|
121
|
+
def init_unprefixes
|
|
122
|
+
@unprefixes = Hash.new do |hash, key|
|
|
123
|
+
hash[key] = key.sub /^[A-Z]*?AX(?:Is)?|\s+/, EMPTY_STRING
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# @return [Hash{String=>Symbol}]
|
|
128
|
+
def init_normalizations
|
|
129
|
+
@normalizations = Hash.new do |hash, key|
|
|
130
|
+
hash[key] = Accessibility::Inflector.underscore(@unprefixes[key]).to_sym
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# @return [Hash{Symbol=>String}]
|
|
135
|
+
def init_rubyisms
|
|
136
|
+
@rubyisms = Hash.new do |hash, key|
|
|
137
|
+
@values.each do |v| hash[@normalizations[v]] = v end
|
|
138
|
+
hash.fetch(key) do |k|
|
|
139
|
+
chomped_key = k.chomp(QUESTION_MARK).to_sym
|
|
140
|
+
chomped_val = hash.fetch(chomped_key, nil)
|
|
141
|
+
hash[key] = chomped_val if chomped_val
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
# preload the table
|
|
145
|
+
@rubyisms[:id] = KAXIdentifierAttribute
|
|
146
|
+
@rubyisms[:placeholder] = KAXPlaceholderValueAttribute
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# @return [Hash{String=>String}]
|
|
150
|
+
def init_classifications
|
|
151
|
+
@classifications = Hash.new do |hash, key|
|
|
152
|
+
hash[key] = Accessibility::Inflector.classify(key)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# @return [Hash{String=>String}]
|
|
157
|
+
def init_singularizations
|
|
158
|
+
@singularizations = Hash.new do |hash, key|
|
|
159
|
+
hash[key] = Accessibility::Inflector.singularize(key)
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
##
|
|
164
|
+
# @private
|
|
165
|
+
#
|
|
166
|
+
# Cached for performance.
|
|
167
|
+
#
|
|
168
|
+
# @return [String]
|
|
169
|
+
EMPTY_STRING = ''
|
|
170
|
+
|
|
171
|
+
##
|
|
172
|
+
# @private
|
|
173
|
+
#
|
|
174
|
+
# Performance hack.
|
|
175
|
+
#
|
|
176
|
+
# @return [String]
|
|
177
|
+
QUESTION_MARK = '?'
|
|
178
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
require 'ax/application'
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
# The main AXElements namespace.
|
|
5
|
+
module Accessibility
|
|
6
|
+
class << self
|
|
7
|
+
|
|
8
|
+
# @group Finding an application object
|
|
9
|
+
|
|
10
|
+
##
|
|
11
|
+
# @todo Move to {AX::Aplication#initialize} eventually.
|
|
12
|
+
# @todo Find a way for this method to work without sleeping;
|
|
13
|
+
# consider looping begin/rescue/end until AX starts up
|
|
14
|
+
# @todo This needs to handle bad bundle identifier's gracefully
|
|
15
|
+
#
|
|
16
|
+
# This is the standard way of creating an application object. It will
|
|
17
|
+
# launch the app if it is not already running and then create the
|
|
18
|
+
# accessibility object.
|
|
19
|
+
#
|
|
20
|
+
# However, this method is a _HUGE_ hack in cases where the app is not
|
|
21
|
+
# already running; I've tried to register for notifications, launch
|
|
22
|
+
# synchronously, etc., but there is always a problem with accessibility
|
|
23
|
+
# not being ready.
|
|
24
|
+
#
|
|
25
|
+
# If this method fails to find an app with the appropriate bundle
|
|
26
|
+
# identifier then it will return nil, eventually.
|
|
27
|
+
#
|
|
28
|
+
# @example
|
|
29
|
+
#
|
|
30
|
+
# application_with_bundle_identifier 'com.apple.mail' # wait a few seconds
|
|
31
|
+
# application_with_bundle_identifier 'com.marketcircle.Daylite'
|
|
32
|
+
#
|
|
33
|
+
# @param [String] bundle a bundle identifier
|
|
34
|
+
# @return [AX::Application,nil]
|
|
35
|
+
def application_with_bundle_identifier bundle
|
|
36
|
+
10.times do
|
|
37
|
+
apps = NSRunningApplication.runningApplicationsWithBundleIdentifier bundle
|
|
38
|
+
return AX::Application.new(apps.first.processIdentifier) unless apps.empty?
|
|
39
|
+
launch_application bundle
|
|
40
|
+
sleep 2
|
|
41
|
+
end
|
|
42
|
+
nil
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
##
|
|
46
|
+
# @deprecated Use {AX::Application.new} instead.
|
|
47
|
+
#
|
|
48
|
+
# Get the accessibility object for an application given its localized
|
|
49
|
+
# name. This will only work if the application is already running.
|
|
50
|
+
#
|
|
51
|
+
# @example
|
|
52
|
+
#
|
|
53
|
+
# application_with_name 'Mail'
|
|
54
|
+
#
|
|
55
|
+
# @param [String] name name of the application to launch
|
|
56
|
+
# @return [AX::Application,nil]
|
|
57
|
+
def application_with_name name
|
|
58
|
+
AX::Application.new name
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @endgroup
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
##
|
|
67
|
+
# Asynchronously launch an application given the bundle identifier.
|
|
68
|
+
#
|
|
69
|
+
# @param [String] bundle the bundle identifier for the app
|
|
70
|
+
# @return [Boolean]
|
|
71
|
+
def launch_application bundle
|
|
72
|
+
NSWorkspace.sharedWorkspace.launchAppWithBundleIdentifier bundle,
|
|
73
|
+
options: NSWorkspaceLaunchAsync,
|
|
74
|
+
additionalEventParamDescriptor: nil,
|
|
75
|
+
launchIdentifier: nil
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
require 'ax/element'
|
|
2
|
+
require 'accessibility/string'
|
|
3
|
+
|
|
4
|
+
##
|
|
5
|
+
# Some additional constructors and conveniences for Application objects.
|
|
6
|
+
#
|
|
7
|
+
# As this class has evolved, it has gathered some functionality from
|
|
8
|
+
# the `NSRunningApplication` class.
|
|
9
|
+
class AX::Application < AX::Element
|
|
10
|
+
include Accessibility::String
|
|
11
|
+
|
|
12
|
+
##
|
|
13
|
+
# Overridden so that we can also cache the `NSRunningApplication`
|
|
14
|
+
# instance for this object.
|
|
15
|
+
#
|
|
16
|
+
# You can initialize an application object with either the process
|
|
17
|
+
# identifier (pid) of the application, the name of the application,
|
|
18
|
+
# an `NSRunningApplication` instance for the application, or an
|
|
19
|
+
# accessibility (`AXUIElementRef`) token.
|
|
20
|
+
def initialize arg
|
|
21
|
+
case arg
|
|
22
|
+
when Fixnum
|
|
23
|
+
super SYSTEMWIDE.application_for arg
|
|
24
|
+
@app = NSRunningApplication.runningApplicationWithProcessIdentifier arg
|
|
25
|
+
when String
|
|
26
|
+
SYSTEMWIDE.spin_run_loop
|
|
27
|
+
@app = NSWorkspace.sharedWorkspace.runningApplications
|
|
28
|
+
.find { |app| app.localizedName == arg }
|
|
29
|
+
super SYSTEMWIDE.application_for @app.processIdentifier
|
|
30
|
+
when NSRunningApplication
|
|
31
|
+
super SYSTEMWIDE.application_for arg.processIdentifier
|
|
32
|
+
@app = arg
|
|
33
|
+
else
|
|
34
|
+
super arg # assume it is an AXUIElementRef
|
|
35
|
+
@app = NSRunningApplication.runningApplicationWithProcessIdentifier pid
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# @group Attributes
|
|
41
|
+
|
|
42
|
+
##
|
|
43
|
+
# Overridden to handle the {Accessibility::Language#set_focus} case.
|
|
44
|
+
#
|
|
45
|
+
# (see AX::Element#attribute)
|
|
46
|
+
def attribute attr
|
|
47
|
+
case attr
|
|
48
|
+
when :focused?, :focused then active?
|
|
49
|
+
when :hidden?, :hidden then hidden?
|
|
50
|
+
else
|
|
51
|
+
super
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
##
|
|
56
|
+
# Ask the app whether or not it is the active app. This is equivalent
|
|
57
|
+
# to the dynamic #focused? method, but might make more sense to use
|
|
58
|
+
# in some cases.
|
|
59
|
+
def active?
|
|
60
|
+
@ref.spin_run_loop
|
|
61
|
+
@app.active?
|
|
62
|
+
end
|
|
63
|
+
alias_method :focused, :active?
|
|
64
|
+
alias_method :focused?, :active?
|
|
65
|
+
|
|
66
|
+
##
|
|
67
|
+
# Ask the app whether or not it is hidden.
|
|
68
|
+
def hidden?
|
|
69
|
+
@ref.spin_run_loop
|
|
70
|
+
@app.hidden?
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
##
|
|
74
|
+
# Ask the app whether or not it is still running.
|
|
75
|
+
def terminated?
|
|
76
|
+
@ref.spin_run_loop
|
|
77
|
+
@app.terminated?
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
##
|
|
81
|
+
# Overridden to handle the {Accessibility::Language#set_focus} case.
|
|
82
|
+
#
|
|
83
|
+
# (see AX::Element#set:to:)
|
|
84
|
+
def set attr, to: value
|
|
85
|
+
case attr
|
|
86
|
+
when :focused
|
|
87
|
+
perform(value ? :unhide : :hide)
|
|
88
|
+
when :active, :hidden
|
|
89
|
+
perform(value ? :hide : :unhide)
|
|
90
|
+
else
|
|
91
|
+
super
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# @group Actions
|
|
97
|
+
|
|
98
|
+
##
|
|
99
|
+
# Overridden to provide extra actions (e.g. `hide`, `terminate`).
|
|
100
|
+
#
|
|
101
|
+
# (see AX::Element#perform)
|
|
102
|
+
#
|
|
103
|
+
# @return [Boolean]
|
|
104
|
+
def perform name
|
|
105
|
+
case name
|
|
106
|
+
when :terminate
|
|
107
|
+
return true if terminated?
|
|
108
|
+
@app.terminate; sleep 0.2; terminated?
|
|
109
|
+
when :force_terminate
|
|
110
|
+
return true if terminated?
|
|
111
|
+
@app.forceTerminate; sleep 0.2; terminated?
|
|
112
|
+
when :hide
|
|
113
|
+
return true if hidden?
|
|
114
|
+
@app.hide; sleep 0.2; hidden?
|
|
115
|
+
when :unhide
|
|
116
|
+
return true if active?
|
|
117
|
+
@app.activateWithOptions(NSApplicationActivateIgnoringOtherApps)
|
|
118
|
+
sleep 0.2; active?
|
|
119
|
+
else
|
|
120
|
+
super
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
##
|
|
125
|
+
# Send keyboard input to `self`, the control in the app that currently
|
|
126
|
+
# has focus will receive the key presses.
|
|
127
|
+
#
|
|
128
|
+
# For details on how to format the string, check out the
|
|
129
|
+
# {file:docs/KeyboardEvents.markdown Keyboard} documentation.
|
|
130
|
+
#
|
|
131
|
+
# @return [Boolean]
|
|
132
|
+
def type_string string
|
|
133
|
+
@ref.post keyboard_events_for string
|
|
134
|
+
true
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# @todo doc and cleanup
|
|
138
|
+
def keydown key
|
|
139
|
+
@ref.post [[EventGenerator::CUSTOM[key], true]]
|
|
140
|
+
true
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# @todo doc and cleanup
|
|
144
|
+
def keyup key
|
|
145
|
+
@ref.post [[EventGenerator::CUSTOM[key], false]]
|
|
146
|
+
true
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# @return [AX::MenuItem]
|
|
150
|
+
def select_menu_item *path
|
|
151
|
+
target = navigate_menu *path
|
|
152
|
+
target.perform :press
|
|
153
|
+
target
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# @return [AX::MenuItem]
|
|
157
|
+
def navigate_menu *path
|
|
158
|
+
perform :unhide # can't navigate menus unless the app is up front
|
|
159
|
+
current = attribute(:menu_bar).search(:menu_bar_item, title: path.shift)
|
|
160
|
+
path.each do |part|
|
|
161
|
+
current.perform :press
|
|
162
|
+
next_item = current.search(:menu_item, title: part)
|
|
163
|
+
if next_item.blank?
|
|
164
|
+
failure = Accessibility::SearchFailure.new(current, :menu_item, title: part)
|
|
165
|
+
current.perform :cancel # close menu
|
|
166
|
+
raise failure
|
|
167
|
+
else
|
|
168
|
+
current = next_item
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
current
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
##
|
|
175
|
+
# Show the "About" window for the app. Returns the window that is
|
|
176
|
+
# opened.
|
|
177
|
+
#
|
|
178
|
+
# @return [AX::Window]
|
|
179
|
+
def show_about_window
|
|
180
|
+
windows = self.children.select { |x| x.kind_of? AX::Window }
|
|
181
|
+
select_menu_item self.title, /^About /
|
|
182
|
+
wait_for(:window, parent: self) { |window| !windows.include?(window) }
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
##
|
|
186
|
+
# @note This method assumes that the app has setup the standard
|
|
187
|
+
# CMD+, hotkey to open the pref window
|
|
188
|
+
#
|
|
189
|
+
# Try to open the preferences for the app. Returns the window that
|
|
190
|
+
# is opened.
|
|
191
|
+
#
|
|
192
|
+
# @return [AX::Window]
|
|
193
|
+
def show_preferences_window
|
|
194
|
+
windows = self.children.select { |x| x.kind_of? AX::Window }
|
|
195
|
+
type_string "\\COMMAND+,"
|
|
196
|
+
wait_for(:window, parent: self) { |window| !windows.include?(window) }
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# @endgroup
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
##
|
|
203
|
+
# @todo Include bundle identifier?
|
|
204
|
+
#
|
|
205
|
+
# Override the base class to make sure the pid is included.
|
|
206
|
+
def inspect
|
|
207
|
+
super.sub! />$/, "#{pp_checkbox(:focused)} pid=#{pid}>"
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
##
|
|
211
|
+
# Find the element in `self` that is present at point given.
|
|
212
|
+
#
|
|
213
|
+
# `nil` will be returned if there was nothing at that point.
|
|
214
|
+
#
|
|
215
|
+
# @param [#to_point]
|
|
216
|
+
# @return [AX::Element,nil]
|
|
217
|
+
def element_at point
|
|
218
|
+
process @ref.element_at point
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
##
|
|
222
|
+
# Get the bundle identifier for the application.
|
|
223
|
+
#
|
|
224
|
+
# @example
|
|
225
|
+
#
|
|
226
|
+
# safari.bundle_identifier 'com.apple.safari'
|
|
227
|
+
# daylite.bundle_identifier 'com.marketcircle.Daylite'
|
|
228
|
+
#
|
|
229
|
+
# @return [String]
|
|
230
|
+
def bundle_identifier
|
|
231
|
+
@app.bundleIdentifier
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
end
|