AXTyper 0.7.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.
- data/.yardopts.typer +10 -0
- data/README.markdown.typer +74 -0
- data/docs/KeyboardEvents.markdown +119 -0
- data/ext/accessibility/key_coder/extconf.rb +12 -0
- data/ext/accessibility/key_coder/key_coder.c +87 -0
- data/lib/accessibility/string.rb +488 -0
- data/lib/accessibility/version.rb +7 -0
- data/test/runner.rb +24 -0
- data/test/unit/accessibility/test_string.rb +233 -0
- metadata +112 -0
data/.yardopts.typer
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# AXTyper
|
2
|
+
|
3
|
+
This gem is a component of AXElements. It provides an interface for
|
4
|
+
posting keyboard events to the system as well as a mixin for parsing
|
5
|
+
a string into a series of events.
|
6
|
+
|
7
|
+
## Demo
|
8
|
+
|
9
|
+
The basics:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
require 'accessibility/string'
|
13
|
+
|
14
|
+
include Accessibility::String
|
15
|
+
|
16
|
+
keyboard_events_for("Hey, there!").each do |event|
|
17
|
+
KeyCoder.post_event event
|
18
|
+
end
|
19
|
+
```
|
20
|
+
|
21
|
+
Something a bit more advanced:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
require 'accessibility/string'
|
25
|
+
|
26
|
+
include Accessibility::String
|
27
|
+
|
28
|
+
keyboard_events_for("\\COMMAND+\t").each do |event|
|
29
|
+
KeyCoder.post_event event
|
30
|
+
end
|
31
|
+
```
|
32
|
+
|
33
|
+
## Testing
|
34
|
+
|
35
|
+
Running the AXElements test suite for only the AXTyper related tests
|
36
|
+
can be accomplished with the `test:string` task.
|
37
|
+
|
38
|
+
```shell
|
39
|
+
rake test:string
|
40
|
+
```
|
41
|
+
|
42
|
+
## TODO
|
43
|
+
|
44
|
+
The API for posting events is ad-hoc for the sake of demonstration;
|
45
|
+
AXElements exposes this functionality via `Kernel#type`. The standalone
|
46
|
+
API provided here could be improved.
|
47
|
+
|
48
|
+
## License
|
49
|
+
|
50
|
+
Copyright (c) 2012 Marketcircle Inc.
|
51
|
+
All rights reserved.
|
52
|
+
|
53
|
+
Redistribution and use in source and binary forms, with or without
|
54
|
+
modification, are permitted provided that the following conditions are met:
|
55
|
+
* Redistributions of source code must retain the above copyright
|
56
|
+
notice, this list of conditions and the following disclaimer.
|
57
|
+
* Redistributions in binary form must reproduce the above copyright
|
58
|
+
notice, this list of conditions and the following disclaimer in the
|
59
|
+
documentation and/or other materials provided with the distribution.
|
60
|
+
* Neither the name of Marketcircle Inc. nor the names of its
|
61
|
+
contributors may be used to endorse or promote products derived
|
62
|
+
from this software without specific prior written permission.
|
63
|
+
|
64
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
65
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
66
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
67
|
+
DISCLAIMED. IN NO EVENT SHALL Marketcircle Inc. BE LIABLE FOR ANY
|
68
|
+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
69
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
70
|
+
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
71
|
+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
|
72
|
+
IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
73
|
+
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
74
|
+
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# Keyboard Events
|
2
|
+
|
3
|
+
Keyboard events are a system provided by Apple that allows you to
|
4
|
+
simulate keyboard input. The API for this in the `ApplicationServices`
|
5
|
+
framework, but there is an analogue in the `Acessibility` APIs which
|
6
|
+
has the additional option of directing the input to a specific application.
|
7
|
+
|
8
|
+
Using accessibility actions and setting attributes you can already
|
9
|
+
perform most of the interactions that would be possible with the
|
10
|
+
keyboard simulation. However, there are some things that you will need
|
11
|
+
to, or it will just make more sense to, simulate keyboard input. For
|
12
|
+
example, to make use of hot keys you would have to add extra actions
|
13
|
+
or attributes to a control in the application; that would be more
|
14
|
+
work, possibly prone to error, than simply simulating the hot key from
|
15
|
+
outside the application. In other situations you may specifically want
|
16
|
+
to test out keyboard navigation and so actions would not be a good
|
17
|
+
substitute. It may be that the APIs that AXElements provides for
|
18
|
+
typing just make more sense when writing tests or scripts.
|
19
|
+
|
20
|
+
## Typing with the DSL
|
21
|
+
|
22
|
+
The {Accessibility::DSL} mixin exposes keyboard events through the
|
23
|
+
`type` method. A simple example would look like this:
|
24
|
+
|
25
|
+
type "Hello, #{ENV['USER']}! How are you today?\n"
|
26
|
+
|
27
|
+
And watch your computer come to life! The `type` command takes an
|
28
|
+
additional optional parameter that we'll get to later. The first
|
29
|
+
parameter is just a string that you want AXElements to type out. How
|
30
|
+
to format the string should be obvious for the most part, but some
|
31
|
+
things like the command key and arrows might not be so obvious.
|
32
|
+
|
33
|
+
## Formatting Strings
|
34
|
+
|
35
|
+
Letters and numbers should be written just as you would for any other
|
36
|
+
string. Any of the standard symbols can also be plainly added to a
|
37
|
+
string that you want to have typed. Here are some examples:
|
38
|
+
|
39
|
+
type "UPPER CASE LETTERS"
|
40
|
+
type "lower case letters"
|
41
|
+
type "1337 message @/\/|) 57|_||=|="
|
42
|
+
type "A proper sentence can be typed out (all at once)."
|
43
|
+
|
44
|
+
### Regular Escape Sequences
|
45
|
+
|
46
|
+
Things like newlines and tabs should be formatted just like they would
|
47
|
+
in a regular string. That is, normal string escape sequences should
|
48
|
+
"just work" with AXElements. Here are some more examples:
|
49
|
+
|
50
|
+
type "Have a bad \b\b\b\b\b good day!"
|
51
|
+
type "First line.\nSecond line."
|
52
|
+
type "I \t like \t to \t use \t tabs \t a \t lot."
|
53
|
+
type "Explicit\sSpaces."
|
54
|
+
|
55
|
+
### Custom Escape Sequences
|
56
|
+
|
57
|
+
Unfortunately, there is no built in escape sequence for deleting to
|
58
|
+
the right or pressing command keys like `F1`. AXElements defines some
|
59
|
+
extra escape sequences in order to easily represent the remaining
|
60
|
+
keys.
|
61
|
+
|
62
|
+
These custom escape sequences __shoud start with two `\` characters__,
|
63
|
+
as in this example:
|
64
|
+
|
65
|
+
type "\\F1"
|
66
|
+
|
67
|
+
A custom escape sequence __should terminate with a space or the end of
|
68
|
+
the string__, as in this example:
|
69
|
+
|
70
|
+
type "\\PAGEDOWN notice the space afterwards\\PAGEUP but not before"
|
71
|
+
|
72
|
+
The full list of supported custom escape sequences is listed in
|
73
|
+
{Accessibility::StringParser::ESCAPES}. Some escapes have an alias,
|
74
|
+
such as the right arrow key which can be escaped as `"\\RIGHT"` or as
|
75
|
+
`"\\->"`.
|
76
|
+
|
77
|
+
### Hot Keys
|
78
|
+
|
79
|
+
To support pressing multiple keys at the same time, also known as hot
|
80
|
+
keys, you must start with the custom escape sequence for the
|
81
|
+
combination and instead of ending with a space you should put a `+`
|
82
|
+
character to chain the next key. The entire sequence should be ended
|
83
|
+
with a space or nil. Some common examples are opening a file or
|
84
|
+
quitting an application:
|
85
|
+
|
86
|
+
type "\\COMMAND+o"
|
87
|
+
type "\\CONTROL+a Typing at the start of the line"
|
88
|
+
type "\\COMMAND+\\SHIFT+s"
|
89
|
+
|
90
|
+
You might also note that `CMD+SHIFT+s` could also be:
|
91
|
+
|
92
|
+
type "\\COMMAND+S"
|
93
|
+
|
94
|
+
Since a capital `S` will cause the shift key to be held down.
|
95
|
+
|
96
|
+
## Protips
|
97
|
+
|
98
|
+
In order make sure that certain sequences of characters are properly
|
99
|
+
escaped, it is recommended to simply always use double quoted
|
100
|
+
strings.
|
101
|
+
|
102
|
+
### Posting To A Specific Application
|
103
|
+
|
104
|
+
The second argument to the `type` command can be an {AX::Application}
|
105
|
+
object. If you do not include the argument, the events will be posted
|
106
|
+
to the system, which usually means the application that currently is
|
107
|
+
active. Note that you cannot be more specific than the application
|
108
|
+
that you want to send the events to, within the application, the
|
109
|
+
control that has keyboard focus will receive the events.
|
110
|
+
|
111
|
+
### Changing Typing Speed
|
112
|
+
|
113
|
+
You can set the typing speed at load time by setting the environment
|
114
|
+
variable `KEY_RATE`. See {Accessibility::Core::KEY\_RATE} for details on
|
115
|
+
possible values. An example of using it would be:
|
116
|
+
|
117
|
+
KEY_RATE=SLOW irb -rubygems -rax_elements
|
118
|
+
KEY_RATE=0.25 rspec gui_spec.rb
|
119
|
+
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'mkmf'
|
2
|
+
|
3
|
+
$CFLAGS << ' -std=c99 -Wall -Werror -ObjC'
|
4
|
+
$LIBS << ' -framework Cocoa -framework Carbon -framework ApplicationServices'
|
5
|
+
|
6
|
+
if RUBY_ENGINE == 'macruby'
|
7
|
+
$CFLAGS << ' -fobjc-gc'
|
8
|
+
else
|
9
|
+
$CFLAGS << ' -DNOT_MACRUBY -fblocks'
|
10
|
+
end
|
11
|
+
|
12
|
+
create_makefile('accessibility/key_coder')
|
@@ -0,0 +1,87 @@
|
|
1
|
+
/*
|
2
|
+
* key_coder.c
|
3
|
+
* KeyCoder
|
4
|
+
*
|
5
|
+
* Created by Mark Rada on 11-07-27.
|
6
|
+
* Copyright 2011 Marketcircle Incorporated. All rights reserved.
|
7
|
+
*/
|
8
|
+
|
9
|
+
|
10
|
+
#import <Cocoa/Cocoa.h>
|
11
|
+
#import <Carbon/Carbon.h>
|
12
|
+
#import <ApplicationServices/ApplicationServices.h>
|
13
|
+
#include "ruby.h"
|
14
|
+
|
15
|
+
static VALUE
|
16
|
+
rb_keycoder_dynamic_mapping()
|
17
|
+
{
|
18
|
+
|
19
|
+
VALUE map = rb_hash_new();
|
20
|
+
|
21
|
+
#ifdef NOT_MACRUBY
|
22
|
+
@autoreleasepool {
|
23
|
+
#endif
|
24
|
+
|
25
|
+
TISInputSourceRef keyboard = TISCopyCurrentKeyboardLayoutInputSource();
|
26
|
+
CFDataRef layout_data = (CFDataRef)TISGetInputSourceProperty(keyboard, kTISPropertyUnicodeKeyLayoutData);
|
27
|
+
const UCKeyboardLayout* layout = (const UCKeyboardLayout*)CFDataGetBytePtr(layout_data);
|
28
|
+
|
29
|
+
void (^key_coder)(int) = ^(int key_code) {
|
30
|
+
UniChar string[255];
|
31
|
+
UniCharCount string_length = 0;
|
32
|
+
UInt32 dead_key_state = 0;
|
33
|
+
UCKeyTranslate(
|
34
|
+
layout,
|
35
|
+
key_code,
|
36
|
+
kUCKeyActionDown,
|
37
|
+
0,
|
38
|
+
LMGetKbdType(), // kb type
|
39
|
+
0, // OptionBits keyTranslateOptions,
|
40
|
+
&dead_key_state,
|
41
|
+
255,
|
42
|
+
&string_length,
|
43
|
+
string
|
44
|
+
);
|
45
|
+
|
46
|
+
NSString* nsstring = [NSString stringWithCharacters:string length:string_length];
|
47
|
+
rb_hash_aset(map, rb_str_new_cstr([nsstring UTF8String]), INT2FIX(key_code));
|
48
|
+
};
|
49
|
+
|
50
|
+
// skip 65-92 since they are hard coded and do not change
|
51
|
+
for (int key_code = 0; key_code < 65; key_code++)
|
52
|
+
key_coder(key_code);
|
53
|
+
for (int key_code = 93; key_code < 127; key_code++)
|
54
|
+
key_coder(key_code);
|
55
|
+
|
56
|
+
#ifdef NOT_MACRUBY
|
57
|
+
CFRelease(keyboard);
|
58
|
+
}; // Close the autorelease pool
|
59
|
+
#else
|
60
|
+
CFMakeCollectable(keyboard);
|
61
|
+
#endif
|
62
|
+
|
63
|
+
return map;
|
64
|
+
}
|
65
|
+
|
66
|
+
|
67
|
+
static VALUE
|
68
|
+
rb_keycoder_post_event(VALUE self, VALUE event)
|
69
|
+
{
|
70
|
+
VALUE code = rb_ary_entry(event, 0);
|
71
|
+
VALUE state = rb_ary_entry(event, 1);
|
72
|
+
|
73
|
+
CGEventRef event_ref = CGEventCreateKeyboardEvent(NULL, FIX2LONG(code), state);
|
74
|
+
CGEventPost(kCGHIDEventTap, event_ref);
|
75
|
+
|
76
|
+
usleep(9000);
|
77
|
+
return Qtrue;
|
78
|
+
}
|
79
|
+
|
80
|
+
|
81
|
+
void
|
82
|
+
Init_key_coder()
|
83
|
+
{
|
84
|
+
VALUE rb_cKeyCoder = rb_define_class("KeyCoder", rb_cObject);
|
85
|
+
rb_define_singleton_method(rb_cKeyCoder, "dynamic_mapping", rb_keycoder_dynamic_mapping, 0);
|
86
|
+
rb_define_singleton_method(rb_cKeyCoder, "post_event", rb_keycoder_post_event, 1);
|
87
|
+
}
|
@@ -0,0 +1,488 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'accessibility/version'
|
4
|
+
require 'accessibility/key_coder'
|
5
|
+
framework 'ApplicationServices' if defined? MACRUBY_VERSION
|
6
|
+
|
7
|
+
##
|
8
|
+
# Parses strings of human readable text into a series of events meant to
|
9
|
+
# be processed by {Accessibility::Core#post:to:} or {KeyCoder.post_event}.
|
10
|
+
#
|
11
|
+
# Supports most, if not all, latin keyboard layouts, maybe some
|
12
|
+
# international layouts as well.
|
13
|
+
module Accessibility::String
|
14
|
+
|
15
|
+
##
|
16
|
+
# Generate keyboard events for the given string. Strings should be in a
|
17
|
+
# human readable with a few exceptions. Command key (e.g. control, option,
|
18
|
+
# command) should be written in string as they appear in
|
19
|
+
# {Accessibility::String::EventGenerator::CUSTOM}.
|
20
|
+
#
|
21
|
+
# For more details on event generation, read the
|
22
|
+
# {file:docs/KeyboardEvents.markdown Keyboard Events} documentation.
|
23
|
+
#
|
24
|
+
# @param [String]
|
25
|
+
# @return [Array<Array(Fixnum,Boolean)>]
|
26
|
+
def keyboard_events_for string
|
27
|
+
EventGenerator.new(Lexer.new(string).lex).generate
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# Tokenizer for strings. This class will take a string and break
|
32
|
+
# it up into chunks for the event generator. The structure generated
|
33
|
+
# here is an array that contains strings and recursively other arrays
|
34
|
+
# of strings and arrays of strings.
|
35
|
+
#
|
36
|
+
# @example
|
37
|
+
#
|
38
|
+
# Lexer.new("Hai").lex # => ['H','a','i']
|
39
|
+
# Lexer.new("\\CAPSLOCK").lex # => [["\\CAPSLOCK"]]
|
40
|
+
# Lexer.new("\\COMMAND+a").lex # => [["\\COMMAND", ['a']]]
|
41
|
+
# Lexer.new("One\nTwo").lex # => ['O','n','e',"\n",'T','w','o']
|
42
|
+
#
|
43
|
+
class Lexer
|
44
|
+
|
45
|
+
##
|
46
|
+
# Once a string is lexed, this contains the tokenized structure.
|
47
|
+
#
|
48
|
+
# @return [Array<String,Array<String,...>]
|
49
|
+
attr_accessor :tokens
|
50
|
+
|
51
|
+
# @param [#to_s]
|
52
|
+
def initialize string
|
53
|
+
@chars = string.to_s
|
54
|
+
@tokens = []
|
55
|
+
end
|
56
|
+
|
57
|
+
##
|
58
|
+
# Tokenize the string that the lexer was initialized with and
|
59
|
+
# return the sequence of tokens that were lexed.
|
60
|
+
#
|
61
|
+
# @return [Array<String,Array<String,...>]
|
62
|
+
def lex
|
63
|
+
length = @chars.length
|
64
|
+
@index = 0
|
65
|
+
while @index < length
|
66
|
+
@tokens << if custom?
|
67
|
+
lex_custom
|
68
|
+
else
|
69
|
+
lex_char
|
70
|
+
end
|
71
|
+
@index += 1
|
72
|
+
end
|
73
|
+
@tokens
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
##
|
80
|
+
# Is it a real custom escape? Kind of a lie, there is one
|
81
|
+
# case it does not handle--they get handled in the generator,
|
82
|
+
# but maybe they should be handled here?
|
83
|
+
# - An upper case letter or symbol following `"\\"` that is
|
84
|
+
# not mapped
|
85
|
+
def custom?
|
86
|
+
@chars[@index] == CUSTOM_ESCAPE &&
|
87
|
+
(next_char = @chars[@index+1]) &&
|
88
|
+
next_char == next_char.upcase &&
|
89
|
+
next_char != SPACE
|
90
|
+
end
|
91
|
+
|
92
|
+
# @return [Array]
|
93
|
+
def lex_custom
|
94
|
+
start = @index
|
95
|
+
loop do
|
96
|
+
char = @chars[@index]
|
97
|
+
if char == PLUS
|
98
|
+
if @chars[@index-1] == CUSTOM_ESCAPE
|
99
|
+
@index += 1
|
100
|
+
return custom_subseq start
|
101
|
+
else
|
102
|
+
tokens = custom_subseq start
|
103
|
+
@index += 1
|
104
|
+
return tokens << lex_custom
|
105
|
+
end
|
106
|
+
elsif char == SPACE
|
107
|
+
return custom_subseq start
|
108
|
+
elsif char == nil
|
109
|
+
raise ArgumentError, "Bad escape sequence" if start == @index
|
110
|
+
return custom_subseq start
|
111
|
+
else
|
112
|
+
@index += 1
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# @return [Array]
|
118
|
+
def custom_subseq start
|
119
|
+
[@chars[start...@index]]
|
120
|
+
end
|
121
|
+
|
122
|
+
# @return [String]
|
123
|
+
def lex_char
|
124
|
+
@chars[@index]
|
125
|
+
end
|
126
|
+
|
127
|
+
# @private
|
128
|
+
SPACE = " "
|
129
|
+
# @private
|
130
|
+
PLUS = "+"
|
131
|
+
# @private
|
132
|
+
CUSTOM_ESCAPE = "\\"
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
##
|
137
|
+
# @todo Add a method to generate just keydown or just keyup events.
|
138
|
+
# Requires separating code lookup from event creation.
|
139
|
+
#
|
140
|
+
# Generate a sequence of keyboard events given a sequence of tokens.
|
141
|
+
# The token format is defined by the {Lexer} class output; it is best
|
142
|
+
# to use that class to generate the tokens.
|
143
|
+
#
|
144
|
+
# @example
|
145
|
+
#
|
146
|
+
# # Upper case 'A'
|
147
|
+
# EventGenerator.new(["A"]).generate # => [[56,true],[70,true],[70,false],[56,false]]
|
148
|
+
#
|
149
|
+
# # Press the caps lock button, turn it on
|
150
|
+
# EventGenerator.new([["\\CAPS"]]).generate # => [[0x39,true],[0x39,false]]
|
151
|
+
#
|
152
|
+
# # Hotkey, press and hold command key and then 'a', then release both
|
153
|
+
# EventGenerator.new([["\\CMD",["a"]]]).generate # => [[55,true],[70,true],[70,false],[55,false]]
|
154
|
+
#
|
155
|
+
# # Press the return/enter key
|
156
|
+
# EventGenerator.new(["\n"]).generate # => [[10,true],[10,false]]
|
157
|
+
#
|
158
|
+
class EventGenerator
|
159
|
+
|
160
|
+
##
|
161
|
+
# Regenerate the portion of the key mapping that is set dynamically
|
162
|
+
# based on keyboard layout (e.g. US, Dvorak, etc.).
|
163
|
+
#
|
164
|
+
# This method should be called whenever the keyboard layout changes.
|
165
|
+
# This can be called automatically by registering for a notification
|
166
|
+
# in a run looped environment.
|
167
|
+
def self.regenerate_dynamic_mapping
|
168
|
+
# KeyCoder is declared in the Objective-C extension
|
169
|
+
MAPPING.merge! KeyCoder.dynamic_mapping
|
170
|
+
# Also add an alias to the mapping
|
171
|
+
MAPPING["\n"] = MAPPING["\r"]
|
172
|
+
end
|
173
|
+
|
174
|
+
##
|
175
|
+
# Dynamic mapping of characters to keycodes. The map is generated at
|
176
|
+
# startup time in order to support multiple keyboard layouts.
|
177
|
+
#
|
178
|
+
# @return [Hash{String=>Fixnum}]
|
179
|
+
MAPPING = {}
|
180
|
+
|
181
|
+
# Initialize the table
|
182
|
+
regenerate_dynamic_mapping
|
183
|
+
|
184
|
+
##
|
185
|
+
# @note These mappings are all static and come from `/System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Headers/Events.h`
|
186
|
+
#
|
187
|
+
# Map of custom escape sequences to their hardcoded keycode value.
|
188
|
+
#
|
189
|
+
# @return [Hash{String=>Fixnum}]
|
190
|
+
CUSTOM = {
|
191
|
+
"\\ESCAPE" => 0x35,
|
192
|
+
"\\ESC" => 0x35,
|
193
|
+
"\\COMMAND" => 0x37,
|
194
|
+
"\\CMD" => 0x37,
|
195
|
+
"\\SHIFT" => 0x38,
|
196
|
+
"\\LSHIFT" => 0x38,
|
197
|
+
"\\CAPS" => 0x39,
|
198
|
+
"\\CAPSLOCK" => 0x39,
|
199
|
+
"\\OPTION" => 0x3A,
|
200
|
+
"\\OPT" => 0x3A,
|
201
|
+
"\\ALT" => 0x3A,
|
202
|
+
"\\CONTROL" => 0x3B,
|
203
|
+
"\\CTRL" => 0x3B,
|
204
|
+
"\\RSHIFT" => 0x3C,
|
205
|
+
"\\ROPTION" => 0x3D,
|
206
|
+
"\\ROPT" => 0x3D,
|
207
|
+
"\\RALT" => 0x3D,
|
208
|
+
"\\RCONTROL" => 0x3E,
|
209
|
+
"\\RCTRL" => 0x3E,
|
210
|
+
"\\FUNCTION" => 0x3F,
|
211
|
+
"\\FN" => 0x3F,
|
212
|
+
"\\VOLUMEUP" => 0x48,
|
213
|
+
"\\VOLUP" => 0x48,
|
214
|
+
"\\VOLUMEDOWN" => 0x49,
|
215
|
+
"\\VOLDOWN" => 0x49,
|
216
|
+
"\\MUTE" => 0x4A,
|
217
|
+
"\\F1" => 0x7A,
|
218
|
+
"\\F2" => 0x78,
|
219
|
+
"\\F3" => 0x63,
|
220
|
+
"\\F4" => 0x76,
|
221
|
+
"\\F5" => 0x60,
|
222
|
+
"\\F6" => 0x61,
|
223
|
+
"\\F7" => 0x62,
|
224
|
+
"\\F8" => 0x64,
|
225
|
+
"\\F9" => 0x65,
|
226
|
+
"\\F10" => 0x6D,
|
227
|
+
"\\F11" => 0x67,
|
228
|
+
"\\F12" => 0x6F,
|
229
|
+
"\\F13" => 0x69,
|
230
|
+
"\\F14" => 0x6B,
|
231
|
+
"\\F15" => 0x71,
|
232
|
+
"\\F16" => 0x6A,
|
233
|
+
"\\F17" => 0x40,
|
234
|
+
"\\F18" => 0x4F,
|
235
|
+
"\\F19" => 0x50,
|
236
|
+
"\\F20" => 0x5A,
|
237
|
+
"\\HELP" => 0x72,
|
238
|
+
"\\HOME" => 0x73,
|
239
|
+
"\\END" => 0x77,
|
240
|
+
"\\PAGEUP" => 0x74,
|
241
|
+
"\\PAGEDOWN" => 0x79,
|
242
|
+
"\\DELETE" => 0x75,
|
243
|
+
"\\LEFT" => 0x7B,
|
244
|
+
"\\<-" => 0x7B,
|
245
|
+
"\\RIGHT" => 0x7C,
|
246
|
+
"\\->" => 0x7C,
|
247
|
+
"\\DOWN" => 0x7D,
|
248
|
+
"\\UP" => 0x7E,
|
249
|
+
"\\0" => 0x52,
|
250
|
+
"\\1" => 0x53,
|
251
|
+
"\\2" => 0x54,
|
252
|
+
"\\3" => 0x55,
|
253
|
+
"\\4" => 0x56,
|
254
|
+
"\\5" => 0x57,
|
255
|
+
"\\6" => 0x58,
|
256
|
+
"\\7" => 0x59,
|
257
|
+
"\\8" => 0x5B,
|
258
|
+
"\\9" => 0x5C,
|
259
|
+
"\\Decimal" => 0x41,
|
260
|
+
"\\." => 0x41,
|
261
|
+
"\\Plus" => 0x45,
|
262
|
+
"\\+" => 0x45,
|
263
|
+
"\\Multiply" => 0x43,
|
264
|
+
"\\*" => 0x43,
|
265
|
+
"\\Minus" => 0x4E,
|
266
|
+
"\\-" => 0x4E,
|
267
|
+
"\\Divide" => 0x4B,
|
268
|
+
"\\/" => 0x4B,
|
269
|
+
"\\Equals" => 0x51,
|
270
|
+
"\\=" => 0x51,
|
271
|
+
"\\Enter" => 0x4C,
|
272
|
+
"\\Clear" => 0x47,
|
273
|
+
}
|
274
|
+
|
275
|
+
##
|
276
|
+
# Mapping of shifted (characters written when holding shift) characters
|
277
|
+
# to keycodes.
|
278
|
+
#
|
279
|
+
# @return [Hash{String=>Fixnum}]
|
280
|
+
SHIFTED = {
|
281
|
+
'~' => '`',
|
282
|
+
'!' => '1',
|
283
|
+
'@' => '2',
|
284
|
+
'#' => '3',
|
285
|
+
'$' => '4',
|
286
|
+
'%' => '5',
|
287
|
+
'^' => '6',
|
288
|
+
'&' => '7',
|
289
|
+
'*' => '8',
|
290
|
+
'(' => '9',
|
291
|
+
')' => '0',
|
292
|
+
'{' => '[',
|
293
|
+
'}' => ']',
|
294
|
+
'?' => '/',
|
295
|
+
'+' => '=',
|
296
|
+
'|' => "\\",
|
297
|
+
':' => ';',
|
298
|
+
'_' => '-',
|
299
|
+
'"' => "'",
|
300
|
+
'<' => ',',
|
301
|
+
'>' => '.',
|
302
|
+
'A' => 'a',
|
303
|
+
'B' => 'b',
|
304
|
+
'C' => 'c',
|
305
|
+
'D' => 'd',
|
306
|
+
'E' => 'e',
|
307
|
+
'F' => 'f',
|
308
|
+
'G' => 'g',
|
309
|
+
'H' => 'h',
|
310
|
+
'I' => 'i',
|
311
|
+
'J' => 'j',
|
312
|
+
'K' => 'k',
|
313
|
+
'L' => 'l',
|
314
|
+
'M' => 'm',
|
315
|
+
'N' => 'n',
|
316
|
+
'O' => 'o',
|
317
|
+
'P' => 'p',
|
318
|
+
'Q' => 'q',
|
319
|
+
'R' => 'r',
|
320
|
+
'S' => 's',
|
321
|
+
'T' => 't',
|
322
|
+
'U' => 'u',
|
323
|
+
'V' => 'v',
|
324
|
+
'W' => 'w',
|
325
|
+
'X' => 'x',
|
326
|
+
'Y' => 'y',
|
327
|
+
'Z' => 'z',
|
328
|
+
}
|
329
|
+
|
330
|
+
##
|
331
|
+
# Mapping of optioned (characters written when holding option/alt)
|
332
|
+
# characters to keycodes.
|
333
|
+
#
|
334
|
+
# @return [Hash{String=>Fixnum}]
|
335
|
+
OPTIONED = {
|
336
|
+
'¡' => '1',
|
337
|
+
'™' => '2',
|
338
|
+
'£' => '3',
|
339
|
+
'¢' => '4',
|
340
|
+
'∞' => '5',
|
341
|
+
'§' => '6',
|
342
|
+
'¶' => '7',
|
343
|
+
'•' => '8',
|
344
|
+
'ª' => '9',
|
345
|
+
'º' => '0',
|
346
|
+
'“' => '[',
|
347
|
+
'‘' => ']',
|
348
|
+
'æ' => "'",
|
349
|
+
'≤' => ',',
|
350
|
+
'≥' => '.',
|
351
|
+
'π' => 'p',
|
352
|
+
'¥' => 'y',
|
353
|
+
'ƒ' => 'f',
|
354
|
+
'©' => 'g',
|
355
|
+
'®' => 'r',
|
356
|
+
'¬' => 'l',
|
357
|
+
'÷' => '/',
|
358
|
+
'≠' => '=',
|
359
|
+
'«' => "\\",
|
360
|
+
'å' => 'a',
|
361
|
+
'ø' => 'o',
|
362
|
+
'´' => 'e',
|
363
|
+
'¨' => 'u',
|
364
|
+
'ˆ' => 'i',
|
365
|
+
'∂' => 'd',
|
366
|
+
'˙' => 'h',
|
367
|
+
'†' => 't',
|
368
|
+
'˜' => 'n',
|
369
|
+
'ß' => 's',
|
370
|
+
'–' => '-',
|
371
|
+
'…' => ';',
|
372
|
+
'œ' => 'q',
|
373
|
+
'∆' => 'j',
|
374
|
+
'˚' => 'k',
|
375
|
+
'≈' => 'x',
|
376
|
+
'∫' => 'b',
|
377
|
+
'µ' => 'm',
|
378
|
+
'∑' => 'w',
|
379
|
+
'√' => 'v',
|
380
|
+
'Ω' => 'z',
|
381
|
+
}
|
382
|
+
|
383
|
+
|
384
|
+
##
|
385
|
+
# Once {generate} is called, this contains the sequence of
|
386
|
+
# events.
|
387
|
+
#
|
388
|
+
# @return [Array<Array(Fixnum,Boolean)>]
|
389
|
+
attr_reader :events
|
390
|
+
|
391
|
+
# @param [Array<String,Array<String,Array...>>]
|
392
|
+
def initialize tokens
|
393
|
+
@tokens = tokens
|
394
|
+
# *3 since the output array will be at least *2 the
|
395
|
+
# number of tokens passed in, but will often be larger
|
396
|
+
# due to shifted/optioned characters and custom escapes;
|
397
|
+
# though a better number could be derived from
|
398
|
+
# analyzing common input...
|
399
|
+
@events = Array.new tokens.size*3
|
400
|
+
end
|
401
|
+
|
402
|
+
##
|
403
|
+
# Generate the events for the tokens the event generator
|
404
|
+
# was initialized with. Returns the generated events.
|
405
|
+
#
|
406
|
+
# @return [Array<Array(Fixnum,Boolean)>]
|
407
|
+
def generate
|
408
|
+
@index = 0
|
409
|
+
gen_all @tokens
|
410
|
+
@events.compact!
|
411
|
+
@events
|
412
|
+
end
|
413
|
+
|
414
|
+
|
415
|
+
private
|
416
|
+
|
417
|
+
def add event
|
418
|
+
@events[@index] = event
|
419
|
+
@index += 1
|
420
|
+
end
|
421
|
+
|
422
|
+
def gen_all tokens
|
423
|
+
tokens.each do |token|
|
424
|
+
if token.kind_of? Array
|
425
|
+
gen_nested token.first, token[1..-1]
|
426
|
+
else
|
427
|
+
gen_single token
|
428
|
+
end
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
def gen_nested head, tail
|
433
|
+
if code = CUSTOM[head] || SHIFTED[head] || OPTIONED[head] || MAPPING[head]
|
434
|
+
add [code, true]
|
435
|
+
gen_all tail
|
436
|
+
add [code, false]
|
437
|
+
else # handling a special case
|
438
|
+
gen_all head.split(EMPTY_STRING)
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
442
|
+
def gen_single token
|
443
|
+
((code = MAPPING[token]) && gen_dynamic(code)) ||
|
444
|
+
((code = SHIFTED[token]) && gen_shifted(code)) ||
|
445
|
+
((code = OPTIONED[token]) && gen_optioned(code)) ||
|
446
|
+
raise(ArgumentError, "#{token.inspect} has no mapping, bail!")
|
447
|
+
end
|
448
|
+
|
449
|
+
def gen_shifted code
|
450
|
+
add SHIFT_DOWN
|
451
|
+
gen_dynamic MAPPING[code]
|
452
|
+
add SHIFT_UP
|
453
|
+
end
|
454
|
+
|
455
|
+
def gen_optioned code
|
456
|
+
add OPTION_DOWN
|
457
|
+
gen_dynamic MAPPING[code]
|
458
|
+
add OPTION_UP
|
459
|
+
end
|
460
|
+
|
461
|
+
def gen_dynamic code
|
462
|
+
add [code, true]
|
463
|
+
add [code, false]
|
464
|
+
end
|
465
|
+
|
466
|
+
# @private
|
467
|
+
EMPTY_STRING = ''
|
468
|
+
# @private
|
469
|
+
OPTION_DOWN = [58, true]
|
470
|
+
# @private
|
471
|
+
OPTION_UP = [58, false]
|
472
|
+
# @private
|
473
|
+
SHIFT_DOWN = [56, true]
|
474
|
+
# @private
|
475
|
+
SHIFT_UP = [56, false]
|
476
|
+
end
|
477
|
+
|
478
|
+
end
|
479
|
+
|
480
|
+
|
481
|
+
##
|
482
|
+
# @note This will only work if a run loop is running
|
483
|
+
#
|
484
|
+
# Register to be notified if the keyboard layout changes at runtime
|
485
|
+
# NSDistributedNotificationCenter.defaultCenter.addObserver Accessibility::String::EventGenerator,
|
486
|
+
# selector: 'regenerate_dynamic_mapping',
|
487
|
+
# name: KTISNotifySelectedKeyboardInputSourceChanged,
|
488
|
+
# object: nil
|
data/test/runner.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'minitest'
|
3
|
+
require 'minitest/autorun'
|
4
|
+
|
5
|
+
# preprocessor powers, assemble!
|
6
|
+
if ENV['BENCH']
|
7
|
+
require 'minitest/benchmark'
|
8
|
+
else
|
9
|
+
require'minitest/pride'
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
class MiniTest::Unit::TestCase
|
14
|
+
|
15
|
+
# You may need this to help track down an issue if a test is crashing MacRuby
|
16
|
+
# def self.test_order
|
17
|
+
# :alpha
|
18
|
+
# end
|
19
|
+
|
20
|
+
def self.bench_range
|
21
|
+
bench_exp 100, 100_000
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,233 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'accessibility/string'
|
4
|
+
|
5
|
+
class TestAccessibilityStringLexer < MiniTest::Unit::TestCase
|
6
|
+
|
7
|
+
def lex string
|
8
|
+
Accessibility::String::Lexer.new(string).lex
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_lex_simple_string
|
12
|
+
assert_equal [], lex('')
|
13
|
+
assert_equal ['"',"J","u","s","t"," ","W","o","r","k","s",'"',"™"], lex('"Just Works"™')
|
14
|
+
assert_equal ["M","i","l","k",","," ","s","h","a","k","e","."], lex("Milk, shake.")
|
15
|
+
assert_equal ["D","B","7"], lex("DB7")
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_lex_single_custom_escape
|
19
|
+
assert_equal [["\\CMD"]], lex("\\CMD")
|
20
|
+
assert_equal [["\\1"]], lex("\\1")
|
21
|
+
assert_equal [["\\F1"]], lex("\\F1")
|
22
|
+
assert_equal [["\\*"]], lex("\\*")
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_lex_hotkey_custom_escape
|
26
|
+
assert_equal [["\\COMMAND",[","]]], lex("\\COMMAND+,")
|
27
|
+
assert_equal [["\\COMMAND",["\\SHIFT",["s"]]]], lex("\\COMMAND+\\SHIFT+s")
|
28
|
+
assert_equal [["\\COMMAND",["\\+"]]], lex("\\COMMAND+\\+")
|
29
|
+
assert_equal [["\\FN",["\\F10"]]], lex("\\FN+\\F10")
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_lex_ruby_escapes
|
33
|
+
assert_equal ["\n","\r","\t","\b"], lex("\n\r\t\b")
|
34
|
+
assert_equal ["O","n","e","\n","T","w","o"], lex("One\nTwo")
|
35
|
+
assert_equal ["L","i","e","\b","\b","\b","d","e","l","i","s","h"], lex("Lie\b\b\bdelish")
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_lex_complex_string
|
39
|
+
assert_equal ["T","e","s","t",["\\CMD",["s"]]], lex("Test\\CMD+s")
|
40
|
+
assert_equal ["Z","O","M","G"," ","1","3","3","7","!","!","1"], lex("ZOMG 1337!!1")
|
41
|
+
assert_equal ["F","u","u","!","@","#","%",["\\CMD",["a"]],"\b"], lex("Fuu!@#%\\CMD+a \b")
|
42
|
+
assert_equal [["\\CMD",["a"]],"\b","A","l","l"," ","g","o","n","e","!"], lex("\\CMD+a \bAll gone!")
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_lex_backslash # make sure we handle these edge cases predictably
|
46
|
+
assert_equal ["\\"], lex("\\")
|
47
|
+
assert_equal ["\\"," "], lex("\\ ")
|
48
|
+
assert_equal ["\\","h","m","m"], lex("\\hmm")
|
49
|
+
assert_equal [["\\HMM"]], lex("\\HMM") # the one missed case
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_lex_plus_escape
|
53
|
+
assert_equal [["\\+"]], lex("\\+")
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_lex_bad_custom_escape_sequence
|
57
|
+
assert_raises ArgumentError do
|
58
|
+
lex("\\COMMAND+")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
class TestAccessibilityStringEventGenerator < MiniTest::Unit::TestCase
|
66
|
+
|
67
|
+
def gen tokens
|
68
|
+
Accessibility::String::EventGenerator.new(tokens).generate
|
69
|
+
end
|
70
|
+
|
71
|
+
def map; @@map ||= KeyCoder.dynamic_mapping; end
|
72
|
+
|
73
|
+
def t; true; end
|
74
|
+
def f; false; end
|
75
|
+
|
76
|
+
def a; @@a ||= map['a']; end
|
77
|
+
def c; @@c ||= map['c']; end
|
78
|
+
def e; @@e ||= map['e']; end
|
79
|
+
def h; @@h ||= map['h']; end
|
80
|
+
def i; @@i ||= map['i']; end
|
81
|
+
def k; @@k ||= map['k']; end
|
82
|
+
def m; @@m ||= map["m"]; end
|
83
|
+
|
84
|
+
def two; @@two ||= map['2']; end
|
85
|
+
def four; @@four ||= map['4']; end
|
86
|
+
|
87
|
+
def retern; @@retern ||= map["\r"]; end
|
88
|
+
def tab; @@tab ||= map["\t"]; end
|
89
|
+
def space; @@space ||= map["\s"]; end
|
90
|
+
|
91
|
+
def dash; @@dash ||= map["-"]; end
|
92
|
+
def comma; @@comma ||= map[","]; end
|
93
|
+
def apos; @@apos ||= map["'"]; end
|
94
|
+
def at; @@at ||= map["2"]; end
|
95
|
+
def paren; @@paren ||= map["9"]; end
|
96
|
+
def chev; @@chev ||= map["."]; end
|
97
|
+
|
98
|
+
def sigma; @@sigma ||= map["w"]; end
|
99
|
+
def tm; @@tm ||= map["2"]; end
|
100
|
+
def gbp; @@gbp ||= map["3"]; end
|
101
|
+
def omega; @@omega ||= map["z"]; end
|
102
|
+
|
103
|
+
def bslash; @@blash ||= map["\\"]; end
|
104
|
+
|
105
|
+
# key code for the left shift key
|
106
|
+
def sd; [56,t]; end
|
107
|
+
def su; [56,f]; end
|
108
|
+
|
109
|
+
# key code for the left option key
|
110
|
+
def od; [58,t]; end
|
111
|
+
def ou; [58,f]; end
|
112
|
+
|
113
|
+
# key code for the left command key
|
114
|
+
def cd; [0x37,t]; end
|
115
|
+
def cu; [0x37,f]; end
|
116
|
+
|
117
|
+
# key code for right arrow key
|
118
|
+
def rd; [0x7c,t]; end
|
119
|
+
def ru; [0x7c,f]; end
|
120
|
+
|
121
|
+
# key code for left control key
|
122
|
+
def ctrld; [0x3B,t]; end
|
123
|
+
def ctrlu; [0x3B,f]; end
|
124
|
+
|
125
|
+
|
126
|
+
def test_generate_lowercase
|
127
|
+
assert_equal [[a,t],[a,f]], gen(['a'])
|
128
|
+
assert_equal [[c,t],[c,f],[k,t],[k,f]], gen(['c','k'])
|
129
|
+
assert_equal [[e,t],[e,f],[e,t],[e,f]], gen(['e','e'])
|
130
|
+
assert_equal [[c,t],[c,f],[a,t],[a,f],[k,t],[k,f],[e,t],[e,f]], gen(['c','a','k','e'])
|
131
|
+
end
|
132
|
+
|
133
|
+
def test_generate_uppercase
|
134
|
+
assert_equal [sd,[a,t],[a,f],su], gen(['A'])
|
135
|
+
assert_equal [sd,[c,t],[c,f],su,sd,[k,t],[k,f],su], gen(['C','K'])
|
136
|
+
assert_equal [sd,[e,t],[e,f],su,sd,[e,t],[e,f],su], gen(['E','E'])
|
137
|
+
assert_equal [sd,[c,t],[c,f],su,sd,[a,t],[a,f],su,sd,[k,t],[k,f],su], gen(['C','A','K'])
|
138
|
+
end
|
139
|
+
|
140
|
+
def test_generate_numbers
|
141
|
+
assert_equal [[two,t],[two,f]], gen(['2'])
|
142
|
+
assert_equal [[four,t],[four,f],[two,t],[two,f]], gen(['4','2'])
|
143
|
+
assert_equal [[two,t],[two,f],[two,t],[two,f]], gen(['2','2'])
|
144
|
+
end
|
145
|
+
|
146
|
+
def test_generate_ruby_escapes
|
147
|
+
assert_equal [[retern,t],[retern,f]], gen(["\r"])
|
148
|
+
assert_equal [[retern,t],[retern,f]], gen(["\n"])
|
149
|
+
assert_equal [[tab,t],[tab,f]], gen(["\t"])
|
150
|
+
assert_equal [[space,t],[space,f]], gen(["\s"])
|
151
|
+
assert_equal [[space,t],[space,f]], gen([" "])
|
152
|
+
end
|
153
|
+
|
154
|
+
def test_generate_symbols
|
155
|
+
assert_equal [[dash,t],[dash,f]], gen(["-"])
|
156
|
+
assert_equal [[comma,t],[comma,f]], gen([","])
|
157
|
+
assert_equal [[apos,t],[apos,f]], gen(["'"])
|
158
|
+
assert_equal [sd,[at,t],[at,f],su], gen(["@"])
|
159
|
+
assert_equal [sd,[paren,t],[paren,f],su], gen(["("])
|
160
|
+
assert_equal [sd,[chev,t],[chev,f],su], gen([">"])
|
161
|
+
end
|
162
|
+
|
163
|
+
def test_generate_unicode # holding option
|
164
|
+
assert_equal [od,[sigma,t],[sigma,f],ou], gen(["∑"])
|
165
|
+
assert_equal [od,[tm,t],[tm,f],ou], gen(["™"])
|
166
|
+
assert_equal [od,[gbp,t],[gbp,f],ou], gen(["£"])
|
167
|
+
assert_equal [od,[omega,t],[omega,f],ou], gen(["Ω"])
|
168
|
+
end
|
169
|
+
|
170
|
+
def test_generate_backslashes
|
171
|
+
assert_equal [[bslash,t],[bslash,f]], gen(["\\"])
|
172
|
+
assert_equal [[bslash,t],[bslash,f],[space,t],[space,f]], gen(["\\"," "])
|
173
|
+
assert_equal [[bslash,t],[bslash,f],[h,t],[h,f],[m,t],[m,f]], gen(["\\",'h','m'])
|
174
|
+
# is this the job of the parser or the lexer?
|
175
|
+
assert_equal [[bslash,t],[bslash,f],sd,[h,t],[h,f],su,sd,[m,t],[m,f],su], gen([["\\HM"]])
|
176
|
+
end
|
177
|
+
|
178
|
+
def test_generate_a_custom_escape
|
179
|
+
assert_equal [cd,cu], gen([["\\COMMAND"]])
|
180
|
+
assert_equal [cd,cu], gen([["\\CMD"]])
|
181
|
+
assert_equal [ctrld,ctrlu], gen([["\\CONTROL"]])
|
182
|
+
assert_equal [ctrld,ctrlu], gen([["\\CTRL"]])
|
183
|
+
end
|
184
|
+
|
185
|
+
def test_generate_hotkey
|
186
|
+
assert_equal [ctrld,[a,t],[a,f],ctrlu], gen([["\\CONTROL",["a"]]])
|
187
|
+
assert_equal [cd,sd,rd,ru,su,cu], gen([["\\COMMAND",['\SHIFT',['\->']]]])
|
188
|
+
end
|
189
|
+
|
190
|
+
def test_generate_real_use # a regression
|
191
|
+
assert_equal [ctrld,[a,t],[a,f],ctrlu,[h,t],[h,f]], gen([["\\CTRL",["a"]],"h"])
|
192
|
+
end
|
193
|
+
|
194
|
+
def test_bails_for_unmapped_token
|
195
|
+
e = assert_raises ArgumentError do
|
196
|
+
gen(["☃"]) # cannot generate snowmen :(
|
197
|
+
end
|
198
|
+
assert_match /bail/i, e.message
|
199
|
+
end
|
200
|
+
|
201
|
+
def test_generate_arbitrary_nested_array_sequence
|
202
|
+
assert_equal [[c,t],[a,t],[k,t],[e,t],[e,f],[k,f],[a,f],[c,f]], gen([["c",["a",["k",["e"]]]]])
|
203
|
+
end
|
204
|
+
|
205
|
+
end
|
206
|
+
|
207
|
+
|
208
|
+
# NOTE: DO NOT TEST POSTING EVENTS HERE
|
209
|
+
# We only want to test posting events if all the tests in this file pass,
|
210
|
+
# otherwise the posted events may be unpredictable depending on what fails.
|
211
|
+
# Test event posting in the integration tests.
|
212
|
+
class TestAccessibilityString < MiniTest::Unit::TestCase
|
213
|
+
include Accessibility::String
|
214
|
+
|
215
|
+
# basic test to make sure the lexer and generator get along
|
216
|
+
def test_keyboard_events_for
|
217
|
+
events = keyboard_events_for 'cheezburger'
|
218
|
+
assert_kind_of Array, events
|
219
|
+
refute_empty events
|
220
|
+
|
221
|
+
assert_equal true, events[0][1]
|
222
|
+
assert_equal false, events[1][1]
|
223
|
+
end
|
224
|
+
|
225
|
+
def test_dynamic_map_initialized
|
226
|
+
refute_empty Accessibility::String::EventGenerator::MAPPING
|
227
|
+
end
|
228
|
+
|
229
|
+
def test_can_parse_empty_string
|
230
|
+
assert_equal [], keyboard_events_for('')
|
231
|
+
end
|
232
|
+
|
233
|
+
end
|
metadata
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: AXTyper
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.7.0
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Mark Rada
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-03-29 00:00:00 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: minitest
|
16
|
+
prerelease: false
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ~>
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: "2.11"
|
23
|
+
type: :development
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: "2.11"
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: yard
|
32
|
+
prerelease: false
|
33
|
+
requirement: !ruby/object:Gem::Requirement
|
34
|
+
none: false
|
35
|
+
requirements:
|
36
|
+
- - ~>
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: 0.7.5
|
39
|
+
type: :development
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.7.5
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: redcarpet
|
48
|
+
prerelease: false
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: "1.17"
|
55
|
+
type: :development
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: "1.17"
|
62
|
+
description: 'Simulate keyboard input via the Mac OS X Accessibility Framework. This
|
63
|
+
|
64
|
+
gem is a component of AXElements.
|
65
|
+
|
66
|
+
'
|
67
|
+
email: mrada@marketcircle.com
|
68
|
+
executables: []
|
69
|
+
extensions:
|
70
|
+
- ext/accessibility/key_coder/extconf.rb
|
71
|
+
extra_rdoc_files:
|
72
|
+
- README.markdown.typer
|
73
|
+
- .yardopts.typer
|
74
|
+
- docs/KeyboardEvents.markdown
|
75
|
+
files:
|
76
|
+
- lib/accessibility/version.rb
|
77
|
+
- lib/accessibility/string.rb
|
78
|
+
- ext/accessibility/key_coder/key_coder.c
|
79
|
+
- ext/accessibility/key_coder/extconf.rb
|
80
|
+
- test/unit/accessibility/test_string.rb
|
81
|
+
- test/runner.rb
|
82
|
+
- README.markdown.typer
|
83
|
+
- .yardopts.typer
|
84
|
+
- docs/KeyboardEvents.markdown
|
85
|
+
homepage: http://github.com/Marketcircle/AXElements
|
86
|
+
licenses:
|
87
|
+
- BSD 3-clause
|
88
|
+
post_install_message:
|
89
|
+
rdoc_options: []
|
90
|
+
require_paths:
|
91
|
+
- lib
|
92
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
93
|
+
none: false
|
94
|
+
requirements:
|
95
|
+
- - '>='
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: "0"
|
98
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
99
|
+
none: false
|
100
|
+
requirements:
|
101
|
+
- - '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: "0"
|
104
|
+
requirements: []
|
105
|
+
rubyforge_project:
|
106
|
+
rubygems_version: 1.8.20
|
107
|
+
signing_key:
|
108
|
+
specification_version: 3
|
109
|
+
summary: Keyboard simulation via accessibility
|
110
|
+
test_files:
|
111
|
+
- test/unit/accessibility/test_string.rb
|
112
|
+
- test/runner.rb
|