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