accessibility_core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. data/.yardopts +9 -0
  2. data/History.markdown +36 -0
  3. data/README.markdown +66 -0
  4. data/Rakefile +72 -0
  5. data/ext/accessibility/bridge/ext/accessibility/bridge/bridge.c +490 -0
  6. data/ext/accessibility/bridge/ext/accessibility/bridge/extconf.rb +22 -0
  7. data/ext/accessibility/bridge/lib/accessibility/bridge.rb +33 -0
  8. data/ext/accessibility/bridge/lib/accessibility/bridge/common.rb +57 -0
  9. data/ext/accessibility/bridge/lib/accessibility/bridge/macruby.rb +185 -0
  10. data/ext/accessibility/bridge/lib/accessibility/bridge/mri.rb +121 -0
  11. data/ext/accessibility/bridge/lib/accessibility/bridge/version.rb +6 -0
  12. data/ext/accessibility/bridge/test/array_test.rb +31 -0
  13. data/ext/accessibility/bridge/test/boxed_test.rb +23 -0
  14. data/ext/accessibility/bridge/test/cfrange_test.rb +21 -0
  15. data/ext/accessibility/bridge/test/cgpoint_test.rb +54 -0
  16. data/ext/accessibility/bridge/test/cgrect_test.rb +60 -0
  17. data/ext/accessibility/bridge/test/cgsize_test.rb +54 -0
  18. data/ext/accessibility/bridge/test/helper.rb +19 -0
  19. data/ext/accessibility/bridge/test/nsstring_test.rb +22 -0
  20. data/ext/accessibility/bridge/test/nsurl_test.rb +17 -0
  21. data/ext/accessibility/bridge/test/object_test.rb +19 -0
  22. data/ext/accessibility/bridge/test/range_test.rb +16 -0
  23. data/ext/accessibility/bridge/test/string_test.rb +35 -0
  24. data/ext/accessibility/bridge/test/uri_test.rb +15 -0
  25. data/ext/accessibility/core/bridge.h +1 -0
  26. data/ext/accessibility/core/core.c +705 -0
  27. data/ext/accessibility/core/extconf.rb +22 -0
  28. data/ext/accessibility/highlighter/extconf.rb +22 -0
  29. data/ext/accessibility/highlighter/highlighter.c +7 -0
  30. data/lib/accessibility/core.rb +12 -0
  31. data/lib/accessibility/core/core_ext/common.rb +57 -0
  32. data/lib/accessibility/core/core_ext/macruby.rb +140 -0
  33. data/lib/accessibility/core/core_ext/mri.rb +121 -0
  34. data/lib/accessibility/core/macruby.rb +858 -0
  35. data/lib/accessibility/core/version.rb +6 -0
  36. data/test/helper.rb +48 -0
  37. metadata +158 -0
@@ -0,0 +1,23 @@
1
+ require 'test/helper'
2
+
3
+ if on_macruby?
4
+
5
+ ##
6
+ # Boxed is the common ancestor of all structs defined by bridge support
7
+ # in MacRuby.
8
+ class BoxedTest < MiniTest::Unit::TestCase
9
+
10
+ def test_to_ax_raises_for_arbitrary_boxes
11
+ assert_raises(NotImplementedError) { NSEdgeInsets.new.to_ax }
12
+ end
13
+
14
+ def test_to_ruby # bet you would never dare to abuse the parser like this!
15
+ assert_equal CGPointZero, CGPointZero .to_ax.to_ruby
16
+ assert_equal CGSize.new(10,10), CGSize.new(10, 10) .to_ax.to_ruby
17
+ assert_equal CGRectMake(1,2,3,4), CGRectMake(1,2,3,4).to_ax.to_ruby
18
+ assert_equal 1..10, CFRange.new(1, 10) .to_ax.to_ruby
19
+ assert_equal Range.new(1,10), CFRange.new(1,10) .to_ax.to_ruby
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,21 @@
1
+ require 'test/helper'
2
+
3
+ if on_macruby?
4
+ class CFRangeTest < MiniTest::Unit::TestCase
5
+
6
+ def test_to_ax
7
+ range = CFRange.new(5, 4)
8
+ value = range.to_ax
9
+ ptr = Pointer.new CFRange.type
10
+ AXValueGetValue(value, 4, ptr)
11
+ assert_equal range, ptr.value, 'range makes a value'
12
+ end
13
+
14
+ def test_cf_range_to_ruby_range_compatability
15
+ assert_equal CFRangeMake(1,10).to_ax, (1..10 ).to_ax
16
+ assert_equal CFRangeMake(1, 9).to_ax, (1...10 ).to_ax
17
+ assert_equal CFRangeMake(0, 3).to_ax, (0..2 ).to_ax
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,54 @@
1
+ require 'test/helper'
2
+
3
+ class CGPointTest < MiniTest::Unit::TestCase
4
+
5
+ def test_attributes
6
+ p = CGPoint.new
7
+ assert_respond_to p, :x
8
+ assert_respond_to p, :y
9
+ end
10
+
11
+ def test_to_a
12
+ p = CGPoint.new 1, 2
13
+ assert_equal [1, 2], p.to_a
14
+ end
15
+
16
+ def test_to_point
17
+ p = CGPoint.new
18
+ assert_same p, p.to_point
19
+
20
+ p = CGPoint.new 4, 2
21
+ assert_same p, p.to_point
22
+ end
23
+
24
+ def test_initialize
25
+ p = CGPoint.new
26
+ assert_equal 0, p.x
27
+ assert_equal 0, p.y
28
+
29
+ x, y = rand_nums 2
30
+ p = CGPoint.new x, y
31
+ assert_equal x, p.x
32
+ assert_equal y, p.y
33
+
34
+ x, y = rand_floats 2
35
+ p = CGPoint.new x, y
36
+ assert_equal x, p.x
37
+ assert_equal y, p.y
38
+ end
39
+
40
+ def test_inspect
41
+ assert_match /Point x=1.0 y=2.0>/, CGPoint.new(1, 2).inspect
42
+ assert_match /Point x=3.0 y=5.0>/, CGPoint.new(3, 5).inspect
43
+ end
44
+
45
+ if on_macruby?
46
+ def test_to_ax
47
+ value = CGPointZero.to_ax
48
+ ptr = Pointer.new CGPoint.type
49
+ AXValueGetValue(value, 1, ptr)
50
+ assert_equal CGPointZero, ptr.value, 'point makes a value'
51
+ end
52
+ end
53
+
54
+ end
@@ -0,0 +1,60 @@
1
+ require 'test/helper'
2
+
3
+ class CGRectTest < MiniTest::Unit::TestCase
4
+
5
+ def test_attributes
6
+ r = CGRect.new
7
+ assert_respond_to r, :origin
8
+ assert_respond_to r, :size
9
+ end
10
+
11
+ def test_to_a # we want to match MacRuby here
12
+ r = CGRect.new
13
+ assert_equal [CGPoint.new, CGSize.new], r.to_a
14
+ end
15
+
16
+ def test_to_rect
17
+ r = CGRect.new
18
+ assert_same r, r.to_rect
19
+
20
+ r = CGRect.new CGPoint.new(4, 2), CGSize.new(8, 3)
21
+ assert_same r, r.to_rect
22
+ end
23
+
24
+ def test_initialize
25
+ r = CGRect.new
26
+ assert_equal 0, r.origin.x
27
+ assert_equal 0, r.origin.y
28
+ assert_equal 0, r.size.width
29
+ assert_equal 0, r.size.height
30
+
31
+ x, y, w, h = rand_nums 4
32
+ p = CGPoint.new x, y
33
+ s = CGSize.new w, h
34
+ r = CGRect.new p, s
35
+ assert_equal p, r.origin
36
+ assert_equal s, r.size
37
+ end
38
+
39
+ def test_inspect
40
+ p1 = CGPoint.new *rand_nums(2)
41
+ p2 = CGPoint.new *rand_nums(2)
42
+ s1 = CGSize.new *rand_nums(2)
43
+ s2 = CGSize.new *rand_nums(2)
44
+ r1 = CGRect.new p1, s1
45
+ r2 = CGRect.new p2, s2
46
+ assert_match /Rect origin=#{p1.inspect} size=#{s1.inspect}>/, r1.inspect
47
+ assert_match /Rect origin=#{p2.inspect} size=#{s2.inspect}>/, r2.inspect
48
+ end
49
+
50
+ if on_macruby?
51
+ def test_to_ax
52
+ value = CGRectZero.to_ax
53
+ ptr = Pointer.new CGRect.type
54
+ AXValueGetValue(value, 3, ptr)
55
+ assert_equal CGRectZero, ptr.value, 'rect makes a value'
56
+ end
57
+ end
58
+
59
+
60
+ end
@@ -0,0 +1,54 @@
1
+ require 'test/helper'
2
+
3
+ class CGSizeTest < MiniTest::Unit::TestCase
4
+
5
+ def test_attributes
6
+ s = CGSize.new
7
+ assert_respond_to s, :width
8
+ assert_respond_to s, :height
9
+ end
10
+
11
+ def test_to_a
12
+ s = CGSize.new 1, 2
13
+ assert_equal [1, 2], s.to_a
14
+ end
15
+
16
+ def test_to_size
17
+ s = CGSize.new
18
+ assert_same s, s.to_size
19
+
20
+ s = CGSize.new 4, 2
21
+ assert_same s, s.to_size
22
+ end
23
+
24
+ def test_initialize
25
+ s = CGSize.new
26
+ assert_equal 0, s.width
27
+ assert_equal 0, s.height
28
+
29
+ w, h = rand_nums 2
30
+ s = CGSize.new w, h
31
+ assert_equal w, s.width
32
+ assert_equal h, s.height
33
+
34
+ w, h = rand_floats 2
35
+ s = CGSize.new w, h
36
+ assert_equal w, s.width
37
+ assert_equal h, s.height
38
+ end
39
+
40
+ def test_inspect
41
+ assert_match /Size width=1.0 height=2.0>/, CGSize.new(1, 2).inspect
42
+ assert_match /Size width=3.0 height=5.0>/, CGSize.new(3, 5).inspect
43
+ end
44
+
45
+ if on_macruby?
46
+ def test_to_ax
47
+ value = CGSizeZero.to_ax
48
+ ptr = Pointer.new CGSize.type
49
+ AXValueGetValue(value, 2, ptr)
50
+ assert_equal CGSizeZero, ptr.value, 'size makes a value'
51
+ end
52
+ end
53
+
54
+ end
@@ -0,0 +1,19 @@
1
+ require 'minitest/autorun'
2
+ require 'minitest/pride'
3
+ require 'accessibility/bridge'
4
+
5
+ class MiniTest::Unit::TestCase
6
+
7
+ def rand_nums count, range = 1_000
8
+ Array.new count do
9
+ rand range
10
+ end
11
+ end
12
+
13
+ def rand_floats count, range = 1_000.0
14
+ Array.new count do
15
+ rand * range
16
+ end
17
+ end
18
+
19
+ end
@@ -0,0 +1,22 @@
1
+ require 'test/helper'
2
+
3
+ if on_macruby?
4
+ class NSStringTest < MiniTest::Unit::TestCase
5
+
6
+ def test_to_url
7
+ site = 'http://marketcircle.com/'
8
+ url = site.to_url
9
+ refute_nil url
10
+ assert_equal NSURL.URLWithString(site), url
11
+
12
+ file = 'file://localhost/Applications/Calculator.app/'
13
+ url = file.to_url
14
+ refute_nil url
15
+ assert_equal NSURL.fileURLWithPath('/Applications/Calculator.app/'), url
16
+
17
+ void = 'not a url at all'
18
+ assert_nil void.to_url
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,17 @@
1
+ require 'test/helper'
2
+
3
+ if on_macruby?
4
+ class TestNSURL < MiniTest::Unit::TestCase
5
+
6
+ def test_to_url_returns_self
7
+ url = NSURL.URLWithString 'http://macruby.org'
8
+ assert_same url, url.to_url
9
+ end
10
+
11
+ def test_to_s
12
+ s = 'http://marketcircle.com'
13
+ assert_equal s, NSURL.URLWithString(s).to_s
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,19 @@
1
+ require 'test/helper'
2
+
3
+ class ObjectTest < MiniTest::Unit::TestCase
4
+
5
+ if on_macruby?
6
+ def test_to_ruby
7
+ assert_same Object, Object.to_ruby
8
+ assert_same 10, 10.to_ruby
9
+ end
10
+
11
+ def test_to_ax
12
+ assert_same Object, Object.to_ax
13
+ assert_same 10, 10.to_ax
14
+ end
15
+ end
16
+
17
+ # we may have other tests for extensions to the Object class
18
+
19
+ end
@@ -0,0 +1,16 @@
1
+ require 'test/helper'
2
+
3
+ if on_macruby?
4
+ class RangeTest < MiniTest::Unit::TestCase
5
+
6
+ def test_to_ax
7
+ assert_equal CFRange.new(5, 4).to_ax, (5..8).to_ax
8
+ end
9
+
10
+ def test_to_ax_raises_when_given_negative_indicies
11
+ assert_raises(ArgumentError) { (1..-10).to_ax }
12
+ assert_raises(ArgumentError) { (-5...10).to_ax }
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,35 @@
1
+ require 'test/helper'
2
+
3
+ class StringTest < MiniTest::Unit::TestCase
4
+
5
+ if on_macruby?
6
+ def test_to_url
7
+ site = 'http://marketcircle.com/'
8
+ url = site.to_url
9
+ refute_nil url
10
+ assert_equal NSURL.URLWithString(site), url
11
+
12
+ file = 'file://localhost/Applications/Calculator.app/'
13
+ url = file.to_url
14
+ refute_nil url
15
+ assert_equal NSURL.fileURLWithPath('/Applications/Calculator.app/'), url
16
+
17
+ assert_nil 'not a url at all'.to_url
18
+ end
19
+
20
+ else
21
+
22
+ def test_to_url
23
+ site = 'http://marketcircle.com/'
24
+ url = site.to_url
25
+ refute_nil url
26
+ assert_equal URI.parse(site), url
27
+
28
+ # Ruby's URI class does not handle the file scheme :(
29
+ # @todo propose the change upstream at some point?
30
+
31
+ assert_raises(URI::InvalidURIError) { 'not a url at all'.to_url }
32
+ end
33
+ end
34
+
35
+ end
@@ -0,0 +1,15 @@
1
+ require 'test/helper'
2
+
3
+ unless on_macruby?
4
+ class TestURIExtensions < MiniTest::Unit::TestCase
5
+
6
+ def test_to_url_returns_self
7
+ url = URI.parse 'http://macruby.org'
8
+ assert_same url, url.to_url
9
+
10
+ url = URI.parse 'ftp://herp.com'
11
+ assert_same url, url.to_url
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1 @@
1
+ #include "../bridge/ext/accessibility/bridge/bridge.c"
@@ -0,0 +1,705 @@
1
+ #include "ruby.h"
2
+
3
+ #ifdef NOT_MACRUBY /* This entire extension is pointless when running on MacRuby */
4
+
5
+ #import <Cocoa/Cocoa.h>
6
+ #include "bridge.h"
7
+
8
+
9
+ static ID ivar_attrs;
10
+ static ID ivar_param_attrs;
11
+ static ID ivar_actions;
12
+ static ID ivar_pid;
13
+ static ID ivar_key_rate;
14
+
15
+ static ID sel_to_f;
16
+
17
+ static ID rate_very_slow;
18
+ static ID rate_slow;
19
+ static ID rate_normal;
20
+ static ID rate_default;
21
+ static ID rate_fast;
22
+ static ID rate_zomg;
23
+
24
+
25
+ static
26
+ VALUE
27
+ handle_error(VALUE self, AXError code)
28
+ {
29
+ // TODO port the error handler from AXElements
30
+ switch (code)
31
+ {
32
+ case kAXErrorInvalidUIElement:
33
+ rb_raise(rb_eArgError, "invalid element (probably dead)");
34
+ case kAXErrorAttributeUnsupported:
35
+ rb_raise(rb_eArgError, "attribute unsupported");
36
+ case kAXErrorActionUnsupported:
37
+ rb_raise(rb_eArgError, "action unsupported");
38
+ case kAXErrorParameterizedAttributeUnsupported:
39
+ rb_raise(rb_eArgError, "parameterized attribute unsupported");
40
+ default:
41
+ rb_raise(rb_eRuntimeError, "you done goofed [%d]", code);
42
+ }
43
+
44
+ return Qnil;
45
+ }
46
+
47
+
48
+ static
49
+ VALUE
50
+ rb_acore_application_for(VALUE self, VALUE pid)
51
+ {
52
+ NSDate* date = [NSDate date];
53
+ [[NSRunLoop currentRunLoop] runUntilDate:date];
54
+ CFRelease(date);
55
+
56
+ pid_t the_pid = NUM2PIDT(pid);
57
+ NSRunningApplication* running_app =
58
+ [NSRunningApplication runningApplicationWithProcessIdentifier:the_pid];
59
+
60
+ if (running_app) {
61
+ VALUE app = wrap_ref(AXUIElementCreateApplication(the_pid));
62
+ CFRelease(running_app);
63
+ return app;
64
+ }
65
+
66
+ rb_raise(
67
+ rb_eArgError,
68
+ "pid `%d' must belong to a running application",
69
+ the_pid
70
+ );
71
+
72
+ return Qnil; // unreachable
73
+ }
74
+
75
+
76
+ static
77
+ VALUE
78
+ rb_acore_system_wide(VALUE self)
79
+ {
80
+ return wrap_ref(AXUIElementCreateSystemWide());
81
+ }
82
+
83
+
84
+ static
85
+ VALUE
86
+ rb_acore_key_rate(VALUE self)
87
+ {
88
+ return rb_ivar_get(self, ivar_key_rate);
89
+ }
90
+
91
+
92
+ static
93
+ VALUE
94
+ rb_acore_set_key_rate(VALUE self, VALUE rate)
95
+ {
96
+ if (TYPE(rate) == T_SYMBOL) {
97
+ ID key_rate = SYM2ID(rate);
98
+ if (key_rate == rate_very_slow)
99
+ rate = DBL2NUM(0.9);
100
+ else if (key_rate == rate_slow)
101
+ rate = DBL2NUM(0.09);
102
+ else if (key_rate == rate_normal || rate == rate_default)
103
+ rate = DBL2NUM(0.009);
104
+ else if (key_rate == rate_fast)
105
+ rate = DBL2NUM(0.0009);
106
+ else if (key_rate == rate_zomg)
107
+ rate = DBL2NUM(0.00009);
108
+ else
109
+ rb_raise(rb_eArgError, "Unknown rate `%s'", rb_id2name(key_rate));
110
+ }
111
+ else {
112
+ rate = rb_funcall(rate, sel_to_f, 0);
113
+ }
114
+
115
+ return rb_ivar_set(self, ivar_key_rate, rate);
116
+ }
117
+
118
+
119
+ static
120
+ int
121
+ acore_is_system_wide(VALUE other)
122
+ {
123
+ AXUIElementRef system = AXUIElementCreateSystemWide();
124
+ int result = CFEqual(unwrap_ref(other), system);
125
+ CFRelease(system);
126
+ return result;
127
+ }
128
+ #define IS_SYSTEM_WIDE(x) (acore_is_system_wide(x))
129
+
130
+
131
+ static
132
+ VALUE
133
+ rb_acore_attributes(VALUE self)
134
+ {
135
+ VALUE cached_attrs = rb_ivar_get(self, ivar_attrs);
136
+ if (cached_attrs != Qnil)
137
+ return cached_attrs;
138
+
139
+ CFArrayRef attrs = NULL;
140
+ AXError code = AXUIElementCopyAttributeNames(unwrap_ref(self), &attrs);
141
+ switch (code)
142
+ {
143
+ case kAXErrorSuccess:
144
+ cached_attrs = wrap_array_strings(attrs);
145
+ rb_ivar_set(self, ivar_attrs, cached_attrs);
146
+ CFRelease(attrs);
147
+ return cached_attrs;
148
+ case kAXErrorInvalidUIElement:
149
+ return rb_ary_new();
150
+ default:
151
+ // TODO we should actually allow for a grace period and try again in
152
+ // every case where would be deferring to the default error handler,
153
+ // and maybe even in every case where we get a non-zero result code
154
+ //
155
+ // WE SHOULD HANDLE THINGS LIKE FAILURE AND CANNOT COMPLETE LIKE THIS
156
+ return handle_error(self, code);
157
+ }
158
+ }
159
+
160
+
161
+ static
162
+ VALUE
163
+ rb_acore_attribute(VALUE self, VALUE name)
164
+ {
165
+ CFTypeRef attr = NULL;
166
+ CFStringRef attr_name = unwrap_string(name);
167
+ AXError code = AXUIElementCopyAttributeValue(
168
+ unwrap_ref(self),
169
+ attr_name,
170
+ &attr
171
+ );
172
+ CFRelease(attr_name);
173
+ switch (code)
174
+ {
175
+ case kAXErrorSuccess:
176
+ return to_ruby(attr);
177
+ case kAXErrorFailure:
178
+ case kAXErrorNoValue:
179
+ case kAXErrorInvalidUIElement:
180
+ case kAXErrorAttributeUnsupported:
181
+ return Qnil;
182
+ default:
183
+ return handle_error(self, code);
184
+ }
185
+ }
186
+
187
+
188
+ static
189
+ VALUE
190
+ rb_acore_size_of(VALUE self, VALUE name)
191
+ {
192
+ CFIndex size = 0;
193
+ CFStringRef attr_name = unwrap_string(name);
194
+ AXError code = AXUIElementGetAttributeValueCount(
195
+ unwrap_ref(self),
196
+ attr_name,
197
+ &size
198
+ );
199
+ CFRelease(attr_name);
200
+ switch (code)
201
+ {
202
+ case kAXErrorSuccess:
203
+ return INT2FIX(size);
204
+ case kAXErrorFailure:
205
+ case kAXErrorNoValue:
206
+ case kAXErrorInvalidUIElement:
207
+ return INT2FIX(0);
208
+ default:
209
+ return handle_error(self, code);
210
+ }
211
+ }
212
+
213
+
214
+ static
215
+ VALUE
216
+ rb_acore_is_writable(VALUE self, VALUE name)
217
+ {
218
+ Boolean result;
219
+ CFStringRef attr_name = unwrap_string(name);
220
+ AXError code = AXUIElementIsAttributeSettable(
221
+ unwrap_ref(self),
222
+ attr_name,
223
+ &result
224
+ );
225
+ CFRelease(attr_name);
226
+ switch (code)
227
+ {
228
+ case kAXErrorSuccess:
229
+ return (result ? Qtrue : Qfalse);
230
+ case kAXErrorFailure:
231
+ case kAXErrorNoValue:
232
+ case kAXErrorInvalidUIElement:
233
+ return Qfalse;
234
+ default:
235
+ return handle_error(self, code);
236
+ }
237
+ }
238
+
239
+
240
+ static
241
+ VALUE
242
+ rb_acore_set(VALUE self, VALUE name, VALUE value)
243
+ {
244
+ CFTypeRef ax_value = to_ax(value);
245
+ CFStringRef attr_name = unwrap_string(name);
246
+ AXError code = AXUIElementSetAttributeValue(
247
+ unwrap_ref(self),
248
+ attr_name,
249
+ ax_value
250
+ );
251
+ switch (code)
252
+ {
253
+ case kAXErrorSuccess:
254
+ return value;
255
+ default:
256
+ return handle_error(self, code); // name, value
257
+ }
258
+ }
259
+
260
+
261
+ static
262
+ VALUE
263
+ rb_acore_role(VALUE self)
264
+ {
265
+ CFTypeRef value = NULL;
266
+ AXError code = AXUIElementCopyAttributeValue(
267
+ unwrap_ref(self),
268
+ kAXRoleAttribute,
269
+ &value
270
+ );
271
+ switch (code)
272
+ {
273
+ case kAXErrorSuccess:
274
+ return wrap_string(value);
275
+ case kAXErrorNoValue:
276
+ case kAXErrorInvalidUIElement:
277
+ return Qnil;
278
+ default:
279
+ return handle_error(self, code);
280
+ }
281
+ }
282
+
283
+
284
+ static
285
+ VALUE
286
+ rb_acore_subrole(VALUE self)
287
+ {
288
+ CFTypeRef value = NULL;
289
+ AXError code = AXUIElementCopyAttributeValue(
290
+ unwrap_ref(self),
291
+ kAXSubroleAttribute,
292
+ &value
293
+ );
294
+ switch (code)
295
+ {
296
+ case kAXErrorSuccess:
297
+ return wrap_string((CFStringRef)value);
298
+ case kAXErrorFailure:
299
+ case kAXErrorNoValue:
300
+ case kAXErrorInvalidUIElement:
301
+ case kAXErrorAttributeUnsupported:
302
+ return Qnil;
303
+ default:
304
+ return handle_error(self, code);
305
+ }
306
+ }
307
+
308
+
309
+ static
310
+ VALUE
311
+ rb_acore_parent(VALUE self)
312
+ {
313
+ CFTypeRef value = NULL;
314
+ AXError code = AXUIElementCopyAttributeValue(
315
+ unwrap_ref(self),
316
+ kAXParentAttribute,
317
+ &value
318
+ );
319
+ switch (code)
320
+ {
321
+ case kAXErrorSuccess:
322
+ return wrap_ref(value);
323
+ case kAXErrorFailure:
324
+ case kAXErrorNoValue:
325
+ case kAXErrorInvalidUIElement:
326
+ case kAXErrorAttributeUnsupported:
327
+ return Qnil;
328
+ default:
329
+ return handle_error(self, code);
330
+ }
331
+ }
332
+
333
+
334
+ static
335
+ VALUE
336
+ rb_acore_children(VALUE self)
337
+ {
338
+ CFTypeRef value = NULL;
339
+ AXError code = AXUIElementCopyAttributeValue(
340
+ unwrap_ref(self),
341
+ kAXChildrenAttribute,
342
+ &value
343
+ );
344
+ switch (code)
345
+ {
346
+ case kAXErrorSuccess:
347
+ return wrap_array_refs(value);
348
+ case kAXErrorNoValue:
349
+ case kAXErrorInvalidUIElement:
350
+ return rb_ary_new();
351
+ default:
352
+ return handle_error(self, code);
353
+ }
354
+ }
355
+
356
+
357
+ static
358
+ VALUE
359
+ rb_acore_value(VALUE self)
360
+ {
361
+ CFTypeRef value = NULL;
362
+ AXError code = AXUIElementCopyAttributeValue(
363
+ unwrap_ref(self),
364
+ kAXValueAttribute,
365
+ &value
366
+ );
367
+ switch (code)
368
+ {
369
+ case kAXErrorSuccess:
370
+ return to_ruby(value);
371
+ default:
372
+ return handle_error(self, code);
373
+ }
374
+ }
375
+
376
+
377
+ static
378
+ VALUE
379
+ rb_acore_pid(VALUE self)
380
+ {
381
+ VALUE cached_pid = rb_ivar_get(self, ivar_pid);
382
+ if (cached_pid != Qnil)
383
+ return cached_pid;
384
+
385
+ pid_t pid = 0;
386
+ AXError code = AXUIElementGetPid(unwrap_ref(self), &pid);
387
+
388
+ switch (code)
389
+ {
390
+ case kAXErrorSuccess:
391
+ break;
392
+ case kAXErrorInvalidUIElement:
393
+ if (IS_SYSTEM_WIDE(self)) {
394
+ pid = 0;
395
+ break;
396
+ }
397
+ default:
398
+ handle_error(self, code);
399
+ }
400
+
401
+ cached_pid = PIDT2NUM(pid);
402
+ rb_ivar_set(self, ivar_pid, cached_pid);
403
+ return cached_pid;
404
+ }
405
+
406
+
407
+ static
408
+ VALUE
409
+ rb_acore_parameterized_attributes(VALUE self)
410
+ {
411
+ VALUE cached_attrs = rb_ivar_get(self, ivar_param_attrs);
412
+ if (cached_attrs != Qnil)
413
+ return cached_attrs;
414
+
415
+ CFArrayRef attrs = NULL;
416
+ AXError code = AXUIElementCopyParameterizedAttributeNames(
417
+ unwrap_ref(self),
418
+ &attrs
419
+ );
420
+ switch (code)
421
+ {
422
+ case kAXErrorSuccess:
423
+ cached_attrs = wrap_array_strings(attrs);
424
+ rb_ivar_set(self, ivar_param_attrs, cached_attrs);
425
+ CFRelease(attrs);
426
+ return cached_attrs;
427
+ case kAXErrorInvalidUIElement:
428
+ return rb_ary_new();
429
+ default:
430
+ return handle_error(self, code);
431
+ }
432
+ }
433
+
434
+
435
+ static
436
+ VALUE
437
+ rb_acore_parameterized_attribute(VALUE self, VALUE name, VALUE parameter)
438
+ {
439
+ CFTypeRef param = to_ax(parameter);
440
+ CFTypeRef attr = NULL;
441
+ CFStringRef attr_name = unwrap_string(name);
442
+ AXError code = AXUIElementCopyParameterizedAttributeValue(
443
+ unwrap_ref(self),
444
+ attr_name,
445
+ param,
446
+ &attr
447
+ );
448
+ CFRelease(param);
449
+ CFRelease(attr_name);
450
+ switch (code)
451
+ {
452
+ case kAXErrorSuccess:
453
+ return to_ruby(attr);
454
+ case kAXErrorNoValue:
455
+ case kAXErrorInvalidUIElement:
456
+ return Qnil;
457
+ default:
458
+ return handle_error(self, code);
459
+ }
460
+ }
461
+
462
+
463
+ static
464
+ VALUE
465
+ rb_acore_actions(VALUE self)
466
+ {
467
+ VALUE cached_actions = rb_ivar_get(self, ivar_actions);
468
+ if (cached_actions != Qnil)
469
+ return cached_actions;
470
+
471
+ CFArrayRef actions = NULL;
472
+ AXError code = AXUIElementCopyActionNames(unwrap_ref(self), &actions);
473
+ switch (code)
474
+ {
475
+ case kAXErrorSuccess:
476
+ cached_actions = wrap_array_strings(actions);
477
+ rb_ivar_set(self, ivar_actions, cached_actions);
478
+ CFRelease(actions);
479
+ return cached_actions;
480
+ case kAXErrorInvalidUIElement:
481
+ return rb_ary_new();
482
+ default:
483
+ return handle_error(self, code);
484
+ }
485
+ }
486
+
487
+
488
+ static
489
+ VALUE
490
+ rb_acore_perform(VALUE self, VALUE name)
491
+ {
492
+ CFStringRef action = unwrap_string(name);
493
+ AXError code = AXUIElementPerformAction(unwrap_ref(self), action);
494
+
495
+ CFRelease(action);
496
+ switch (code)
497
+ {
498
+ case kAXErrorSuccess:
499
+ return Qtrue;
500
+ case kAXErrorInvalidUIElement:
501
+ return Qfalse;
502
+ default:
503
+ return handle_error(self, code);
504
+ }
505
+ }
506
+
507
+
508
+ static
509
+ VALUE
510
+ rb_acore_post(VALUE self, VALUE events)
511
+ {
512
+ events = rb_ary_to_ary(events);
513
+ long length = RARRAY_LEN(events);
514
+ useconds_t sleep_time = NUM2DBL(rb_ivar_get(rb_cElement, ivar_key_rate)) * 100000;
515
+
516
+ // CGCharCode key_char = 0; // TODO this value seems to not matter?
517
+ VALUE pair;
518
+ CGKeyCode virtual_key;
519
+ int key_state;
520
+ AXError code;
521
+
522
+
523
+ for (long i = 0; i < length; i++) {
524
+ pair = rb_ary_entry(events, i);
525
+ virtual_key = NUM2INT(rb_ary_entry(pair, 0));
526
+ key_state = rb_ary_entry(pair, 1) == Qtrue ? true : false;
527
+ code = AXUIElementPostKeyboardEvent(
528
+ unwrap_ref(self),
529
+ 0,
530
+ virtual_key,
531
+ key_state
532
+ );
533
+ switch (code)
534
+ {
535
+ case kAXErrorSuccess:
536
+ break;
537
+ default:
538
+ handle_error(self, code);
539
+ }
540
+
541
+ usleep(sleep_time);
542
+ }
543
+
544
+ return self;
545
+ }
546
+
547
+
548
+ static
549
+ VALUE
550
+ rb_acore_is_invalid(VALUE self)
551
+ {
552
+ CFTypeRef value = NULL;
553
+ AXError code = AXUIElementCopyAttributeValue(
554
+ unwrap_ref(self),
555
+ kAXRoleAttribute,
556
+ &value
557
+ );
558
+ if (value)
559
+ CFRelease(value);
560
+ return (code == kAXErrorInvalidUIElement ? Qtrue : Qfalse);
561
+ }
562
+
563
+
564
+ static
565
+ VALUE
566
+ rb_acore_application(VALUE self)
567
+ {
568
+ return rb_acore_application_for(rb_cElement, rb_acore_pid(self));
569
+ }
570
+
571
+
572
+ static
573
+ VALUE
574
+ rb_acore_set_timeout_to(VALUE self, VALUE seconds)
575
+ {
576
+ float timeout = NUM2DBL(seconds);
577
+ AXError code = AXUIElementSetMessagingTimeout(unwrap_ref(self), timeout);
578
+
579
+ switch (code)
580
+ {
581
+ case kAXErrorSuccess:
582
+ return seconds;
583
+ default:
584
+ return handle_error(self, code); // seconds
585
+ }
586
+ }
587
+
588
+
589
+ static
590
+ VALUE
591
+ rb_acore_element_at(VALUE self, VALUE point)
592
+ {
593
+ if (self == rb_cElement)
594
+ self = rb_acore_system_wide(self);
595
+
596
+ AXUIElementRef ref = NULL;
597
+ CGPoint p = unwrap_point(point);
598
+ AXError code = AXUIElementCopyElementAtPosition(
599
+ unwrap_ref(self),
600
+ p.x,
601
+ p.y,
602
+ &ref
603
+ );
604
+ switch (code)
605
+ {
606
+ case kAXErrorSuccess:
607
+ return wrap_ref(ref);
608
+ case kAXErrorNoValue:
609
+ return Qnil;
610
+ case kAXErrorInvalidUIElement:
611
+ if (!IS_SYSTEM_WIDE(self))
612
+ return rb_acore_element_at(rb_acore_system_wide(rb_cElement), point);
613
+ else
614
+ return Qnil;
615
+ default:
616
+ return handle_error(self, code); // point, nil, nil
617
+ }
618
+ }
619
+
620
+
621
+ static
622
+ VALUE
623
+ rb_acore_equality(VALUE self, VALUE other)
624
+ {
625
+ if (CLASS_OF(other) == rb_cElement)
626
+ if (CFEqual(unwrap_ref(self), unwrap_ref(other)))
627
+ return Qtrue;
628
+ return Qfalse;
629
+ }
630
+ #endif
631
+
632
+
633
+ void
634
+ Init_core()
635
+ {
636
+ #ifdef NOT_MACRUBY
637
+ if (!AXAPIEnabled())
638
+ rb_raise(
639
+ rb_eRuntimeError,
640
+ "\n" \
641
+ "------------------------------------------------------------------------\n" \
642
+ "Universal Access is disabled on this machine.\n" \
643
+ "Please enable it in the System Preferences.\n" \
644
+ "See https://github.com/Marketcircle/AXElements#getting-setup\n" \
645
+ "------------------------------------------------------------------------\n"
646
+ );
647
+
648
+ Init_bridge();
649
+
650
+ // these should be defined by now
651
+ rb_mAccessibility = rb_const_get(rb_cObject, rb_intern("Accessibility"));
652
+ rb_cElement = rb_define_class_under(rb_mAccessibility, "Element", rb_cObject);
653
+
654
+ ivar_attrs = rb_intern("@attrs");
655
+ ivar_param_attrs = rb_intern("@param_attrs");
656
+ ivar_actions = rb_intern("@actions");
657
+ ivar_pid = rb_intern("@pid");
658
+ ivar_key_rate = rb_intern("@key_rate");
659
+
660
+ rb_define_singleton_method(rb_cElement, "application_for", rb_acore_application_for, 1);
661
+ rb_define_singleton_method(rb_cElement, "system_wide", rb_acore_system_wide, 0);
662
+ rb_define_singleton_method(rb_cElement, "element_at", rb_acore_element_at, 1);
663
+ rb_define_singleton_method(rb_cElement, "key_rate", rb_acore_key_rate, 0);
664
+ rb_define_singleton_method(rb_cElement, "key_rate=", rb_acore_set_key_rate, 1);
665
+
666
+ sel_to_f = rb_intern("to_f");
667
+ rate_very_slow = rb_intern("very_slow");
668
+ rate_slow = rb_intern("slow");
669
+ rate_normal = rb_intern("normal");
670
+ rate_default = rb_intern("default");
671
+ rate_fast = rb_intern("fast");
672
+ rate_zomg = rb_intern("zomg");
673
+ rb_acore_set_key_rate(rb_cElement, DBL2NUM(0.009)); // initialize the value right now
674
+
675
+ rb_define_method(rb_cElement, "attributes", rb_acore_attributes, 0);
676
+ rb_define_method(rb_cElement, "attribute", rb_acore_attribute, 1);
677
+ rb_define_method(rb_cElement, "size_of", rb_acore_size_of, 1);
678
+ rb_define_method(rb_cElement, "writable?", rb_acore_is_writable, 1);
679
+ rb_define_method(rb_cElement, "set", rb_acore_set, 2);
680
+
681
+ rb_define_method(rb_cElement, "role", rb_acore_role, 0);
682
+ rb_define_method(rb_cElement, "subrole", rb_acore_subrole, 0);
683
+ rb_define_method(rb_cElement, "parent", rb_acore_parent, 0);
684
+ rb_define_method(rb_cElement, "children", rb_acore_children, 0);
685
+ rb_define_method(rb_cElement, "value", rb_acore_value, 0);
686
+ rb_define_method(rb_cElement, "pid", rb_acore_pid, 0);
687
+
688
+ rb_define_method(rb_cElement, "parameterized_attributes", rb_acore_parameterized_attributes, 0);
689
+ rb_define_method(rb_cElement, "parameterized_attribute", rb_acore_parameterized_attribute, 2);
690
+
691
+ rb_define_method(rb_cElement, "actions", rb_acore_actions, 0);
692
+ rb_define_method(rb_cElement, "perform", rb_acore_perform, 1);
693
+ rb_define_method(rb_cElement, "post", rb_acore_post, 1);
694
+
695
+ rb_define_method(rb_cElement, "invalid?", rb_acore_is_invalid, 0);
696
+ rb_define_method(rb_cElement, "set_timeout_to", rb_acore_set_timeout_to, 1);
697
+ // TODO make this meaningful, currently has no effect on calling rb_acore_post
698
+ rb_define_method(rb_cElement, "key_rate", rb_acore_key_rate, 0);
699
+ rb_define_method(rb_cElement, "key_rate=", rb_acore_set_key_rate, 1);
700
+ rb_define_method(rb_cElement, "application", rb_acore_application, 0);
701
+ rb_define_method(rb_cElement, "element_at", rb_acore_element_at, 1);
702
+ rb_define_method(rb_cElement, "==", rb_acore_equality, 1);
703
+
704
+ #endif
705
+ }