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