├── .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 | --------------------------------------------------------------------------------