accessibility_core 0.1.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.
- data/.yardopts +9 -0
- data/History.markdown +36 -0
- data/README.markdown +66 -0
- data/Rakefile +72 -0
- data/ext/accessibility/bridge/ext/accessibility/bridge/bridge.c +490 -0
- data/ext/accessibility/bridge/ext/accessibility/bridge/extconf.rb +22 -0
- data/ext/accessibility/bridge/lib/accessibility/bridge.rb +33 -0
- data/ext/accessibility/bridge/lib/accessibility/bridge/common.rb +57 -0
- data/ext/accessibility/bridge/lib/accessibility/bridge/macruby.rb +185 -0
- data/ext/accessibility/bridge/lib/accessibility/bridge/mri.rb +121 -0
- data/ext/accessibility/bridge/lib/accessibility/bridge/version.rb +6 -0
- data/ext/accessibility/bridge/test/array_test.rb +31 -0
- data/ext/accessibility/bridge/test/boxed_test.rb +23 -0
- data/ext/accessibility/bridge/test/cfrange_test.rb +21 -0
- data/ext/accessibility/bridge/test/cgpoint_test.rb +54 -0
- data/ext/accessibility/bridge/test/cgrect_test.rb +60 -0
- data/ext/accessibility/bridge/test/cgsize_test.rb +54 -0
- data/ext/accessibility/bridge/test/helper.rb +19 -0
- data/ext/accessibility/bridge/test/nsstring_test.rb +22 -0
- data/ext/accessibility/bridge/test/nsurl_test.rb +17 -0
- data/ext/accessibility/bridge/test/object_test.rb +19 -0
- data/ext/accessibility/bridge/test/range_test.rb +16 -0
- data/ext/accessibility/bridge/test/string_test.rb +35 -0
- data/ext/accessibility/bridge/test/uri_test.rb +15 -0
- data/ext/accessibility/core/bridge.h +1 -0
- data/ext/accessibility/core/core.c +705 -0
- data/ext/accessibility/core/extconf.rb +22 -0
- data/ext/accessibility/highlighter/extconf.rb +22 -0
- data/ext/accessibility/highlighter/highlighter.c +7 -0
- data/lib/accessibility/core.rb +12 -0
- data/lib/accessibility/core/core_ext/common.rb +57 -0
- data/lib/accessibility/core/core_ext/macruby.rb +140 -0
- data/lib/accessibility/core/core_ext/mri.rb +121 -0
- data/lib/accessibility/core/macruby.rb +858 -0
- data/lib/accessibility/core/version.rb +6 -0
- data/test/helper.rb +48 -0
- metadata +158 -0
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'mkmf'
|
2
|
+
|
3
|
+
$CFLAGS << ' -std=c99 -Wall -Werror -pedantic -ObjC'
|
4
|
+
$LIBS << ' -framework CoreFoundation -framework ApplicationServices -framework Cocoa'
|
5
|
+
$LIBS << ' -framework CoreGraphics' unless `sw_vers -productVersion`.to_f == 10.7
|
6
|
+
|
7
|
+
if RUBY_ENGINE == 'macruby'
|
8
|
+
$CFLAGS << ' -fobjc-gc'
|
9
|
+
else
|
10
|
+
unless RbConfig::CONFIG["CC"].match /clang/
|
11
|
+
clang = `which clang`.chomp
|
12
|
+
if clang.empty?
|
13
|
+
raise "Clang not installed. Cannot build C extension"
|
14
|
+
else
|
15
|
+
RbConfig::MAKEFILE_CONFIG["CC"] = clang
|
16
|
+
RbConfig::MAKEFILE_CONFIG["CXX"] = clang
|
17
|
+
end
|
18
|
+
end
|
19
|
+
$CFLAGS << ' -DNOT_MACRUBY'
|
20
|
+
end
|
21
|
+
|
22
|
+
create_makefile 'accessibility/core'
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'mkmf'
|
2
|
+
|
3
|
+
$CFLAGS << ' -std=c99 -Wall -Werror -pedantic -ObjC'
|
4
|
+
$LIBS << ' -framework CoreFoundation -framework Cocoa'
|
5
|
+
$LIBS << ' -framework CoreGraphics' unless `sw_vers -productVersion`.to_f == 10.7
|
6
|
+
|
7
|
+
if RUBY_ENGINE == 'macruby'
|
8
|
+
$CFLAGS << ' -fobjc-gc'
|
9
|
+
else
|
10
|
+
unless RbConfig::CONFIG["CC"].match /clang/
|
11
|
+
clang = `which clang`.chomp
|
12
|
+
if clang.empty?
|
13
|
+
raise "Clang not installed. Cannot build C extension"
|
14
|
+
else
|
15
|
+
RbConfig::MAKEFILE_CONFIG["CC"] = clang
|
16
|
+
RbConfig::MAKEFILE_CONFIG["CXX"] = clang
|
17
|
+
end
|
18
|
+
end
|
19
|
+
$CFLAGS << ' -DNOT_MACRUBY'
|
20
|
+
end
|
21
|
+
|
22
|
+
create_makefile 'accessibility/highlighter'
|
@@ -0,0 +1,57 @@
|
|
1
|
+
class CGPoint
|
2
|
+
##
|
3
|
+
# Returns the receiver, since the receiver is already a {CGPoint}
|
4
|
+
#
|
5
|
+
# @return [CGPoint]
|
6
|
+
def to_point
|
7
|
+
self
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class CGSize
|
12
|
+
##
|
13
|
+
# Returns the receiver, since the receiver is already a {CGSize}
|
14
|
+
#
|
15
|
+
# @return [CGSize]
|
16
|
+
def to_size
|
17
|
+
self
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class CGRect
|
22
|
+
##
|
23
|
+
# Returns the receiver, since the receiver is already a {CGRect}
|
24
|
+
#
|
25
|
+
# @return [CGRect]
|
26
|
+
def to_rect
|
27
|
+
self
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
##
|
32
|
+
# accessibility-core extensions to `Array`
|
33
|
+
class Array
|
34
|
+
##
|
35
|
+
# Coerce the first two elements of the receiver into a {CGPoint}
|
36
|
+
#
|
37
|
+
# @return [CGPoint]
|
38
|
+
def to_point
|
39
|
+
CGPoint.new self[0], self[1]
|
40
|
+
end
|
41
|
+
|
42
|
+
##
|
43
|
+
# Coerce the first two elements of the receiver into a {CGSize}
|
44
|
+
#
|
45
|
+
# @return [CGSize]
|
46
|
+
def to_size
|
47
|
+
CGSize.new self[0], self[1]
|
48
|
+
end
|
49
|
+
|
50
|
+
##
|
51
|
+
# Coerce the first four elements of the receiver into a {CGRect}
|
52
|
+
#
|
53
|
+
# @return [CGRect]
|
54
|
+
def to_rect
|
55
|
+
CGRect.new CGPoint.new(self[0], self[1]), CGSize.new(self[2], self[3])
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
require 'accessibility/core/core_ext/common'
|
2
|
+
|
3
|
+
##
|
4
|
+
# accessibility-core extensions for `NSURL`
|
5
|
+
class NSURL
|
6
|
+
##
|
7
|
+
# Return the reciver, for the receiver is already a URL object
|
8
|
+
#
|
9
|
+
# @return [NSURL]
|
10
|
+
def to_url
|
11
|
+
self
|
12
|
+
end
|
13
|
+
|
14
|
+
# because printing is easier this way
|
15
|
+
alias_method :to_s, :inspect
|
16
|
+
end
|
17
|
+
|
18
|
+
##
|
19
|
+
# accessibility-core extensions for `NSString`
|
20
|
+
class NSString
|
21
|
+
##
|
22
|
+
# Create an NSURL using the receiver as the initialization string
|
23
|
+
#
|
24
|
+
# If the receiver is not a valid URL then `nil` will be returned.
|
25
|
+
#
|
26
|
+
# This exists because of
|
27
|
+
# [rdar://11207662](http://openradar.appspot.com/11207662).
|
28
|
+
#
|
29
|
+
# @return [NSURL,nil]
|
30
|
+
def to_url
|
31
|
+
NSURL.URLWithString self
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
##
|
36
|
+
# `accessibility-core` extensions for `NSObject`
|
37
|
+
class NSObject
|
38
|
+
##
|
39
|
+
# Return an object safe for passing to AXAPI
|
40
|
+
def to_ax
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
##
|
45
|
+
# Return a usable object from an AXAPI pointer
|
46
|
+
def to_ruby
|
47
|
+
self
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
##
|
52
|
+
# `accessibility-core` extensions for `CFRange`
|
53
|
+
class CFRange
|
54
|
+
##
|
55
|
+
# Convert the {CFRange} to a Ruby {Range} object
|
56
|
+
#
|
57
|
+
# @return [Range]
|
58
|
+
def to_ruby
|
59
|
+
Range.new location, (location + length - 1)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
##
|
64
|
+
# `accessibility-core` extensions for `Range`
|
65
|
+
class Range
|
66
|
+
# @return [AXValueRef]
|
67
|
+
def to_ax
|
68
|
+
raise ArgumentError, "can't convert negative index" if last < 0 || first < 0
|
69
|
+
length = if exclude_end?
|
70
|
+
last - first
|
71
|
+
else
|
72
|
+
last - first + 1
|
73
|
+
end
|
74
|
+
CFRange.new(first, length).to_ax
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
##
|
79
|
+
# AXElements extensions to the `Boxed` class
|
80
|
+
#
|
81
|
+
# The `Boxed` class is simply an abstract base class for structs that
|
82
|
+
# MacRuby can use via bridge support.
|
83
|
+
class Boxed
|
84
|
+
##
|
85
|
+
# Returns the number that AXAPI uses in order to know how to wrap
|
86
|
+
# a struct.
|
87
|
+
#
|
88
|
+
# @return [Number]
|
89
|
+
def self.ax_value
|
90
|
+
raise NotImplementedError, "#{inspect}:#{self.class} cannot be wrapped"
|
91
|
+
end
|
92
|
+
|
93
|
+
##
|
94
|
+
# Create an `AXValueRef` from the `Boxed` instance. This will only
|
95
|
+
# work if for the most common boxed types, you will need to check
|
96
|
+
# the AXAPI documentation for an up to date list.
|
97
|
+
#
|
98
|
+
# @example
|
99
|
+
#
|
100
|
+
# CGPoint.new(12, 34).to_ax # => #<AXValueRef:0x455678e2>
|
101
|
+
# CGSize.new(56, 78).to_ax # => #<AXValueRef:0x555678e2>
|
102
|
+
#
|
103
|
+
# @return [AXValueRef]
|
104
|
+
def to_ax
|
105
|
+
klass = self.class
|
106
|
+
ptr = Pointer.new klass.type
|
107
|
+
ptr.assign self
|
108
|
+
AXValueCreate(klass.ax_value, ptr)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# `accessibility-core` extensions for `CFRange`'s metaclass
|
113
|
+
class << CFRange
|
114
|
+
# (see Boxed.ax_value)
|
115
|
+
def ax_value; KAXValueCFRangeType end
|
116
|
+
end
|
117
|
+
# `accessibility-core` extensions for `CGSize`'s metaclass
|
118
|
+
class << CGSize
|
119
|
+
# (see Boxed.ax_value)
|
120
|
+
def ax_value; KAXValueCGSizeType end
|
121
|
+
end
|
122
|
+
# `accessibility-core` extensions for `CGRect`'s metaclass
|
123
|
+
class << CGRect
|
124
|
+
# (see Boxed.ax_value)
|
125
|
+
def ax_value; KAXValueCGRectType end
|
126
|
+
end
|
127
|
+
# `accessibility-core` extensions for `CGPoint`'s metaclass
|
128
|
+
class << CGPoint
|
129
|
+
# (see Boxed.ax_value)
|
130
|
+
def ax_value; KAXValueCGPointType end
|
131
|
+
end
|
132
|
+
|
133
|
+
##
|
134
|
+
# `accessibility-core` extensions to `NSArray`
|
135
|
+
class NSArray
|
136
|
+
# @return [Array]
|
137
|
+
def to_ruby
|
138
|
+
map do |obj| obj.to_ruby end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
##
|
2
|
+
# A structure that contains a point in a two-dimensional coordinate system
|
3
|
+
CGPoint = Struct.new(:x, :y) do
|
4
|
+
|
5
|
+
# @param x [Number]
|
6
|
+
# @param y [Number]
|
7
|
+
def initialize x = 0.0, y = 0.0
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
# @!attribute [rw] x
|
12
|
+
# The `x` co-ordinate of the screen point
|
13
|
+
# @return [Number]
|
14
|
+
|
15
|
+
# @!attribute [rw] y
|
16
|
+
# The `y` co-ordinate of the screen point
|
17
|
+
# @return [Number]
|
18
|
+
|
19
|
+
##
|
20
|
+
# Return a nice string representation of the point
|
21
|
+
#
|
22
|
+
# Overrides `Object#inspect` to more closely mimic MacRuby `Boxed#inspect`.
|
23
|
+
#
|
24
|
+
# @return [String]
|
25
|
+
def inspect
|
26
|
+
"#<CGPoint x=#{self.x.to_f} y=#{self.y.to_f}>"
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
##
|
33
|
+
# A structure that contains the size of a rectangle in a 2D co-ordinate system
|
34
|
+
CGSize = Struct.new(:width, :height) do
|
35
|
+
|
36
|
+
# @param width [Number]
|
37
|
+
# @param height [Number]
|
38
|
+
def initialize width = 0.0, height = 0.0
|
39
|
+
super
|
40
|
+
end
|
41
|
+
|
42
|
+
# @!attribute [rw] width
|
43
|
+
# The `width` of the box
|
44
|
+
# @return [Number]
|
45
|
+
|
46
|
+
# @!attribute [rw] height
|
47
|
+
# The `heighth` of the box
|
48
|
+
# @return [Number]
|
49
|
+
|
50
|
+
##
|
51
|
+
# Return a nice string representation of the size
|
52
|
+
#
|
53
|
+
# Overrides `Object#inspect` to more closely mimic MacRuby `Boxed#inspect`.
|
54
|
+
#
|
55
|
+
# @return [String]
|
56
|
+
def inspect
|
57
|
+
"#<CGSize width=#{self.width.to_f} height=#{self.height.to_f}>"
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
##
|
64
|
+
# Complete definition of a rectangle in a 2D coordinate system
|
65
|
+
CGRect = Struct.new(:origin, :size) do
|
66
|
+
|
67
|
+
# @param origin [CGPoint,#to_point]
|
68
|
+
# @param size [CGSize,#to_size]
|
69
|
+
def initialize origin = CGPoint.new, size = CGSize.new
|
70
|
+
super(origin.to_point, size.to_size)
|
71
|
+
end
|
72
|
+
|
73
|
+
# @!attribute [rw] origin
|
74
|
+
# The `origin` point
|
75
|
+
# @return [CGPoint,#to_point]
|
76
|
+
|
77
|
+
# @!attribute [rw] size
|
78
|
+
# The `size` of the rectangle
|
79
|
+
# @return [CGSize,#to_size]
|
80
|
+
|
81
|
+
##
|
82
|
+
# Return a nice string representation of the rectangle
|
83
|
+
#
|
84
|
+
# Overrides `Object#inspect` to more closely mimic MacRuby `Boxed#inspect`.
|
85
|
+
#
|
86
|
+
# @return [String]
|
87
|
+
def inspect
|
88
|
+
"#<CGRect origin=#{self.origin.inspect} size=#{self.size.inspect}>"
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
require 'uri'
|
95
|
+
|
96
|
+
##
|
97
|
+
# `accessibility-core` extensions to the `URI` family of classes
|
98
|
+
class URI::Generic
|
99
|
+
##
|
100
|
+
# Returns the receiver (since the receiver is already a `URI` object)
|
101
|
+
#
|
102
|
+
# @return [URI::Generic]
|
103
|
+
def to_url
|
104
|
+
self
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
##
|
109
|
+
# `accessibility-core` extensions to the `String` class
|
110
|
+
class String
|
111
|
+
##
|
112
|
+
# Parse the receiver into a `URI` object
|
113
|
+
#
|
114
|
+
# @return [URI::Generic]
|
115
|
+
def to_url
|
116
|
+
URI.parse self
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
require 'accessibility/core/core_ext/common'
|
@@ -0,0 +1,858 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
framework 'Cocoa'
|
4
|
+
|
5
|
+
# A workaround that guarantees that `CGPoint` is defined
|
6
|
+
MOUNTAIN_LION_APPKIT_VERSION ||= 1187
|
7
|
+
if NSAppKitVersionNumber >= MOUNTAIN_LION_APPKIT_VERSION
|
8
|
+
framework '/System/Library/Frameworks/CoreGraphics.framework'
|
9
|
+
end
|
10
|
+
|
11
|
+
# check that the Accessibility APIs are enabled and are available to MacRuby
|
12
|
+
begin
|
13
|
+
unless AXAPIEnabled()
|
14
|
+
raise RuntimeError, <<-EOS
|
15
|
+
------------------------------------------------------------------------
|
16
|
+
Universal Access is disabled on this machine.
|
17
|
+
|
18
|
+
Please enable it in the System Preferences.
|
19
|
+
------------------------------------------------------------------------
|
20
|
+
EOS
|
21
|
+
end
|
22
|
+
rescue NoMethodError
|
23
|
+
raise NotImplementedError, <<-EOS
|
24
|
+
------------------------------------------------------------------------
|
25
|
+
You need to install the latest BridgeSupport preview so that AXElements
|
26
|
+
has access to CoreFoundation.
|
27
|
+
------------------------------------------------------------------------
|
28
|
+
EOS
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
require 'accessibility/core/core_ext/macruby'
|
33
|
+
|
34
|
+
|
35
|
+
##
|
36
|
+
# Core abstraction layer that that adds OO to the OS X Accessibility APIs
|
37
|
+
#
|
38
|
+
# This provides a generic object oriented mixin/class for the low level APIs.
|
39
|
+
# A more Ruby-ish wrapper is available through
|
40
|
+
# [AXElements](https://github.com/Marketcircle/AXElements).
|
41
|
+
#
|
42
|
+
# On MacRuby, bridge support turns C structs into "first class" objects. To
|
43
|
+
# that end, instead of adding an extra allocation to wrap the object, we will
|
44
|
+
# simply add a mixin to add some basic functionality. On MRI, a C extension
|
45
|
+
# encapsulates the structures into a class.
|
46
|
+
#
|
47
|
+
# For both Ruby platforms the interface should be the same. It is a bug if they
|
48
|
+
# are different.
|
49
|
+
#
|
50
|
+
# This module is responsible for handling pointers and dealing with error
|
51
|
+
# codes for functions that make use of them. The methods in this class
|
52
|
+
# provide a cleaner, more Ruby-ish interface to the low level CoreFoundation
|
53
|
+
# functions that compose AXAPI than are natively available.
|
54
|
+
#
|
55
|
+
# @example
|
56
|
+
#
|
57
|
+
# element = Accessibility::Element.application_for pid_for_terminal
|
58
|
+
# element.attributes # => ["AXRole", "AXChildren", ...]
|
59
|
+
# element.size_of "AXChildren" # => 2
|
60
|
+
# element.children.first.role # => "AXWindow"
|
61
|
+
#
|
62
|
+
module Accessibility::Element
|
63
|
+
|
64
|
+
class << self
|
65
|
+
|
66
|
+
# @!group Element Hierarchy Entry Points
|
67
|
+
|
68
|
+
##
|
69
|
+
# Get the application object object for an application given the
|
70
|
+
# process identifier (PID) for that application.
|
71
|
+
#
|
72
|
+
# @example
|
73
|
+
#
|
74
|
+
# app = Element.application_for 54743 # => #<Accessibility::Element>
|
75
|
+
#
|
76
|
+
# @param pid [Number]
|
77
|
+
# @return [Accessibility::Element]
|
78
|
+
def application_for pid
|
79
|
+
NSRunLoop.currentRunLoop.runUntilDate Time.now
|
80
|
+
if NSRunningApplication.runningApplicationWithProcessIdentifier pid
|
81
|
+
AXUIElementCreateApplication(pid)
|
82
|
+
else
|
83
|
+
raise ArgumentError, 'pid must belong to a running application'
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
##
|
88
|
+
# Create a new reference to the system wide object
|
89
|
+
#
|
90
|
+
# This is very useful when working with the system wide object as
|
91
|
+
# caching the system wide reference does not seem to work.
|
92
|
+
#
|
93
|
+
# @example
|
94
|
+
#
|
95
|
+
# system_wide # => #<Accessibility::Element>
|
96
|
+
#
|
97
|
+
# @return [Accessibility::Element]
|
98
|
+
def system_wide
|
99
|
+
AXUIElementCreateSystemWide()
|
100
|
+
end
|
101
|
+
|
102
|
+
##
|
103
|
+
# Find the top most element at the given point on the screen
|
104
|
+
#
|
105
|
+
# This is the same as {Accessibility::Element#element_at} except
|
106
|
+
# that the check for the topmost element does not care which app
|
107
|
+
# the element belongs to.
|
108
|
+
#
|
109
|
+
# The coordinates should be specified using the flipped coordinate
|
110
|
+
# system (origin is in the top-left, increasing downward and to the right
|
111
|
+
# as if reading a book in English).
|
112
|
+
#
|
113
|
+
# If more than one element is at the position then the z-order of the
|
114
|
+
# elements will be used to determine which is "on top".
|
115
|
+
#
|
116
|
+
# This method will safely return `nil` if there is no UI element at the
|
117
|
+
# give point.
|
118
|
+
#
|
119
|
+
# @example
|
120
|
+
#
|
121
|
+
# Element.element_at [453, 200] # table
|
122
|
+
# Element.element_at CGPoint.new(453, 200) # table
|
123
|
+
#
|
124
|
+
# @param point [#to_point]
|
125
|
+
# @return [Accessibility::Element,nil]
|
126
|
+
def element_at point
|
127
|
+
system_wide.element_at point
|
128
|
+
end
|
129
|
+
|
130
|
+
# @!endgroup
|
131
|
+
|
132
|
+
|
133
|
+
##
|
134
|
+
# The delay between keyboard events used by {Accessibility::Element#post}
|
135
|
+
#
|
136
|
+
# The default value is `0.009` (`:normal`), which should be about 50
|
137
|
+
# characters per second (down and up are separate events).
|
138
|
+
#
|
139
|
+
# This is just a magic number from trial and error. Both the repeat
|
140
|
+
# interval (NXKeyRepeatInterval) and threshold (NXKeyRepeatThreshold)
|
141
|
+
# were tried, but were way too big.
|
142
|
+
#
|
143
|
+
# @return [Number]
|
144
|
+
attr_reader :key_rate
|
145
|
+
|
146
|
+
##
|
147
|
+
# Set the delay between key events
|
148
|
+
#
|
149
|
+
# This value is used by {Accessibility::Element#post} to slow down the
|
150
|
+
# typing speed so apps do not get overloaded by all the events arriving
|
151
|
+
# at the same time.
|
152
|
+
#
|
153
|
+
# You can pass either an exact value for sleeping (a `Float` or
|
154
|
+
# `Fixnum`), or you can use a preset symbol:
|
155
|
+
#
|
156
|
+
# - `:very_slow`
|
157
|
+
# - `:slow`
|
158
|
+
# - `:normal`/`:default`
|
159
|
+
# - `:fast`
|
160
|
+
# - `:zomg`
|
161
|
+
#
|
162
|
+
# The `:zomg` setting will be too fast in almost all cases, but
|
163
|
+
# it is fun to watch.
|
164
|
+
#
|
165
|
+
# @param [Number,Symbol]
|
166
|
+
def self.key_rate= value
|
167
|
+
@key_rate = case value
|
168
|
+
when :very_slow then 0.9
|
169
|
+
when :slow then 0.09
|
170
|
+
when :normal, :default then 0.009
|
171
|
+
when :fast then 0.0009
|
172
|
+
when :zomg then 0.00009
|
173
|
+
else value.to_f
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
@key_rate = 0.009
|
178
|
+
|
179
|
+
|
180
|
+
# @!group Attributes
|
181
|
+
|
182
|
+
##
|
183
|
+
# @todo Invalid elements do not always raise an error. This is a bug
|
184
|
+
# that should be logged with Apple (but I keep procrastinating).
|
185
|
+
#
|
186
|
+
# Get the list of attributes for the element
|
187
|
+
#
|
188
|
+
# As a convention, this method will return an empty array if the
|
189
|
+
# backing element is no longer alive.
|
190
|
+
#
|
191
|
+
# @example
|
192
|
+
#
|
193
|
+
# button.attributes # => ["AXRole", "AXRoleDescription", ...]
|
194
|
+
#
|
195
|
+
# @return [Array<String>]
|
196
|
+
def attributes
|
197
|
+
@attributes ||= (
|
198
|
+
ptr = Pointer.new ARRAY
|
199
|
+
code = AXUIElementCopyAttributeNames(self, ptr)
|
200
|
+
|
201
|
+
case code
|
202
|
+
when 0 then ptr.value
|
203
|
+
when KAXErrorInvalidUIElement then []
|
204
|
+
else handle_error code
|
205
|
+
end
|
206
|
+
)
|
207
|
+
end
|
208
|
+
|
209
|
+
##
|
210
|
+
# Fetch the value for the given attribute from the receiver
|
211
|
+
#
|
212
|
+
# CoreFoundation wrapped objects will be unwrapped for you, if you expect
|
213
|
+
# to get a {CFRange} you will be given a {Range} instead.
|
214
|
+
#
|
215
|
+
# As a convention, if the backing element is no longer alive then
|
216
|
+
# any attribute value will return `nil`. If the attribute is not supported
|
217
|
+
# by the element then `nil` will be returned instead. These
|
218
|
+
# conventions are debatably necessary, inquire for details.
|
219
|
+
#
|
220
|
+
# @example
|
221
|
+
# window.attribute 'AXTitle' # => "HotCocoa Demo"
|
222
|
+
# window.attribute 'AXSize' # => #<CGSize width=10.0 height=88>
|
223
|
+
# window.attribute 'AXParent' # => #<Accessibility::Element>
|
224
|
+
# window.attribute 'AXHerpDerp' # => nil
|
225
|
+
#
|
226
|
+
# @param name [String]
|
227
|
+
def attribute name
|
228
|
+
ptr = Pointer.new :id
|
229
|
+
code = AXUIElementCopyAttributeValue(self, name, ptr)
|
230
|
+
|
231
|
+
case code
|
232
|
+
when 0
|
233
|
+
ptr.value.to_ruby
|
234
|
+
when KAXErrorFailure, KAXErrorNoValue,
|
235
|
+
KAXErrorInvalidUIElement, KAXErrorAttributeUnsupported
|
236
|
+
nil
|
237
|
+
else
|
238
|
+
handle_error code, name
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
##
|
243
|
+
# @note It has been observed that some elements may lie with this value.
|
244
|
+
# Bugs should be reported to the app developers in those cases.
|
245
|
+
# I'm looking at you, Safari!
|
246
|
+
#
|
247
|
+
# Get the size of the array that would be returned by calling {#attribute}
|
248
|
+
#
|
249
|
+
# When performance matters, this is much faster than getting the array
|
250
|
+
# and asking for the size.
|
251
|
+
#
|
252
|
+
# If there is a failure or the backing element is no longer alive, this
|
253
|
+
# method will return `0`.
|
254
|
+
#
|
255
|
+
# @example
|
256
|
+
#
|
257
|
+
# window.size_of 'AXChildren' # => 19
|
258
|
+
# table.size_of 'AXRows' # => 100
|
259
|
+
#
|
260
|
+
# @param name [String]
|
261
|
+
# @return [Number]
|
262
|
+
def size_of name
|
263
|
+
ptr = Pointer.new :long_long
|
264
|
+
code = AXUIElementGetAttributeValueCount(self, name, ptr)
|
265
|
+
|
266
|
+
case code
|
267
|
+
when 0
|
268
|
+
ptr.value
|
269
|
+
when KAXErrorFailure, KAXErrorNoValue, KAXErrorInvalidUIElement
|
270
|
+
0
|
271
|
+
else
|
272
|
+
handle_error code, name
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
##
|
277
|
+
# Returns whether or not the given attribute is writable on the reciver
|
278
|
+
#
|
279
|
+
# Often, you will want/need to check writability of an attribute before
|
280
|
+
# trying to call {Accessibility::Element#set} for the attribute.
|
281
|
+
#
|
282
|
+
# In case of internal error, or if the element dies, this method will
|
283
|
+
# return `false`.
|
284
|
+
#
|
285
|
+
# @example
|
286
|
+
#
|
287
|
+
# window.writable? 'AXSize' # => true
|
288
|
+
# window.writable? 'AXTitle' # => false
|
289
|
+
#
|
290
|
+
# @param name [String]
|
291
|
+
def writable? name
|
292
|
+
ptr = Pointer.new :bool
|
293
|
+
code = AXUIElementIsAttributeSettable(self, name, ptr)
|
294
|
+
|
295
|
+
case code
|
296
|
+
when 0
|
297
|
+
ptr.value
|
298
|
+
when KAXErrorFailure, KAXErrorNoValue, KAXErrorInvalidUIElement
|
299
|
+
false
|
300
|
+
else
|
301
|
+
handle_error code, name
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
##
|
306
|
+
# Set the given value for the given attribute on the receiver
|
307
|
+
#
|
308
|
+
# You do not need to worry about wrapping objects first, `Range`
|
309
|
+
# objects will also be automatically converted into `CFRange` objects
|
310
|
+
# (unless they have a negative index) and then wrapped.
|
311
|
+
#
|
312
|
+
# This method does not check writability of the attribute you are
|
313
|
+
# setting. If you need to check, use {Accessibility::Element#writable?}
|
314
|
+
# first to check.
|
315
|
+
#
|
316
|
+
# Unlike when reading attributes, writing to a dead element, and
|
317
|
+
# other error conditions, will raise an exception.
|
318
|
+
#
|
319
|
+
# @example
|
320
|
+
#
|
321
|
+
# set 'AXValue', "hi" # => "hi"
|
322
|
+
# set 'AXSize', [250,250] # => [250,250]
|
323
|
+
# set 'AXVisibleRange', 0..3 # => 0..3
|
324
|
+
# set 'AXVisibleRange', 1...4 # => 1..3
|
325
|
+
#
|
326
|
+
# @param name [String]
|
327
|
+
# @param value [Object]
|
328
|
+
def set name, value
|
329
|
+
code = AXUIElementSetAttributeValue(self, name, value.to_ax)
|
330
|
+
if code.zero?
|
331
|
+
value
|
332
|
+
else
|
333
|
+
handle_error code, name, value
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
##
|
338
|
+
# Shortcut for getting the `"AXRole"` attribute
|
339
|
+
#
|
340
|
+
# The role of an element roughly translates to the class of the object;
|
341
|
+
# however this can be thought of as the superclass of the object if the
|
342
|
+
# object also has a `"AXSubrole"` attribute.
|
343
|
+
#
|
344
|
+
# Remember that dead elements may return `nil` for their role.
|
345
|
+
#
|
346
|
+
# @example
|
347
|
+
#
|
348
|
+
# window.role # => "AXWindow"
|
349
|
+
#
|
350
|
+
# @return [String,nil]
|
351
|
+
def role
|
352
|
+
attribute KAXRoleAttribute
|
353
|
+
end
|
354
|
+
|
355
|
+
##
|
356
|
+
# @note You might get `nil` back as the subrole even if the object claims
|
357
|
+
# to have a subrole attribute. AXWebArea objects are known to do this.
|
358
|
+
# You need to check. :(
|
359
|
+
#
|
360
|
+
# Shortcut for getting the `"AXSubrole"`
|
361
|
+
#
|
362
|
+
# The subrole of an element roughly translates to the class of the object,
|
363
|
+
# but only if the object has a subrole. If an object does not have a subrole
|
364
|
+
# then the class of the object would be the {#role}.
|
365
|
+
#
|
366
|
+
# @example
|
367
|
+
# window.subrole # => "AXDialog"
|
368
|
+
# web_area.subrole # => nil
|
369
|
+
#
|
370
|
+
# @return [String,nil]
|
371
|
+
def subrole
|
372
|
+
attribute KAXSubroleAttribute
|
373
|
+
end
|
374
|
+
|
375
|
+
##
|
376
|
+
# Shortcut for getting the `"AXParent"`
|
377
|
+
#
|
378
|
+
# The "parent" attribute of an element is the general way in which you would
|
379
|
+
# navigate upwards through the hierarchy of the views in an app.
|
380
|
+
#
|
381
|
+
# An element will be returned if the receiver has a parent, otherwise `nil`
|
382
|
+
# will be returned. Incorrectly implemented elements may also return `nil`.
|
383
|
+
# Usually only something that has a {#role} of `"AXApplication"` will return
|
384
|
+
# `nil` since it does not have a parent.
|
385
|
+
#
|
386
|
+
# @example
|
387
|
+
#
|
388
|
+
# window.parent # => app
|
389
|
+
# app.parent # => nil
|
390
|
+
#
|
391
|
+
# @return [Accessibility::Element,nil]
|
392
|
+
def parent
|
393
|
+
ptr = Pointer.new :id
|
394
|
+
code = AXUIElementCopyAttributeValue(self, KAXParentAttribute, ptr)
|
395
|
+
code.zero? ? ptr.value.to_ruby : nil
|
396
|
+
end
|
397
|
+
|
398
|
+
##
|
399
|
+
# Shortcut for getting the `"AXChildren"`
|
400
|
+
#
|
401
|
+
# The "children" attribute of an element is the general way in which you would
|
402
|
+
# navigate downwards through the hierarchy of the views in an app.
|
403
|
+
#
|
404
|
+
# An array will always be returned, even if the element is dead or has no
|
405
|
+
# children (but the array will be empty in those cases).
|
406
|
+
#
|
407
|
+
# @example
|
408
|
+
#
|
409
|
+
# app.children # => [MenuBar, Window, ...]
|
410
|
+
#
|
411
|
+
# @return [Array<Accessibility::Element>]
|
412
|
+
def children
|
413
|
+
ptr = Pointer.new :id
|
414
|
+
code = AXUIElementCopyAttributeValue(self, KAXChildrenAttribute, ptr)
|
415
|
+
code.zero? ? ptr.value.to_ruby : []
|
416
|
+
end
|
417
|
+
|
418
|
+
##
|
419
|
+
# Shortcut for getting the `"AXValue"`
|
420
|
+
#
|
421
|
+
# @example
|
422
|
+
#
|
423
|
+
# label.value # => "Mark Rada"
|
424
|
+
# slider.value # => 42
|
425
|
+
#
|
426
|
+
def value
|
427
|
+
attribute KAXValueAttribute
|
428
|
+
end
|
429
|
+
|
430
|
+
##
|
431
|
+
# Get the process identifier (PID) of the application of the receiver
|
432
|
+
#
|
433
|
+
# This method will return `0` if the element is dead or if the receiver
|
434
|
+
# is the the system wide element.
|
435
|
+
#
|
436
|
+
# @example
|
437
|
+
#
|
438
|
+
# window.pid # => 12345
|
439
|
+
# Element.system_wide.pid # => 0
|
440
|
+
#
|
441
|
+
# @return [Fixnum]
|
442
|
+
def pid
|
443
|
+
@pid ||= (
|
444
|
+
ptr = Pointer.new :int
|
445
|
+
code = AXUIElementGetPid(self, ptr)
|
446
|
+
|
447
|
+
case code
|
448
|
+
when 0
|
449
|
+
ptr.value
|
450
|
+
when KAXErrorInvalidUIElement
|
451
|
+
self == Accessibility::Element.system_wide ? 0 : handle_error(code)
|
452
|
+
else
|
453
|
+
handle_error code
|
454
|
+
end
|
455
|
+
)
|
456
|
+
end
|
457
|
+
|
458
|
+
|
459
|
+
# @!group Parameterized Attributes
|
460
|
+
|
461
|
+
##
|
462
|
+
# Get the list of parameterized attributes for the element
|
463
|
+
#
|
464
|
+
# Similar to {#attributes}, this method will also return an empty
|
465
|
+
# array if the element is dead.
|
466
|
+
#
|
467
|
+
# Most elements do not have parameterized attributes, but the ones
|
468
|
+
# that do, have many.
|
469
|
+
#
|
470
|
+
# @example
|
471
|
+
#
|
472
|
+
# text_area.parameterized_attributes # => ["AXStringForRange", ...]
|
473
|
+
# app.parameterized_attributes # => []
|
474
|
+
#
|
475
|
+
# @return [Array<String>]
|
476
|
+
def parameterized_attributes
|
477
|
+
@parameterized_attributes ||= (
|
478
|
+
ptr = Pointer.new ARRAY
|
479
|
+
code = AXUIElementCopyParameterizedAttributeNames(self, ptr)
|
480
|
+
|
481
|
+
case code
|
482
|
+
when 0 then ptr.value
|
483
|
+
when KAXErrorNoValue, KAXErrorInvalidUIElement then []
|
484
|
+
else handle_error code
|
485
|
+
end
|
486
|
+
)
|
487
|
+
end
|
488
|
+
|
489
|
+
##
|
490
|
+
# Fetch the given pramaeterized attribute value for the given parameter
|
491
|
+
#
|
492
|
+
# Low level objects, such as {Accessibility::Element} and {Boxed} objects,
|
493
|
+
# will be unwrapped for you automatically and {CFRange} objects will be
|
494
|
+
# turned into {Range} objects. Similarly, you do not need to worry about
|
495
|
+
# wrapping the parameter as that will be done for you (except for {Range}
|
496
|
+
# objects that use a negative index).
|
497
|
+
#
|
498
|
+
# As a convention, if the backing element is no longer alive, or the
|
499
|
+
# attribute does not exist, or a system failure occurs then you will
|
500
|
+
# receive `nil`.
|
501
|
+
#
|
502
|
+
# @example
|
503
|
+
#
|
504
|
+
# parameterized_attribute KAXStringForRangeParameterizedAttribute, 1..10
|
505
|
+
# # => "ello, worl"
|
506
|
+
#
|
507
|
+
# @param name [String]
|
508
|
+
# @param param [Object]
|
509
|
+
def parameterized_attribute name, param
|
510
|
+
ptr = Pointer.new :id
|
511
|
+
code = AXUIElementCopyParameterizedAttributeValue(self, name, param.to_ax, ptr)
|
512
|
+
|
513
|
+
case code
|
514
|
+
when 0
|
515
|
+
ptr.value.to_ruby
|
516
|
+
when KAXErrorFailure, KAXErrorNoValue, KAXErrorInvalidUIElement
|
517
|
+
nil
|
518
|
+
else
|
519
|
+
handle_error code, name, param
|
520
|
+
end
|
521
|
+
end
|
522
|
+
|
523
|
+
|
524
|
+
# @!group Actions
|
525
|
+
|
526
|
+
##
|
527
|
+
# Get the list of actions that the element can perform
|
528
|
+
#
|
529
|
+
# If an element does not have actions, then an empty list will be
|
530
|
+
# returned. Dead elements will also return an empty array.
|
531
|
+
#
|
532
|
+
# @example
|
533
|
+
#
|
534
|
+
# button.actions # => ["AXPress"]
|
535
|
+
#
|
536
|
+
# @return [Array<String>]
|
537
|
+
def actions
|
538
|
+
@actions ||= (
|
539
|
+
ptr = Pointer.new ARRAY
|
540
|
+
code = AXUIElementCopyActionNames(self, ptr)
|
541
|
+
|
542
|
+
case code
|
543
|
+
when 0 then ptr.value
|
544
|
+
when KAXErrorInvalidUIElement then []
|
545
|
+
else handle_error code
|
546
|
+
end
|
547
|
+
)
|
548
|
+
end
|
549
|
+
|
550
|
+
##
|
551
|
+
# Ask the receiver to perform the given action
|
552
|
+
#
|
553
|
+
# This method will always return true or raises an exception. Actions
|
554
|
+
# should never fail, but there are some extreme edge cases (e.g. out
|
555
|
+
# of memory, etc.).
|
556
|
+
#
|
557
|
+
# Unlike when reading attributes, performing an action on a dead element
|
558
|
+
# will raise an exception.
|
559
|
+
#
|
560
|
+
# @example
|
561
|
+
#
|
562
|
+
# perform KAXPressAction # => true
|
563
|
+
#
|
564
|
+
# @param action [String]
|
565
|
+
# @return [Boolean]
|
566
|
+
def perform action
|
567
|
+
code = AXUIElementPerformAction(self, action)
|
568
|
+
if code.zero?
|
569
|
+
true
|
570
|
+
else
|
571
|
+
handle_error code, action
|
572
|
+
end
|
573
|
+
end
|
574
|
+
|
575
|
+
##
|
576
|
+
# Post the list of given keyboard events to the receiver
|
577
|
+
#
|
578
|
+
# This only applies if the given element is an application object or
|
579
|
+
# the system wide object. The focused element will receive the events.
|
580
|
+
#
|
581
|
+
# Events could be generated from a string using output from the
|
582
|
+
# `accessibility_keyboard` gem's `Accessibility::String#keyboard_events_for`
|
583
|
+
# method.
|
584
|
+
#
|
585
|
+
# Events are number/boolean tuples, where the number is a keycode
|
586
|
+
# and the boolean is the keypress state (true is keydown, false is
|
587
|
+
# keyup).
|
588
|
+
#
|
589
|
+
# You can learn more about keyboard events from the
|
590
|
+
# [Keyboard Events documentation](http://github.com/Marketcircle/AXElements/wiki/Keyboarding).
|
591
|
+
#
|
592
|
+
# @example
|
593
|
+
#
|
594
|
+
# include Accessibility::String
|
595
|
+
# events = keyboard_events_for "Hello, world!\n"
|
596
|
+
# app.post events
|
597
|
+
#
|
598
|
+
# @param events [Array<Array(Number,Boolean)>]
|
599
|
+
# @return [self]
|
600
|
+
def post events
|
601
|
+
events.each do |event|
|
602
|
+
code = AXUIElementPostKeyboardEvent(self, 0, *event)
|
603
|
+
handle_error code unless code.zero?
|
604
|
+
sleep Accessibility::Element.key_rate
|
605
|
+
end
|
606
|
+
sleep 0.1 # in many cases, UI is not done updating right away
|
607
|
+
self
|
608
|
+
end
|
609
|
+
|
610
|
+
|
611
|
+
# @!group Misc.
|
612
|
+
|
613
|
+
##
|
614
|
+
# Return whether or not the receiver is "dead"
|
615
|
+
#
|
616
|
+
# A dead element is one that is no longer in the app's view
|
617
|
+
# hierarchy. This is not the same as visibility; an element that is
|
618
|
+
# invalid will not be visible, but an invisible element might still
|
619
|
+
# be valid (it depends on the clients implementation of the API).
|
620
|
+
def invalid?
|
621
|
+
AXUIElementCopyAttributeValue(self, KAXRoleAttribute, Pointer.new(:id)) ==
|
622
|
+
KAXErrorInvalidUIElement
|
623
|
+
end
|
624
|
+
|
625
|
+
##
|
626
|
+
# Change the timeout value for the element
|
627
|
+
#
|
628
|
+
# The timeout value is mostly effective for apps that are slow to respond to
|
629
|
+
# accessibility queries, or if you intend to make a large query (such as thousands
|
630
|
+
# of rows in a table).
|
631
|
+
#
|
632
|
+
# If you change the timeout on the system wide object, it affets all timeouts.
|
633
|
+
#
|
634
|
+
# Setting the global timeout to `0` seconds will reset the timeout value
|
635
|
+
# to the system default. The system default timeout value is `6 seconds`
|
636
|
+
# as of the writing of this documentation, but Apple has not publicly
|
637
|
+
# documented this (we had to ask in person at WWDC).
|
638
|
+
#
|
639
|
+
# @param seconds [Number]
|
640
|
+
# @return [Number]
|
641
|
+
def set_timeout_to seconds
|
642
|
+
case code = AXUIElementSetMessagingTimeout(self, seconds)
|
643
|
+
when 0 then seconds
|
644
|
+
else handle_error code, seconds
|
645
|
+
end
|
646
|
+
end
|
647
|
+
|
648
|
+
##
|
649
|
+
# Returns the application reference (toplevel element) for the receiver
|
650
|
+
#
|
651
|
+
# @return [Accessibility::Element]
|
652
|
+
def application
|
653
|
+
Accessibility::Element.application_for pid
|
654
|
+
end
|
655
|
+
|
656
|
+
|
657
|
+
# @!group Element Hierarchy Entry Points
|
658
|
+
|
659
|
+
##
|
660
|
+
# Find the topmost element at the given point for the receiver's app
|
661
|
+
#
|
662
|
+
# If the receiver is the system wide object then the return is the
|
663
|
+
# topmost element regardless of application.
|
664
|
+
#
|
665
|
+
# The coordinates should be specified using the flipped coordinate
|
666
|
+
# system (origin is in the top-left, increasing downward and to the right
|
667
|
+
# as if reading a book in English).
|
668
|
+
#
|
669
|
+
# If more than one element is at the position then the z-order of the
|
670
|
+
# elements will be used to determine which is "on top".
|
671
|
+
#
|
672
|
+
# This method will safely return `nil` if there is no UI element at the
|
673
|
+
# give point.
|
674
|
+
#
|
675
|
+
# @example
|
676
|
+
#
|
677
|
+
# Element.system_wide.element_at [453, 200] # table
|
678
|
+
# app.element_at CGPoint.new(453, 200) # table
|
679
|
+
#
|
680
|
+
# @param point [#to_point]
|
681
|
+
# @return [Accessibility::Element,nil]
|
682
|
+
def element_at point
|
683
|
+
ptr = Pointer.new ELEMENT
|
684
|
+
code = AXUIElementCopyElementAtPosition(self, *point.to_point, ptr)
|
685
|
+
|
686
|
+
case code
|
687
|
+
when 0
|
688
|
+
ptr.value.to_ruby
|
689
|
+
when KAXErrorNoValue
|
690
|
+
nil
|
691
|
+
when KAXErrorInvalidUIElement # @todo uhh, why is this here again?
|
692
|
+
unless self == Accessibility::Element.system_wide
|
693
|
+
Accessibility::Element.element_at point
|
694
|
+
end
|
695
|
+
else
|
696
|
+
handle_error code, point, nil, nil
|
697
|
+
end
|
698
|
+
end
|
699
|
+
|
700
|
+
|
701
|
+
private
|
702
|
+
|
703
|
+
# @!group Error Handling
|
704
|
+
|
705
|
+
##
|
706
|
+
# @private
|
707
|
+
#
|
708
|
+
# Mapping of `AXError` values to static information on how to handle
|
709
|
+
# the error. Used by {handle_error}.
|
710
|
+
#
|
711
|
+
# @return [Hash{Number=>Array(Symbol,Range)}]
|
712
|
+
AXERROR = {
|
713
|
+
KAXErrorFailure => [
|
714
|
+
RuntimeError,
|
715
|
+
lambda { |*args|
|
716
|
+
"A system failure occurred with #{args[0].inspect}, stopping to be safe"
|
717
|
+
}
|
718
|
+
],
|
719
|
+
KAXErrorIllegalArgument => [
|
720
|
+
ArgumentError,
|
721
|
+
lambda { |*args|
|
722
|
+
case args.size
|
723
|
+
when 1
|
724
|
+
"#{args[0].inspect} is not an Accessibility::Element"
|
725
|
+
when 2
|
726
|
+
"Either the element #{args[0].inspect} or the attribute/action" +
|
727
|
+
"#{args[1].inspect} is not a legal argument"
|
728
|
+
when 3
|
729
|
+
"You can't get/set #{args[1].inspect} with/to #{args[2].inspect} " +
|
730
|
+
"for #{args[0].inspect}"
|
731
|
+
when 4
|
732
|
+
"The point #{args[1].to_point.inspect} is not a valid point, " +
|
733
|
+
"or #{args[0].inspect} is not an Accessibility::Element"
|
734
|
+
end
|
735
|
+
}
|
736
|
+
],
|
737
|
+
KAXErrorInvalidUIElement => [
|
738
|
+
ArgumentError,
|
739
|
+
lambda { |*args|
|
740
|
+
"#{args[0].inspect} is no longer a valid reference"
|
741
|
+
}
|
742
|
+
],
|
743
|
+
KAXErrorInvalidUIElementObserver => [
|
744
|
+
ArgumentError,
|
745
|
+
lambda { |*args|
|
746
|
+
'AXElements no longer supports notifications'
|
747
|
+
}
|
748
|
+
],
|
749
|
+
KAXErrorCannotComplete => [
|
750
|
+
RuntimeError,
|
751
|
+
lambda { |*args|
|
752
|
+
NSRunLoop.currentRunLoop.runUntilDate Time.now # spin the run loop once
|
753
|
+
pid = args[0].pid
|
754
|
+
app = NSRunningApplication.runningApplicationWithProcessIdentifier pid
|
755
|
+
if app
|
756
|
+
"An unspecified error occurred using #{args[0].inspect} with AXAPI, maybe a timeout :("
|
757
|
+
else
|
758
|
+
"Application for pid=#{pid} is no longer running. Maybe it crashed?"
|
759
|
+
end
|
760
|
+
}
|
761
|
+
],
|
762
|
+
KAXErrorAttributeUnsupported => [
|
763
|
+
ArgumentError,
|
764
|
+
lambda { |*args|
|
765
|
+
"#{args[0].inspect} does not have a #{args[1].inspect} attribute"
|
766
|
+
}
|
767
|
+
],
|
768
|
+
KAXErrorActionUnsupported => [
|
769
|
+
ArgumentError,
|
770
|
+
lambda { |*args|
|
771
|
+
"#{args[0].inspect} does not have a #{args[1].inspect} action"
|
772
|
+
}
|
773
|
+
],
|
774
|
+
KAXErrorNotificationUnsupported => [
|
775
|
+
ArgumentError,
|
776
|
+
lambda { |*args|
|
777
|
+
'AXElements no longer supports notifications'
|
778
|
+
}
|
779
|
+
],
|
780
|
+
KAXErrorNotImplemented => [
|
781
|
+
NotImplementedError,
|
782
|
+
lambda { |*args|
|
783
|
+
"The program that owns #{args[0].inspect} does not work with AXAPI properly"
|
784
|
+
}
|
785
|
+
],
|
786
|
+
KAXErrorNotificationAlreadyRegistered => [
|
787
|
+
ArgumentError,
|
788
|
+
lambda { |*args|
|
789
|
+
'AXElements no longer supports notifications'
|
790
|
+
}
|
791
|
+
],
|
792
|
+
KAXErrorNotificationNotRegistered => [
|
793
|
+
RuntimeError,
|
794
|
+
lambda { |*args|
|
795
|
+
'AXElements no longer supports notifications'
|
796
|
+
}
|
797
|
+
],
|
798
|
+
KAXErrorAPIDisabled => [
|
799
|
+
RuntimeError,
|
800
|
+
lambda { |*args|
|
801
|
+
'AXAPI has been disabled'
|
802
|
+
}
|
803
|
+
],
|
804
|
+
KAXErrorNoValue => [
|
805
|
+
RuntimeError,
|
806
|
+
lambda { |*args|
|
807
|
+
'AXElements internal error. ENoValue should be handled internally!'
|
808
|
+
}
|
809
|
+
],
|
810
|
+
KAXErrorParameterizedAttributeUnsupported => [
|
811
|
+
ArgumentError,
|
812
|
+
lambda { |*args|
|
813
|
+
"#{args[0].inspect} does not have a #{args[1].inspect} parameterized attribute"
|
814
|
+
}
|
815
|
+
],
|
816
|
+
KAXErrorNotEnoughPrecision => [
|
817
|
+
RuntimeError,
|
818
|
+
lambda { |*args|
|
819
|
+
'AXAPI said there was not enough precision ¯\(°_o)/¯'
|
820
|
+
}
|
821
|
+
]
|
822
|
+
}
|
823
|
+
|
824
|
+
# @param code [Number]
|
825
|
+
def handle_error code, *args
|
826
|
+
raise RuntimeError, 'assertion failed: code 0 means success!' if code.zero?
|
827
|
+
klass, handler = AXERROR.fetch code, [
|
828
|
+
RuntimeError,
|
829
|
+
lambda { |*args| "An unknown error code was returned [#{code}]:#{inspect}" }
|
830
|
+
]
|
831
|
+
raise klass, handler.call(self, *args), caller(1)
|
832
|
+
end
|
833
|
+
|
834
|
+
|
835
|
+
# @!endgroup
|
836
|
+
|
837
|
+
|
838
|
+
##
|
839
|
+
# @private
|
840
|
+
#
|
841
|
+
# `Pointer` type encoding for `CFArrayRef` objects
|
842
|
+
#
|
843
|
+
# @return [String]
|
844
|
+
ARRAY = '^{__CFArray}'
|
845
|
+
|
846
|
+
##
|
847
|
+
# @private
|
848
|
+
#
|
849
|
+
# `Pointer` type encoding for `AXUIElementRef` objects
|
850
|
+
#
|
851
|
+
# @return [String]
|
852
|
+
ELEMENT = '^{__AXUIElement}'
|
853
|
+
|
854
|
+
end
|
855
|
+
|
856
|
+
# hack to find the __NSCFType class and mix things in
|
857
|
+
klass = AXUIElementCreateSystemWide().class
|
858
|
+
klass.send :include, Accessibility::Element
|