├── .gitignore
├── _vm_setup.py
├── voxhub_config.py
├── tformat.py
├── _all.py
├── programs.py
├── words.py
├── _aenea.py
└── keyboard.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 |
--------------------------------------------------------------------------------
/_vm_setup.py:
--------------------------------------------------------------------------------
1 | # commands for controlling various programs
2 |
3 | from aenea import *
4 |
5 | def copyGrammars():
6 | import os
7 | os.system("copy C:\\grammar\\*.py C:\\Natlink\\Natlink\\MacroSystem");
8 | print "Done copying, reloading grammar..."
9 |
10 | from _aenea import reload_code
11 | reload_code()
12 |
13 | class VMSetupRule(MappingRule):
14 | mapping = {
15 | "copy in new grammars": Function(copyGrammars)
16 | }
17 |
18 | grammar = dragonfly.Grammar('vm_setup')
19 |
20 | grammar.add_rule(VMSetupRule())
21 |
22 | grammar.load()
23 |
24 |
25 | # Unload function which will be called at unload time.
26 | def unload():
27 | global grammar
28 | if grammar:
29 | grammar.unload()
30 | grammar = None
31 |
--------------------------------------------------------------------------------
/voxhub_config.py:
--------------------------------------------------------------------------------
1 | # This Python file uses the following encoding: utf-8
2 |
3 | """
4 | VoxhubEngine engine configuration module.
5 | """
6 |
7 | # Speech-recognition server (Default: localhost)
8 | SERVER = "silvius-server.voxhub.io"
9 |
10 | # Server port
11 | PORT = "8018"
12 |
13 | # Use the specified content type (default is " + content_type + ")"
14 | CONTENT_TYPE = "audio/x-raw, layout=(string)interleaved, rate=(int)16000, format=(string)S16LE, channels=(int)1"
15 |
16 | # Default path to access in server
17 | PATH = 'client/ws/speech'
18 |
19 | MISC_CONFIG = {
20 | 'device': 9, # Select a different microphone (give device ID)
21 | 'keep_going': False, # Keep reconnecting to the server after periods of silence
22 | 'hypotheses': True, # Show partial recognition hypotheses (default: True)
23 | 'audio_gate': 0, # Audio-gate level to reduce detections when not talking
24 | 'byte_rate': 16000, # Rate in bytes/sec at which audio should be sent to the server.
25 | 'send_adaptation_state': None, # Send adaptation state from file
26 | 'save_adaptation_state': None, # Save adaptation state to file
27 | 'chunk': 2048 * 2 # Try adjusting this if you want fewer network packets
28 | }
29 |
30 | QUIT_PHRASE = "rate"
31 | SLEEP_PHRASE = "go to sleep"
32 | WAKE_UP_PHRASE = "wake up"
33 |
34 |
--------------------------------------------------------------------------------
/tformat.py:
--------------------------------------------------------------------------------
1 | # This file is part of Aenea
2 | #
3 | # Aenea is free software: you can redistribute it and/or modify it under
4 | # the terms of version 3 of the GNU Lesser General Public License as
5 | # published by the Free Software Foundation.
6 | #
7 | # Aenea is distributed in the hope that it will be useful, but WITHOUT
8 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
9 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
10 | # License for more details.
11 | #
12 | # You should have received a copy of the GNU Lesser General Public
13 | # License along with Aenea. If not, see .
14 | #
15 | # Copyright (2014) Alex Roper
16 | # Alex Roper
17 |
18 | def format_snakeword(text):
19 | formatted = text[0][0].upper()
20 | formatted += text[0][1:]
21 | formatted += ('_' if len(text) > 1 else '')
22 | formatted += format_score(text[1:])
23 | return formatted
24 |
25 |
26 | def format_score(text):
27 | return '_'.join(text)
28 |
29 |
30 | def format_camel(text):
31 | return text[0] + ''.join([word[0].upper() + word[1:] for word in text[1:]])
32 |
33 |
34 | def format_proper(text):
35 | return ''.join(word.capitalize() for word in text)
36 |
37 |
38 | def format_relpath(text):
39 | return '/'.join(text)
40 |
41 |
42 | def format_abspath(text):
43 | return '/' + format_relpath(text)
44 |
45 |
46 | def format_scoperesolve(text):
47 | return '::'.join(text)
48 |
49 |
50 | def format_jumble(text):
51 | return ''.join(text)
52 |
53 |
54 | def format_dotword(text):
55 | return '.'.join(text)
56 |
57 |
58 | def format_dashword(text):
59 | return '-'.join(text)
60 |
61 |
62 | def format_natword(text):
63 | return ' '.join(text)
64 |
65 |
66 | def format_broodingnarrative(text):
67 | return ''
68 |
69 |
70 | def format_sentence(text):
71 | return ' '.join([text[0].capitalize()] + text[1:])
72 |
--------------------------------------------------------------------------------
/_all.py:
--------------------------------------------------------------------------------
1 | # _all.py: main rule for DWK's grammar
2 |
3 | try:
4 | from aenea import *
5 | except:
6 | from dragonfly import *
7 |
8 | import keyboard
9 | import words
10 | import programs
11 |
12 | release = Key("shift:up, ctrl:up, alt:up")
13 |
14 | alternatives = []
15 | alternatives.append(RuleRef(rule=keyboard.KeystrokeRule()))
16 | alternatives.append(RuleRef(rule=words.FormatRule()))
17 | alternatives.append(RuleRef(rule=words.ReFormatRule()))
18 | alternatives.append(RuleRef(rule=words.NopeFormatRule()))
19 | alternatives.append(RuleRef(rule=words.PhraseFormatRule()))
20 | alternatives.append(RuleRef(rule=programs.ProgramsRule()))
21 | root_action = Alternative(alternatives)
22 |
23 | sequence = Repetition(root_action, min=1, max=16, name="sequence")
24 |
25 | class RepeatRule(CompoundRule):
26 | # Here we define this rule's spoken-form and special elements.
27 | spec = " [[[and] repeat [that]] times]"
28 | extras = [
29 | sequence, # Sequence of actions defined above.
30 | IntegerRef("n", 1, 100), # Times to repeat the sequence.
31 | ]
32 | defaults = {
33 | "n": 1, # Default repeat count.
34 | }
35 |
36 | def _process_recognition(self, node, extras): # @UnusedVariable
37 | sequence = extras["sequence"] # A sequence of actions.
38 | count = extras["n"] # An integer repeat count.
39 | for i in range(count): # @UnusedVariable
40 | for action in sequence:
41 | action.execute()
42 | release.execute()
43 |
44 | grammar = Grammar("root rule")
45 | grammar.add_rule(RepeatRule()) # Add the top-level rule.
46 | grammar.load() # Load the grammar.
47 |
48 | def unload():
49 | """Unload function which will be called at unload time."""
50 | global grammar
51 | if grammar:
52 | grammar.unload()
53 | grammar = None
54 |
55 | if __name__ == '__main__':
56 | engine = get_engine("voxhub")
57 | #engine.list_available_microphones()
58 | #engine.dump_grammar()
59 | engine.connect()
60 |
--------------------------------------------------------------------------------
/programs.py:
--------------------------------------------------------------------------------
1 | # commands for controlling various programs
2 |
3 | try:
4 | from aenea import *
5 | except:
6 | from dragonfly import *
7 |
8 | gitcommand_array = [
9 | 'add',
10 | 'branch',
11 | 'checkout',
12 | 'clone',
13 | 'commit',
14 | 'diff',
15 | 'fetch',
16 | 'init',
17 | 'log',
18 | 'merge',
19 | 'pull',
20 | 'push',
21 | 'rebase',
22 | 'reset',
23 | 'show',
24 | 'stash',
25 | 'status',
26 | 'tag',
27 | ]
28 | gitcommand = {}
29 | for command in gitcommand_array:
30 | gitcommand[command] = command
31 |
32 | class ProgramsRule(MappingRule):
33 | mapping = {
34 | "vim save": Key("escape, colon, w, enter"),
35 | "vim quit": Key("escape, colon, q, enter"),
36 | "vim really quit": Key("escape, colon, q, exclamation, enter"),
37 | "vim save and quit": Key("escape, colon, w, q, enter"),
38 | "vim split": Text(":sp "),
39 | "vim vertical split": Text(":vs "),
40 | "vim tab new": Text(":tabnew "),
41 | "vim tab close": Text(":tabclose\n"),
42 |
43 | "vim open source": Text(":vs %<.c\n"),
44 | "vim open source plus": Text(":vs %<.cpp\n"),
45 | "vim open header": Text(":vs %<.h\n") + Key('c-w, c-w'),
46 | "vim next [tab] []": Text(':tabnext +%(n)d\n'),
47 | "vim previous [tab] []": Text(':tabprevious %(n)d\n'),
48 | "vim (switch|toggle|swap)": Key('c-w, c-w'),
49 | "vim rotate": Key('c-w, r'),
50 | "vim try that": Key('escape, colon, w, enter, a-tab/5, up, enter'),
51 |
52 | 'screen': Key('c-a'),
53 | 'screen switch': Key('c-a, c-a'),
54 | 'screen scroll': Key('c-a, lbracket'),
55 |
56 | "just execute": Key("backspace, enter"),
57 | "command (git|get)": Text("git "),
58 | "command (git|get) ": Text("git %(gitcommand)s "),
59 | "command vim": Text("vim "),
60 | #"command C D": Text("cd "),
61 | #"command list": Text("ls "),
62 | "command make": Text("make "),
63 | "command make clean": Text("make clean "),
64 | #"command cat": Text("cat "),
65 | "command (grep|grip)": Text("grep "),
66 | #"command background": Text("bg "),
67 | #"command foreground": Text("fg "),
68 |
69 | # web browser
70 | 'address bar': Key('a-d'),
71 | 'refresh page': Key('f5'),
72 | 'really refresh page': Key('s-f5'),
73 | 'go back []': Key('a-left:%(n)d'),
74 | 'go forward []': Key('a-right:%(n)d'),
75 | 'previous tab []': Key('c-pgup:%(n)d'),
76 | 'next tab []': Key('c-pgdown:%(n)d'),
77 | 'open [new] tab': Key('c-t'),
78 | 'close tab': Key('c-w'),
79 |
80 | # Xfce-like desktop environment commands
81 | '(desk|desktop) left []': Key('ca-left:%(n)d'),
82 | '(desk|desktop) right []': Key('ca-right:%(n)d'),
83 | '(desk|desktop) up []': Key('ca-up:%(n)d'),
84 | '(desk|desktop) down []': Key('ca-down:%(n)d'),
85 | '(desk|desktop) (top|upper) []': Key('c-f1, ca-left, ca-right:%(n)d'),
86 | '(desk|desktop) (bottom|lower) []': Key('c-f1, ca-down, ca-left, ca-right:%(n)d'),
87 | 'switch window []': Key('a-tab:%(n)d'),
88 | 'really close window': Key('a-f4'),
89 | 'maximize window': Key('a-f10'),
90 | 'minimize window': Key('a-f9'),
91 | 'open new terminal': Key('ca-m'),
92 | 'copy this image': Key('c-c/10,ca-right/10,c-v/10,ca-left'),
93 | 'image go up': Key('a-up'),
94 | 'read elf': Text('readelf -Wa '),
95 | 'object dump': Text('objdump -d '),
96 | 'code standard': Text('std::'),
97 | 'code vector': Text('vector'),
98 | 'code map': Text('map'),
99 | 'code list': Text('map'),
100 | 'code string': Text('string'),
101 | 'standard string': Text('std::string'),
102 | 'standard vector': Text('std::vector'),
103 | 'standard map': Text('std::map'),
104 | 'standard list': Text('std::list'),
105 | 'const': Text('const '),
106 | }
107 | extras = [
108 | Dictation("text"),
109 | IntegerRef("n", 1, 100),
110 | Choice('gitcommand', gitcommand),
111 | ]
112 | defaults = {
113 | "n": 1,
114 | }
115 |
--------------------------------------------------------------------------------
/words.py:
--------------------------------------------------------------------------------
1 | # module for dictating words and basic sentences
2 | #
3 | # (based on the multiedit module from dragonfly-modules project)
4 | # (heavily modified)
5 | # (the original copyright notice is reproduced below)
6 | #
7 | # (c) Copyright 2008 by Christo Butcher
8 | # Licensed under the LGPL, see
9 | #
10 |
11 | try:
12 | from aenea import (Key, Text, Mimic, CompoundRule, Dictation, Pause)
13 | except:
14 | from dragonfly import (Key, Text, Mimic, CompoundRule, Dictation, Pause)
15 | import tformat
16 |
17 | lastFormatRuleLength = 0
18 | lastFormatRuleWords = []
19 | def handle_word(text):
20 | words = str(text).split()
21 | print 'word (', words, ')'
22 | if len(words) > 0:
23 | Text(words[0]).execute()
24 |
25 | global lastFormatRuleWords
26 | global lastFormatRuleLength
27 | lastFormatRuleWords = words[0:1]
28 | lastFormatRuleLength = len(words[0])
29 |
30 | if len(words) > 1:
31 | Mimic(' '.join(words[1:])).execute()
32 |
33 |
34 | class NopeFormatRule(CompoundRule):
35 | spec = ('nope')
36 |
37 | def value(self, node):
38 | global lastFormatRuleLength
39 | print "erasing previous format of length", lastFormatRuleLength
40 | return Key('backspace:' + str(lastFormatRuleLength))
41 |
42 | class ReFormatRule(CompoundRule):
43 | spec = ('that was [upper | natural] ( proper | camel | rel-path | abs-path | score | sentence | '
44 | 'scope-resolve | jumble | dotword | dashword | natword | snakeword | brooding-narrative)')
45 |
46 | def value(self, node):
47 | global lastFormatRuleWords
48 | words = lastFormatRuleWords
49 | words = node.words()[2:] + lastFormatRuleWords
50 | print words
51 |
52 | uppercase = words[0] == 'upper'
53 | lowercase = words[0] != 'natural'
54 |
55 | if lowercase:
56 | words = [word.lower() for word in words]
57 | if uppercase:
58 | words = [word.upper() for word in words]
59 |
60 | words = [word.split('\\', 1)[0].replace('-', '') for word in words]
61 | if words[0].lower() in ('upper', 'natural'):
62 | del words[0]
63 |
64 | function = getattr(tformat, 'format_%s' % words[0].lower())
65 | formatted = function(words[1:])
66 |
67 | global lastFormatRuleLength
68 | lastFormatRuleLength = len(formatted)
69 | return Text(formatted)
70 |
71 | class FormatRule(CompoundRule):
72 | spec = ('[upper | natural] ( proper | camel | rel-path | abs-path | score | sentence | '
73 | 'scope-resolve | jumble | dotword | dashword | natword | snakeword | brooding-narrative) [] [bomb]')
74 | extras = [Dictation(name='dictation')]
75 | exported = False
76 |
77 | def value(self, node):
78 | words = node.words()
79 | print "format rule:", words
80 |
81 | uppercase = words[0] == 'upper'
82 | lowercase = words[0] != 'natural'
83 |
84 | if lowercase:
85 | words = [word.lower() for word in words]
86 | if uppercase:
87 | words = [word.upper() for word in words]
88 |
89 | words = [word.split('\\', 1)[0].replace('-', '') for word in words]
90 | if words[0].lower() in ('upper', 'natural'):
91 | del words[0]
92 |
93 | bomb = None
94 | if 'bomb' in words:
95 | bomb_point = words.index('bomb')
96 | if bomb_point+1 < len(words):
97 | bomb = words[bomb_point+1 : ]
98 | words = words[ : bomb_point]
99 |
100 | function = getattr(tformat, 'format_%s' % words[0].lower())
101 | formatted = function(words[1:])
102 | global lastFormatRuleWords
103 | lastFormatRuleWords = words[1:]
104 |
105 | global lastFormatRuleLength
106 | lastFormatRuleLength = len(formatted)
107 |
108 | # empty formatted causes problems here
109 | print " ->", formatted
110 | if bomb != None:
111 | return Text(formatted) + Mimic(' '.join(bomb))
112 | else:
113 | return Text(formatted)
114 |
115 | class PhraseFormatRule(CompoundRule):
116 | spec = ('[start] [new] phrase []')
117 | extras = [Dictation(name='dictation')]
118 |
119 | def value(self, node):
120 | words = node.words()
121 | print "format rule:", words
122 |
123 | leading = (words[0] != 'start')
124 | trailing = False #(words[0] != 'end' and words[0] != 'isolated')
125 | if words[0] == 'start' or words[0] == 'isolated': words = words[1:]
126 | capitalize = (words[0] == 'new')
127 | if words[0] == 'new': words = words[1:]
128 | words = words[1:] # delete "phrase"
129 |
130 | if len(words) == 0: return Pause("0")
131 |
132 | formatted = ''
133 | addedWords = False
134 | for word in words:
135 | special = word.split('\\', 1)
136 | if(len(special) > 1 and special[1] != 'pronoun' and special[1] != 'determiner'):
137 | formatted += special[0]
138 | else:
139 | if(addedWords): formatted += ' '
140 | formatted += special[0]
141 | addedWords = True
142 | if capitalize: formatted = formatted[:1].capitalize() + formatted[1:]
143 | if leading: formatted = ' ' + formatted
144 | if trailing: formatted += ' '
145 |
146 | global lastFormatRuleWords
147 | lastFormatRuleWords = words
148 |
149 | global lastFormatRuleLength
150 | lastFormatRuleLength = len(formatted)
151 |
152 | print " ->", formatted
153 | return Text(formatted)
154 |
155 |
--------------------------------------------------------------------------------
/_aenea.py:
--------------------------------------------------------------------------------
1 | # This is a command module for Dragonfly. It provides support for several of
2 | # Aenea's built-in capabilities. This module is NOT required for Aenea to
3 | # work correctly, but it is strongly recommended.
4 |
5 | # This file is part of Aenea
6 | #
7 | # Aenea is free software: you can redistribute it and/or modify it under
8 | # the terms of version 3 of the GNU Lesser General Public License as
9 | # published by the Free Software Foundation.
10 | #
11 | # Aenea is distributed in the hope that it will be useful, but WITHOUT
12 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
14 | # License for more details.
15 | #
16 | # You should have received a copy of the GNU Lesser General Public
17 | # License along with Aenea. If not, see .
18 | #
19 | # Copyright (2014) Alex Roper
20 | # Alex Roper
21 |
22 | import os
23 | import sys
24 |
25 | import dragonfly
26 |
27 | try:
28 | # Internal NatLink module for reloading grammars.
29 | import natlinkmain
30 | except ImportError:
31 | natlinkmain = None
32 |
33 | try:
34 | import aenea
35 | import aenea.proxy_contexts
36 | import aenea.configuration
37 | import aenea.communications
38 | import aenea.config
39 | import aenea.configuration
40 | except ImportError:
41 | print 'Unable to import Aenea client-side modules.'
42 | raise
43 |
44 | print 'Aenea client-side modules loaded successfully'
45 | print 'Settings:'
46 | print '\tHOST:', aenea.config.DEFAULT_SERVER_ADDRESS[0]
47 | print '\tPORT:', aenea.config.DEFAULT_SERVER_ADDRESS[1]
48 | print '\tPLATFORM:', aenea.config.PLATFORM
49 | print '\tUSE_MULTIPLE_ACTIONS:', aenea.config.USE_MULTIPLE_ACTIONS
50 | print '\tSCREEN_RESOLUTION:', aenea.config.SCREEN_RESOLUTION
51 |
52 | try:
53 | aenea.proxy_contexts._get_context()
54 | print 'Aenea: Successfully connected to server.'
55 | except:
56 | print 'Aenea: Unable to connect to server.'
57 |
58 |
59 | # Commands that can be rebound.
60 | command_table = [
61 | 'set proxy server to ',
62 | 'disable proxy server',
63 | 'enable proxy server',
64 | 'force natlink to reload all grammars'
65 | ]
66 | command_table = aenea.configuration.make_grammar_commands(
67 | 'aenea',
68 | dict(zip(command_table, command_table))
69 | )
70 |
71 |
72 | def topy(path):
73 | if path.endswith == ".pyc":
74 | return path[:-1]
75 |
76 | return path
77 |
78 |
79 | class DisableRule(dragonfly.CompoundRule):
80 | spec = command_table['disable proxy server']
81 |
82 | def _process_recognition(self, node, extras):
83 | aenea.config.disable_proxy()
84 |
85 |
86 | class EnableRule(dragonfly.CompoundRule):
87 | spec = command_table['enable proxy server']
88 |
89 | def _process_recognition(self, node, extras):
90 | aenea.config.enable_proxy()
91 |
92 |
93 | def reload_code():
94 | # Do not reload anything in these directories or their subdirectories.
95 | dir_reload_blacklist = set(["core"])
96 | macro_dir = "C:\\NatLink\\NatLink\\MacroSystem"
97 |
98 | # Unload all grammars if natlinkmain is available.
99 | if natlinkmain:
100 | natlinkmain.unloadEverything()
101 |
102 | # Unload all modules in macro_dir except for those in directories on the
103 | # blacklist.
104 | # Consider them in sorted order to try to make things as predictable as possible to ease debugging.
105 | for name, module in sorted(sys.modules.items()):
106 | if module and hasattr(module, "__file__"):
107 | # Some builtin modules only have a name so module is None or
108 | # do not have a __file__ attribute. We skip these.
109 | path = module.__file__
110 |
111 | # Convert .pyc paths to .py paths.
112 | path = topy(path)
113 |
114 | # Do not unimport this module! This will cause major problems!
115 | if (path.startswith(macro_dir) and
116 | not bool(set(path.split(os.path.sep)) & dir_reload_blacklist)
117 | and path != topy(os.path.abspath(__file__))):
118 |
119 | print "removing %s from cache" % name
120 |
121 | # Remove the module from the cache so that it will be reloaded
122 | # the next time # that it is imported. The paths for packages
123 | # end with __init__.pyc so this # takes care of them as well.
124 | del sys.modules[name]
125 |
126 | try:
127 | # Reload the top-level modules in macro_dir if natlinkmain is available.
128 | if natlinkmain:
129 | natlinkmain.findAndLoadFiles()
130 | except Exception as e:
131 | print "reloading failed: {}".format(e)
132 | else:
133 | print "finished reloading"
134 |
135 |
136 | # Note that you do not need to turn mic off and then on after saying this. This
137 | # also unloads all modules and packages in the macro directory so that they will
138 | # be reloaded the next time that they are imported. It even reloads Aenea!
139 | class ReloadGrammarsRule(dragonfly.MappingRule):
140 | mapping = {command_table['force natlink to reload all grammars']: dragonfly.Function(reload_code)}
141 |
142 | server_list = dragonfly.DictList('aenea servers')
143 | server_list_watcher = aenea.configuration.ConfigWatcher(
144 | ('grammar_config', 'aenea'))
145 |
146 |
147 | class ChangeServer(dragonfly.CompoundRule):
148 | spec = command_table['set proxy server to ']
149 | extras = [dragonfly.DictListRef('proxy', server_list)]
150 |
151 | def _process_recognition(self, node, extras):
152 | aenea.communications.set_server_address((extras['proxy']['host'], extras['proxy']['port']))
153 |
154 | def _process_begin(self):
155 | if server_list_watcher.refresh():
156 | server_list.clear()
157 | for k, v in server_list_watcher.conf.get('servers', {}).iteritems():
158 | server_list[str(k)] = v
159 |
160 | grammar = dragonfly.Grammar('aenea')
161 |
162 | grammar.add_rule(EnableRule())
163 | grammar.add_rule(DisableRule())
164 | grammar.add_rule(ReloadGrammarsRule())
165 | grammar.add_rule(ChangeServer())
166 |
167 | grammar.load()
168 |
169 |
170 | # Unload function which will be called at unload time.
171 | def unload():
172 | global grammar
173 | if grammar:
174 | grammar.unload()
175 | grammar = None
176 |
--------------------------------------------------------------------------------
/keyboard.py:
--------------------------------------------------------------------------------
1 | # Low-level keyboard input module
2 | #
3 | # Based on the work done by the creators of the Dictation Toolbox
4 | # https://github.com/dictation-toolbox/dragonfly-scripts
5 | #
6 | # and _multiedit-en.py found at:
7 | # http://dragonfly-modules.googlecode.com/svn/trunk/command-modules/documentation/mod-_multiedit.html
8 | #
9 | # Modifications by: Tony Grosinger
10 | #
11 | # Licensed under LGPL
12 |
13 | try:
14 | from aenea import *
15 | except:
16 | from dragonfly import *
17 |
18 | try:
19 | from dragonfly.actions.keyboard import keyboard
20 | from dragonfly.actions.typeables import typeables
21 | if 'semicolon' not in typeables:
22 | typeables["semicolon"] = keyboard.get_typeable(char=';')
23 | except:
24 | pass
25 |
26 | from words import handle_word
27 |
28 | release = Key("shift:up, ctrl:up, alt:up")
29 |
30 |
31 | def cancel_and_sleep(text=None, text2=None):
32 | """Used to cancel an ongoing dictation and puts microphone to sleep.
33 |
34 | This method notifies the user that the dictation was in fact canceled,
35 | a message in the Natlink feedback window.
36 | Then the the microphone is put to sleep.
37 | Example:
38 | "'random mumbling go to sleep'" => Microphone sleep.
39 |
40 | """
41 | try:
42 | from natlink import setMicState
43 | setMicState("sleeping")
44 | print("* Dictation canceled. Going to sleep. *")
45 | except:
46 | pass
47 |
48 |
49 | # For repeating of characters.
50 | specialCharMap = {
51 | "(bar|vertical bar|pipe)": "|",
52 | "(dash|minus|hyphen)": "-",
53 | "dit": ".",
54 | "comma": ",",
55 | "backslash": "\\",
56 | "underscore": "_",
57 | "(star|asterisk)": "*",
58 | "colon": ":",
59 | "(semicolon|semi-colon)": ";",
60 | #"at": "@",
61 | "location": "@",
62 | "[double] quote": '"',
63 | "single quote": "'",
64 | "hash": "#",
65 | "dollar": "$",
66 | "percent": "%",
67 | "ampersand": "&",
68 | "slash": "/",
69 | "equal": "=",
70 | "plus": "+",
71 | "space": " ",
72 |
73 | "bang": "!",
74 | "question": "?",
75 | "caret": "^",
76 | # some other symbols I haven't imported yet, lazy sorry
77 | # 'ampersand': Key('ampersand'),
78 | # 'apostrophe': Key('apostrophe'),
79 | # 'asterisk': Key('asterisk'),
80 | # 'at': Key('at'),
81 | # 'backslash': Key('backslash'),
82 | # 'backtick': Key('backtick'),
83 | # 'bar': Key('bar'),
84 | # 'caret': Key('caret'),
85 | # 'colon': Key('colon'),
86 | # 'comma': Key('comma'),
87 | # 'dollar': Key('dollar'),
88 | # #'(dot|period)': Key('dot'),
89 | # 'double quote': Key('dquote'),
90 | # 'equal': Key('equal'),
91 | # 'bang': Key('exclamation'),
92 | # 'hash': Key('hash'),
93 | # 'hyphen': Key('hyphen'),
94 | # 'minus': Key('minus'),
95 | # 'percent': Key('percent'),
96 | # 'plus': Key('plus'),
97 | # 'question': Key('question'),
98 | # # Getting Invalid key name: 'semicolon'
99 | # #'semicolon': Key('semicolon'),
100 | # 'slash': Key('slash'),
101 | # '[single] quote': Key('squote'),
102 | # 'tilde': Key('tilde'),
103 | # 'underscore | score': Key('underscore'),
104 | }
105 |
106 | # Modifiers for the press-command.
107 | modifierMap = {
108 | "alt": "a",
109 | "angry": "a",
110 | "control": "c",
111 | "shift": "s",
112 | "super": "w",
113 | }
114 |
115 | # Modifiers for the press-command, if only the modifier is pressed.
116 | singleModifierMap = {
117 | "alt": "alt",
118 | "angry": "alt",
119 | "control": "ctrl",
120 | "shift": "shift",
121 | "super": "win",
122 | }
123 |
124 | letterMap = {
125 | "(alpha|arch)": "a",
126 | "(bravo|brav) ": "b",
127 | "(charlie|turley|char) ": "c",
128 | "(delta|del) ": "d",
129 | "(echo|every) ": "e",
130 | "(foxtrot|fox) ": "f",
131 | "(golf|gang) ": "g",
132 | "(hotel) ": "h",
133 | "(india|indigo) ": "i",
134 | "(juliet|julia) ": "j",
135 | "(kilo) ": "k",
136 | "(lima|line) ": "l",
137 | "(mike) ": "m",
138 | "(november|noy) ": "n",
139 | "(Oscar|osh) ": "o",
140 | "(papa|poppa) ": "p",
141 | "(quebec|queen) ": "q",
142 | "(romeo) ": "r",
143 | "(sierra) ": "s",
144 | "(tango|tarnish) ": "t",
145 | "(uniform) ": "u",
146 | "(victor) ": "v",
147 | "(whiskey) ": "w",
148 | "(x-ray) ": "x",
149 | "(yankee) ": "y",
150 | "(zulu|zipper) ": "z",
151 | }
152 | #letterMap = {
153 | #"(alpha|arch)": "a",
154 | #"(bravo|brav) ": "b",
155 | #"(charlie|turley|char) ": "c",
156 | #"(delta|del) ": "d",
157 | #"(echo|eck) ": "e",
158 | #"(foxtrot|fox) ": "f",
159 | #"(golf|gang) ": "g",
160 | #"(hotel) ": "h",
161 | #"(india|indigo|ish) ": "i",
162 | #"(juliet|julia) ": "j",
163 | #"(kilo) ": "k",
164 | #"(lima|lion|line|lie) ": "l",
165 | #"(mike) ": "m",
166 | #"(november|noy) ": "n",
167 | #"(Oscar|osh) ": "o",
168 | #"(papa|poppa|pom) ": "p",
169 | #"(quebec|quiche|queen) ": "q",
170 | #"(romeo|ree) ": "r",
171 | #"(sierra|soy) ": "s",
172 | #"(tango|tay) ": "t",
173 | #"(uniform|umm) ": "u",
174 | #"(victor|van) ": "v",
175 | #"(whiskey|wes) ": "w",
176 | #"(x-ray) ": "x",
177 | #"(yankee|yaa) ": "y",
178 | #"(zulu) ": "z",
179 | #}
180 |
181 |
182 | # generate uppercase versions of every letter
183 | upperLetterMap = {}
184 | for letter in letterMap:
185 | upperLetterMap["(upper|sky) " + letter] = letterMap[letter].upper()
186 | letterMap.update(upperLetterMap)
187 |
188 | numberMap = {
189 | "zero": "0",
190 | "one": "1",
191 | "two": "2",
192 | "three": "3",
193 | "four": "4",
194 | "five": "5",
195 | "six": "6",
196 | "seven": "7",
197 | "eight": "8",
198 | "nine": "9",
199 | }
200 |
201 | controlKeyMap = {
202 | "left": "left",
203 | "right": "right",
204 | "up": "up",
205 | "down": "down",
206 | "page up": "pgup",
207 | "page down": "pgdown",
208 | "home": "home",
209 | "end": "end",
210 | "space": "space",
211 | "(enter|return)": "enter",
212 | "escape": "escape",
213 | "tab": "tab",
214 | "backspace": "backspace"
215 | }
216 |
217 | # F1 to F12. (do these actually work?)
218 | functionKeyMap = {
219 | 'F one': 'f1',
220 | 'F two': 'f2',
221 | 'F three': 'f3',
222 | 'F four': 'f4',
223 | 'F five': 'f5',
224 | 'F six': 'f6',
225 | 'F seven': 'f7',
226 | 'F eight': 'f8',
227 | 'F nine': 'f9',
228 | 'F ten': 'f10',
229 | 'F eleven': 'f11',
230 | 'F twelve': 'f12',
231 | }
232 |
233 | pressKeyMap = {}
234 | pressKeyMap.update(letterMap)
235 | pressKeyMap.update(numberMap)
236 | pressKeyMap.update(controlKeyMap)
237 | pressKeyMap.update(functionKeyMap)
238 |
239 |
240 |
241 | grammarCfg = Config("multi edit")
242 | grammarCfg.cmd = Section("Language section")
243 | grammarCfg.cmd.map = Item(
244 | {
245 | # Navigation keys.
246 | "up []": Key("up:%(n)d"),
247 | "down []": Key("down:%(n)d"),
248 | "left []": Key("left:%(n)d"),
249 | "right []": Key("right:%(n)d"),
250 | "page up []": Key("pgup:%(n)d"),
251 | "page down []": Key("pgdown:%(n)d"),
252 | #"up (page|pages)": Key("pgup:%(n)d"),
253 | #"down (page|pages)": Key("pgdown:%(n)d"),
254 | #"left (word|words)": Key("c-left/3:%(n)d/10"),
255 | #"right (word|words)": Key("c-right/3:%(n)d/10"),
256 | "home": Key("home"),
257 | "end": Key("end"),
258 | "doc home": Key("c-home/3"),
259 | "doc end": Key("c-end/3"),
260 | # Functional keys.
261 | "space": release + Key("space"),
262 | "space []": release + Key("space:%(n)d"),
263 | "(enter|slap|slop) []": release + Key("enter:%(n)d"),
264 | "tab []": Key("tab:%(n)d"),
265 | ###"delete []": Key("del/3:%(n)d"),
266 | "delete [this] line": Key("home, s-end, del"), # @IgnorePep8
267 | "backspace []": release + Key("backspace:%(n)d"),
268 | "application key": release + Key("apps/3"),
269 | "win key": release + Key("win/3"),
270 | #"paste [that]": Function(paste_command),
271 | #"copy [that]": Function(copy_command),
272 | "cut [that]": release + Key("c-x/3"),
273 | "select all": release + Key("c-a/3"),
274 | "[(hold|press)] alt": Key("alt:down/3"),
275 | "[(hold|press)] angry": Key("alt:down/3"),
276 | "release alt": Key("alt:up"),
277 | "[(hold|press)] shift": Key("shift:down/3"),
278 | "release shift": Key("shift:up"),
279 | "[(hold|press)] control": Key("ctrl:down/3"),
280 | "release control": Key("ctrl:up"),
281 | "release [all]": release,
282 | "press key ": Key("%(pressKey)s"),
283 | # Closures.
284 | #"angle brackets": Key("langle, rangle, left/3"),
285 | #"[square] brackets": Key("lbracket, rbracket, left/3"),
286 | #"[curly] braces": Key("lbrace, rbrace, left/3"),
287 | #"(parens|parentheses)": Key("lparen, rparen, left/3"),
288 | #"quotes": Key("dquote/3, dquote/3, left/3"),
289 | #"backticks": Key("backtick:2, left"),
290 | #"single quotes": Key("squote, squote, left/3"),
291 | "squiggle": Text("~"),
292 | "backtick": Key("backtick"),
293 | # Shorthand multiple characters.
294 | "double ": Text("%(char)s%(char)s"),
295 | "triple ": Text("%(char)s%(char)s%(char)s"),
296 | "double escape": Key("escape, escape"), # Exiting menus.
297 | # Punctuation and separation characters, for quick editing.
298 | "colon []": Key("colon/2:%(n)d"),
299 | "semi-colon []": Key("semicolon/2:%(n)d"),
300 | "comma []": Key("comma/2:%(n)d"),
301 | #"(dot|period|dit|point)": Key("dot"), # cannot be followed by a repeat count
302 | "(period|point)": Key("dot"), # cannot be followed by a repeat count
303 | "(dash|hyphen|minus) []": Key("hyphen/2:%(n)d"),
304 | "underscore []": Key("underscore/2:%(n)d"),
305 | "": Text("%(letters)s"),
306 | "": Text("%(char)s"),
307 |
308 | 'langle []': Key('langle:%(n)d'),
309 | 'lace []': Key('lbrace:%(n)d'),
310 | '(lack|lair) []': Key('lbracket:%(n)d'),
311 | #'(laip|len) []': Key('lparen:%(n)d'),
312 | 'len []': Key('lparen:%(n)d'),
313 | 'rangle []': Key('rangle:%(n)d'),
314 | 'race []': Key('rbrace:%(n)d'),
315 | '(rack|rare) []': Key('rbracket:%(n)d'),
316 | #'(raip|ren|wren) []': Key('rparen:%(n)d'),
317 | '(ren|wren) []': Key('rparen:%(n)d'),
318 |
319 | "act []": Key("escape:%(n)d"),
320 | "calm []": Key("comma:%(n)d"),
321 | 'tunnel': Key('space,bar,space'),
322 | 'care': Key('home'),
323 | '(doll|dole)': Key('end'),
324 | 'chuck []': Key('del:%(n)d'),
325 | 'scratch []': Key('backspace:%(n)d'),
326 | #"visual": Key("v"),
327 | "visual line": Key("s-v"),
328 | "visual block": Key("c-v"),
329 | "doc save": Key("c-s"),
330 | "arrow": Text("->"),
331 |
332 | 'gope []': Key('pgup:%(n)d'),
333 | 'drop []': Key('pgdown:%(n)d'),
334 |
335 | 'lope []': Key('c-left:%(n)d'),
336 | '(yope|rope) []': Key('c-right:%(n)d'),
337 | #'(hill scratch|hatch) []': Key('c-backspace:%(n)d'),
338 |
339 | 'hexadecimal': Text("0x"),
340 | 'suspend': Key('c-z'),
341 |
342 | 'word ': Function(handle_word),
343 | 'number ': Text("%(num)d"),
344 | 'change to ': Key("home, slash") + Text("%(text)s") + Key("enter, c, e") + Text("%(text2)s") + Key("escape"),
345 |
346 | # Microphone sleep/cancel started dictation.
347 | "[] (go to sleep|cancel and sleep) []": Function(cancel_and_sleep), # @IgnorePep8
348 | },
349 | namespace={
350 | "Key": Key,
351 | "Text": Text,
352 | }
353 | )
354 |
355 |
356 | class KeystrokeRule(MappingRule):
357 | exported = False
358 | mapping = grammarCfg.cmd.map
359 | extras = [
360 | IntegerRef("n", 1, 100),
361 | IntegerRef("num", 0, 1000000),
362 | Dictation("text"),
363 | Dictation("text2"),
364 | Choice("char", specialCharMap),
365 | Choice("letters", letterMap),
366 | Choice("modifier1", modifierMap),
367 | Choice("modifier2", modifierMap),
368 | Choice("modifierSingle", singleModifierMap),
369 | Choice("pressKey", pressKeyMap),
370 | ]
371 | defaults = {
372 | "n": 1,
373 | }
374 |
375 |
376 |
--------------------------------------------------------------------------------