AXElements 1.0.0.beta4 → 6.0.0

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