accessibility_core 0.4.3 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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