AXElements 1.0.0.beta4 → 6.0.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 (43) hide show
  1. checksums.yaml +7 -0
  2. data/History.markdown +7 -0
  3. data/README.markdown +41 -29
  4. data/Rakefile +1 -6
  5. data/lib/accessibility/dsl.rb +17 -15
  6. data/lib/accessibility/pretty_printer.rb +40 -12
  7. data/lib/accessibility/system_info.rb +2 -0
  8. data/lib/accessibility/text_highlighter.rb +73 -0
  9. data/lib/accessibility/translator.rb +1 -0
  10. data/lib/accessibility/version.rb +1 -1
  11. data/lib/accessibility.rb +13 -114
  12. data/lib/ax/application.rb +54 -42
  13. data/lib/ax/element.rb +27 -7
  14. data/lib/ax/scroll_area.rb +1 -0
  15. data/lib/ax/systemwide.rb +5 -4
  16. data/lib/ax/text_area.rb +16 -0
  17. data/lib/ax/text_field.rb +10 -0
  18. data/lib/ax_elements/mri.rb +36 -1
  19. data/rakelib/ci.rake +9 -0
  20. data/rakelib/doc.rake +4 -13
  21. data/rakelib/ext.rake +1 -57
  22. data/rakelib/gem.rake +7 -23
  23. data/rakelib/test.rake +10 -17
  24. data/test/helper.rb +15 -18
  25. data/test/integration/accessibility/test_dsl.rb +9 -3
  26. data/test/integration/ax/test_application.rb +1 -1
  27. data/test/integration/ax/test_text_area.rb +62 -0
  28. data/test/integration/ax/test_text_field.rb +49 -0
  29. data/test/sanity/accessibility/test_dsl.rb +1 -0
  30. data/test/sanity/accessibility/test_errors.rb +1 -1
  31. data/test/sanity/accessibility/test_pretty_printer.rb +6 -1
  32. data/test/sanity/accessibility/test_qualifier.rb +1 -1
  33. data/test/sanity/accessibility/test_system_info.rb +109 -0
  34. data/test/sanity/accessibility/test_translator.rb +1 -1
  35. data/test/sanity/accessibility/test_version.rb +2 -2
  36. data/test/sanity/ax_elements/test_nsarray_compat.rb +1 -1
  37. data/test/sanity/ax_elements/test_nsobject_inspect.rb +1 -1
  38. data/test/sanity/test_ax_elements.rb +1 -1
  39. metadata +34 -59
  40. data/ext/accessibility/key_coder/extconf.rb +0 -22
  41. data/ext/accessibility/key_coder/key_coder.c +0 -123
  42. data/lib/accessibility/string.rb +0 -502
  43. data/test/sanity/accessibility/test_string.rb +0 -238
@@ -1,502 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- require 'accessibility/version'
4
- require 'accessibility/key_coder'
5
-
6
- ##
7
- # Parses strings of human readable text into a series of events meant to
8
- # be processed by {Accessibility::Core#post} or {KeyCoder.post_event}.
9
- #
10
- # Supports most, if not all, latin keyboard layouts, maybe some
11
- # international layouts as well. Japanese layouts can be made to work with
12
- # use of `String#transform`.
13
- #
14
- # @example
15
- #
16
- # app = AXUIElementCreateApplication(3456)
17
- # include Accessibility::String
18
- # app.post keyboard_events_for "Hello, world!\n"
19
- #
20
- module Accessibility::String
21
-
22
- ##
23
- # Generate keyboard events for the given string. Strings should be in a
24
- # human readable with a few exceptions. Command key (e.g. control, option,
25
- # command) should be written in string as they appear in
26
- # {Accessibility::String::EventGenerator::CUSTOM}.
27
- #
28
- # For more details on event generation, read the
29
- # [Keyboarding wiki](http://github.com/Marketcircle/AXElements/wiki/Keyboarding).
30
- #
31
- # @param string [#to_s]
32
- # @return [Array<Array(Fixnum,Boolean)>]
33
- def keyboard_events_for string
34
- EventGenerator.new(Lexer.new(string).lex).generate
35
- end
36
-
37
- ##
38
- # Tokenizer for strings. This class will take a string and break
39
- # it up into chunks for the event generator. The structure generated
40
- # here is an array that contains strings and recursively other arrays
41
- # of strings and arrays of strings.
42
- #
43
- # @example
44
- #
45
- # Lexer.new("Hai").lex # => ['H','a','i']
46
- # Lexer.new("\\CONTROL").lex # => [["\\CONTROL"]]
47
- # Lexer.new("\\COMMAND+a").lex # => [["\\COMMAND", ['a']]]
48
- # Lexer.new("One\nTwo").lex # => ['O','n','e',"\n",'T','w','o']
49
- #
50
- class Lexer
51
-
52
- ##
53
- # Once a string is lexed, this contains the tokenized structure.
54
- #
55
- # @return [Array<String,Array<String,...>]
56
- attr_accessor :tokens
57
-
58
- # @param string [#to_s]
59
- def initialize string
60
- @chars = string.to_s
61
- @tokens = []
62
- end
63
-
64
- ##
65
- # Tokenize the string that the lexer was initialized with and
66
- # return the sequence of tokens that were lexed.
67
- #
68
- # @return [Array<String,Array<String,...>]
69
- def lex
70
- length = @chars.length
71
- @index = 0
72
- while @index < length
73
- @tokens << if custom?
74
- lex_custom
75
- else
76
- lex_char
77
- end
78
- @index += 1
79
- end
80
- @tokens
81
- end
82
-
83
-
84
- private
85
-
86
- ##
87
- # Is it a real custom escape? Kind of a lie, there is one
88
- # case it does not handle--they get handled in the generator,
89
- # but maybe they should be handled here?
90
- # - An upper case letter or symbol following `"\\"` that is
91
- # not mapped
92
- def custom?
93
- @chars[@index] == CUSTOM_ESCAPE &&
94
- (next_char = @chars[@index+1]) &&
95
- next_char == next_char.upcase &&
96
- next_char != SPACE
97
- end
98
-
99
- # @return [Array]
100
- def lex_custom
101
- start = @index
102
- loop do
103
- char = @chars[@index]
104
- if char == PLUS
105
- if @chars[@index-1] == CUSTOM_ESCAPE # \\+ case
106
- @index += 1
107
- return custom_subseq start
108
- else
109
- tokens = custom_subseq start
110
- @index += 1
111
- return tokens << lex_custom
112
- end
113
- elsif char == SPACE
114
- return custom_subseq start
115
- elsif char == nil
116
- raise ArgumentError, "Bad escape sequence" if start == @index
117
- return custom_subseq start
118
- else
119
- @index += 1
120
- end
121
- end
122
- end
123
-
124
- # @return [Array]
125
- def custom_subseq start
126
- [@chars[start...@index]]
127
- end
128
-
129
- # @return [String]
130
- def lex_char
131
- @chars[@index]
132
- end
133
-
134
- # @private
135
- # @return [String]
136
- SPACE = " "
137
- # @private
138
- # @return [String]
139
- PLUS = "+"
140
- # @private
141
- # @return [String]
142
- CUSTOM_ESCAPE = "\\"
143
- end
144
-
145
-
146
- ##
147
- # Generate a sequence of keyboard events given a sequence of tokens.
148
- # The token format is defined by the {Lexer} class output; it is best
149
- # to use that class to generate the tokens.
150
- #
151
- # @example
152
- #
153
- # # Upper case 'A'
154
- # EventGenerator.new(["A"]).generate # => [[56,true],[70,true],[70,false],[56,false]]
155
- #
156
- # # Press the volume up key
157
- # EventGenerator.new([["\\F12"]]).generate # => [[0x6F,true],[0x6F,false]]
158
- #
159
- # # Hotkey, press and hold command key and then 'a', then release both
160
- # EventGenerator.new([["\\CMD",["a"]]]).generate # => [[55,true],[70,true],[70,false],[55,false]]
161
- #
162
- # # Press the return/enter key
163
- # EventGenerator.new(["\n"]).generate # => [[10,true],[10,false]]
164
- #
165
- class EventGenerator
166
-
167
- ##
168
- # Regenerate the portion of the key mapping that is set dynamically
169
- # based on keyboard layout (e.g. US, Dvorak, etc.).
170
- #
171
- # This method should be called whenever the keyboard layout changes.
172
- # This can be called automatically by registering for a notification
173
- # in a run looped environment.
174
- def self.regenerate_dynamic_mapping
175
- # KeyCoder is declared in the Objective-C extension
176
- MAPPING.merge! KeyCoder.dynamic_mapping
177
- # Also add an alias to the mapping
178
- MAPPING["\n"] = MAPPING["\r"]
179
- end
180
-
181
- ##
182
- # Dynamic mapping of characters to keycodes. The map is generated at
183
- # startup time in order to support multiple keyboard layouts.
184
- #
185
- # @return [Hash{String=>Fixnum}]
186
- MAPPING = {}
187
-
188
- # Initialize the table
189
- regenerate_dynamic_mapping
190
-
191
- ##
192
- # @note These mappings are all static and come from `/System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Headers/Events.h`
193
- #
194
- # Map of custom escape sequences to their hardcoded keycode value.
195
- #
196
- # @return [Hash{String=>Fixnum}]
197
- CUSTOM = {
198
- "\\FUNCTION" => 0x3F, # Standard Control Keys
199
- "\\FN" => 0x3F,
200
- "\\CONTROL" => 0x3B,
201
- "\\CTRL" => 0x3B,
202
- "\\OPTION" => 0x3A,
203
- "\\OPT" => 0x3A,
204
- "\\ALT" => 0x3A,
205
- "\\COMMAND" => 0x37,
206
- "\\CMD" => 0x37,
207
- "\\LSHIFT" => 0x38,
208
- "\\SHIFT" => 0x38,
209
- "\\CAPSLOCK" => 0x39,
210
- "\\CAPS" => 0x39,
211
- "\\ROPTION" => 0x3D,
212
- "\\ROPT" => 0x3D,
213
- "\\RALT" => 0x3D,
214
- "\\RCONTROL" => 0x3E,
215
- "\\RCTRL" => 0x3E,
216
- "\\RSHIFT" => 0x3C,
217
- "\\ESCAPE" => 0x35, # Top Row Keys
218
- "\\ESC" => 0x35,
219
- "\\VOLUMEUP" => 0x48,
220
- "\\VOLUP" => 0x48,
221
- "\\VOLUMEDOWN" => 0x49,
222
- "\\VOLDOWN" => 0x49,
223
- "\\MUTE" => 0x4A,
224
- "\\F1" => 0x7A,
225
- "\\F2" => 0x78,
226
- "\\F3" => 0x63,
227
- "\\F4" => 0x76,
228
- "\\F5" => 0x60,
229
- "\\F6" => 0x61,
230
- "\\F7" => 0x62,
231
- "\\F8" => 0x64,
232
- "\\F9" => 0x65,
233
- "\\F10" => 0x6D,
234
- "\\F11" => 0x67,
235
- "\\F12" => 0x6F,
236
- "\\F13" => 0x69,
237
- "\\F14" => 0x6B,
238
- "\\F15" => 0x71,
239
- "\\F16" => 0x6A,
240
- "\\F17" => 0x40,
241
- "\\F18" => 0x4F,
242
- "\\F19" => 0x50,
243
- "\\F20" => 0x5A,
244
- "\\HELP" => 0x72, # Island Keys
245
- "\\HOME" => 0x73,
246
- "\\END" => 0x77,
247
- "\\PAGEUP" => 0x74,
248
- "\\PAGEDOWN" => 0x79,
249
- "\\DELETE" => 0x75,
250
- "\\LEFT" => 0x7B, # Arrow Keys
251
- "\\<-" => 0x7B,
252
- "\\RIGHT" => 0x7C,
253
- "\\->" => 0x7C,
254
- "\\DOWN" => 0x7D,
255
- "\\UP" => 0x7E,
256
- "\\0" => 0x52, # Keypad Keys
257
- "\\1" => 0x53,
258
- "\\2" => 0x54,
259
- "\\3" => 0x55,
260
- "\\4" => 0x56,
261
- "\\5" => 0x57,
262
- "\\6" => 0x58,
263
- "\\7" => 0x59,
264
- "\\8" => 0x5B,
265
- "\\9" => 0x5C,
266
- "\\DECIMAL" => 0x41,
267
- "\\." => 0x41,
268
- "\\PLUS" => 0x45,
269
- "\\+" => 0x45,
270
- "\\MULTIPLY" => 0x43,
271
- "\\*" => 0x43,
272
- "\\MINUS" => 0x4E,
273
- "\\-" => 0x4E,
274
- "\\DIVIDE" => 0x4B,
275
- "\\/" => 0x4B,
276
- "\\EQUALS" => 0x51,
277
- "\\=" => 0x51,
278
- "\\ENTER" => 0x4C,
279
- "\\CLEAR" => 0x47,
280
- }
281
-
282
- ##
283
- # Mapping of shifted (characters written when holding shift) characters
284
- # to keycodes.
285
- #
286
- # @return [Hash{String=>Fixnum}]
287
- SHIFTED = {
288
- '~' => '`',
289
- '!' => '1',
290
- '@' => '2',
291
- '#' => '3',
292
- '$' => '4',
293
- '%' => '5',
294
- '^' => '6',
295
- '&' => '7',
296
- '*' => '8',
297
- '(' => '9',
298
- ')' => '0',
299
- '{' => '[',
300
- '}' => ']',
301
- '?' => '/',
302
- '+' => '=',
303
- '|' => "\\",
304
- ':' => ';',
305
- '_' => '-',
306
- '"' => "'",
307
- '<' => ',',
308
- '>' => '.',
309
- 'A' => 'a',
310
- 'B' => 'b',
311
- 'C' => 'c',
312
- 'D' => 'd',
313
- 'E' => 'e',
314
- 'F' => 'f',
315
- 'G' => 'g',
316
- 'H' => 'h',
317
- 'I' => 'i',
318
- 'J' => 'j',
319
- 'K' => 'k',
320
- 'L' => 'l',
321
- 'M' => 'm',
322
- 'N' => 'n',
323
- 'O' => 'o',
324
- 'P' => 'p',
325
- 'Q' => 'q',
326
- 'R' => 'r',
327
- 'S' => 's',
328
- 'T' => 't',
329
- 'U' => 'u',
330
- 'V' => 'v',
331
- 'W' => 'w',
332
- 'X' => 'x',
333
- 'Y' => 'y',
334
- 'Z' => 'z',
335
- }
336
-
337
- ##
338
- # Mapping of optioned (characters written when holding option/alt)
339
- # characters to keycodes.
340
- #
341
- # @return [Hash{String=>Fixnum}]
342
- OPTIONED = {
343
- '¡' => '1',
344
- '™' => '2',
345
- '£' => '3',
346
- '¢' => '4',
347
- '∞' => '5',
348
- '§' => '6',
349
- '¶' => '7',
350
- '•' => '8',
351
- 'ª' => '9',
352
- 'º' => '0',
353
- '“' => '[',
354
- '‘' => ']',
355
- 'æ' => "'",
356
- '≤' => ',',
357
- '≥' => '.',
358
- 'π' => 'p',
359
- '¥' => 'y',
360
- 'ƒ' => 'f',
361
- '©' => 'g',
362
- '®' => 'r',
363
- '¬' => 'l',
364
- '÷' => '/',
365
- '≠' => '=',
366
- '«' => "\\",
367
- 'å' => 'a',
368
- 'ø' => 'o',
369
- '´' => 'e',
370
- '¨' => 'u',
371
- 'ˆ' => 'i',
372
- '∂' => 'd',
373
- '˙' => 'h',
374
- '†' => 't',
375
- '˜' => 'n',
376
- 'ß' => 's',
377
- '–' => '-',
378
- '…' => ';',
379
- 'œ' => 'q',
380
- '∆' => 'j',
381
- '˚' => 'k',
382
- '≈' => 'x',
383
- '∫' => 'b',
384
- 'µ' => 'm',
385
- '∑' => 'w',
386
- '√' => 'v',
387
- 'Ω' => 'z',
388
- }
389
-
390
-
391
- ##
392
- # Once {#generate} is called, this contains the sequence of
393
- # events.
394
- #
395
- # @return [Array<Array(Fixnum,Boolean)>]
396
- attr_reader :events
397
-
398
- # @param tokens [Array<String,Array<String,Array...>>]
399
- def initialize tokens
400
- @tokens = tokens
401
- # *3 since the output array will be at least *2 the
402
- # number of tokens passed in, but will often be larger
403
- # due to shifted/optioned characters and custom escapes;
404
- # though a better number could be derived from
405
- # analyzing common input...
406
- @events = Array.new tokens.size*3
407
- end
408
-
409
- ##
410
- # Generate the events for the tokens the event generator
411
- # was initialized with. Returns the generated events, though
412
- # you can also use {#events} to get the events later.
413
- #
414
- # @return [Array<Array(Fixnum,Boolean)>]
415
- def generate
416
- @index = 0
417
- gen_all @tokens
418
- @events.compact!
419
- @events
420
- end
421
-
422
-
423
- private
424
-
425
- def add event
426
- @events[@index] = event
427
- @index += 1
428
- end
429
- def previous_token; @events[@index-1] end
430
- def rewind_index; @index -= 1 end
431
-
432
- def gen_all tokens
433
- tokens.each do |token|
434
- if token.kind_of? Array
435
- gen_nested token.first, token[1..-1]
436
- else
437
- gen_single token
438
- end
439
- end
440
- end
441
-
442
- def gen_nested head, tail
443
- ((code = CUSTOM[head]) && gen_dynamic(code, tail)) ||
444
- ((code = MAPPING[head]) && gen_dynamic(code, tail)) ||
445
- ((code = SHIFTED[head]) && gen_shifted(code, tail)) ||
446
- ((code = OPTIONED[head]) && gen_optioned(code, tail)) ||
447
- gen_all(head.split(EMPTY_STRING)) # handling a special case :(
448
- end
449
-
450
- def gen_single token
451
- ((code = MAPPING[token]) && gen_dynamic(code, nil)) ||
452
- ((code = SHIFTED[token]) && gen_shifted(code, nil)) ||
453
- ((code = OPTIONED[token]) && gen_optioned(code, nil)) ||
454
- raise(ArgumentError, "#{token.inspect} has no mapping, bail!")
455
- end
456
-
457
- def gen_shifted code, tail
458
- previous_token == SHIFT_UP ? rewind_index : add(SHIFT_DOWN)
459
- gen_dynamic MAPPING[code], tail
460
- add SHIFT_UP
461
- end
462
-
463
- def gen_optioned code, tail
464
- previous_token == OPTION_UP ? rewind_index : add(OPTION_DOWN)
465
- gen_dynamic MAPPING[code], tail
466
- add OPTION_UP
467
- end
468
-
469
- def gen_dynamic code, tail
470
- add [code, true]
471
- gen_all tail if tail
472
- add [code, false]
473
- end
474
-
475
- # @private
476
- # @return [String]
477
- EMPTY_STRING = ""
478
- # @private
479
- # @return [Array(Number,Boolean)]
480
- OPTION_DOWN = [58, true]
481
- # @private
482
- # @return [Array(Number,Boolean)]
483
- OPTION_UP = [58, false]
484
- # @private
485
- # @return [Array(Number,Boolean)]
486
- SHIFT_DOWN = [56, true]
487
- # @private
488
- # @return [Array(Number,Boolean)]
489
- SHIFT_UP = [56, false]
490
- end
491
-
492
- end
493
-
494
-
495
- ##
496
- # @note This will only work if a run loop is running
497
- # framework 'ApplicationServices' if defined? MACRUBY_VERSION
498
- # Register to be notified if the keyboard layout changes at runtime
499
- # NSDistributedNotificationCenter.defaultCenter.addObserver Accessibility::String::EventGenerator,
500
- # selector: 'regenerate_dynamic_mapping',
501
- # name: KTISNotifySelectedKeyboardInputSourceChanged,
502
- # object: nil