AXElements 1.0.0.alpha11 → 1.0.0.beta

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