AXElements 1.0.0.alpha11 → 1.0.0.beta

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