├── MacroSystem ├── server_state.json ├── aenea.json ├── punctuationmap.py ├── _all.py ├── lettermap.py ├── mousebuttons.py ├── words.py ├── symbolmap.py ├── _mousegrid_symbols.py ├── _mouse_start.py ├── programs.py ├── mousegridutils.py ├── _aenea.py └── keyboard.py ├── Screenshots ├── 2D_grid.png ├── X_grid.png └── Y_grid.png ├── .gitignore ├── plugins ├── clickMouseGrid.yapsy-plugin ├── clickMouseGrid.py ├── clickMouseGrid.sh ├── invisibleWindow.py └── standalone_grids.py ├── run_mousegrid_server.sh ├── README.md └── LICENSE /MacroSystem/server_state.json: -------------------------------------------------------------------------------- 1 | {"host": "192.168.56.1", "port": 8240} -------------------------------------------------------------------------------- /Screenshots/2D_grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shervinemami/BeamGrid/HEAD/Screenshots/2D_grid.png -------------------------------------------------------------------------------- /Screenshots/X_grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shervinemami/BeamGrid/HEAD/Screenshots/X_grid.png -------------------------------------------------------------------------------- /Screenshots/Y_grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shervinemami/BeamGrid/HEAD/Screenshots/Y_grid.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .*.swp 3 | .*.sh 4 | client/util/personal.py 5 | client/util/config.py 6 | server/linux_x11/config.py 7 | -------------------------------------------------------------------------------- /plugins/clickMouseGrid.yapsy-plugin: -------------------------------------------------------------------------------- 1 | [Core] 2 | Name = ClickMouseGrid plugin 3 | Module = clickMouseGrid 4 | 5 | [Documentation] 6 | Author = Shervin Emami 7 | Version = 0.1 8 | Description = Plugin that clicks the mouse 9 | -------------------------------------------------------------------------------- /MacroSystem/aenea.json: -------------------------------------------------------------------------------- 1 | {"screen_resolution": [6400, 1440], "project_root": "C:\\NatLink\\NatLink\\MacroSystem", "security_token": "MFzuQAWl9uYTtbIUwOV+C1HxExkED2tGGthjNajPt4Y=", "restrict_proxy_to_aenea_client": false, "platform": "proxy", "host": "192.168.56.1", "use_multiple_actions": true, "port": 8240} -------------------------------------------------------------------------------- /plugins/clickMouseGrid.py: -------------------------------------------------------------------------------- 1 | from yapsy.IPlugin import IPlugin 2 | 3 | import subprocess 4 | 5 | enabled = True 6 | pid = -1 7 | 8 | def clickMouseGrid(button, security_token): 9 | '''RPC command ''' 10 | print "In clickMouseGrid(", button, ") aenea server plugin." 11 | # Stop the grid script, and wait until it has stopped before continuing. 12 | subprocess.call("plugins/clickMouseGrid.sh %d" % (button), shell=True) 13 | pid = -1 14 | 15 | 16 | class ClickMouseGridPlugin(IPlugin): 17 | def register_rpcs(self, server): 18 | server.register_function(clickMouseGrid) 19 | -------------------------------------------------------------------------------- /MacroSystem/punctuationmap.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # Dictionary of my non-alphabet keyboard mappings, ie: for punctuation and similar letters that exist on the keyboard but aren't in the English alphabet. 3 | # Saying the phrase on the left should generate the symbol on the right. 4 | # Symbols that are easier to say should be placed in the top of the list, since some screens won't need to show the symbols on the bottom of the list. 5 | # Order is preserved, since we use the order as the way to find the index that directly controls the mouse position. 6 | 7 | from collections import OrderedDict # Allows ordered dictionaries even in Python 2 8 | punctuationMap1 = OrderedDict() 9 | punctuationMap2 = OrderedDict() 10 | 11 | # Keyboard characters that aren't numbers or letters 12 | punctuationMap1["tilda"] = u"~" 13 | punctuationMap1["quotes"] = u'"' 14 | punctuationMap1["at"] = u"@" 15 | punctuationMap1["hash"] = u"#" 16 | punctuationMap1["dollar"] = u"$" 17 | punctuationMap1["percent"] = u"%" 18 | punctuationMap1["caret"] = u"^" 19 | punctuationMap1["plus"] = u"+" 20 | punctuationMap1["minus"] = u"-" 21 | punctuationMap1["equals"] = u"=" 22 | punctuationMap1["colon"] = u":" 23 | punctuationMap1["slash"] = u"/" 24 | punctuationMap1["pipe"] = u"|" 25 | punctuationMap1["comma"] = u"," 26 | punctuationMap1["dot"] = u"." 27 | punctuationMap1["question"] = u"?" 28 | 29 | # These are part of normal keyboard characters, but since they are harder to say than most symbols, they are being added late so that they're less likely to be used (eg: Y axis on small screens probably won't use symbols this far down) 30 | punctuationMap2["underscore"] = u"_" 31 | punctuationMap2["semicolon"] = u";" 32 | punctuationMap2["backtick"] = u"`" 33 | punctuationMap2["exclamation"] = u"!" 34 | punctuationMap2["ampersand"] = u"&" 35 | punctuationMap2["asterisk"] = u"*" 36 | punctuationMap2["backslash"] = u"\\" 37 | punctuationMap2["round bracket"] = u"(" 38 | punctuationMap2["close round"] = u")" 39 | punctuationMap2["square bracket"] = u"[" 40 | punctuationMap2["close square"] = u"]" 41 | punctuationMap2["curly brace"] = u"{" 42 | punctuationMap2["close curly"] = u"}" 43 | punctuationMap2["less than"] = u"<" 44 | punctuationMap2["greater than"] = u">" 45 | -------------------------------------------------------------------------------- /run_mousegrid_server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # A voice controlled mouse that supports Linux and fine-grained movements. Caster's Rainbow grid is very cool, it lets 3 | # you move the mouse by voice and is quite fast. But it's not fine-grained enough to select text, and it only runs on 4 | # Windows. I use Aenea to remotely control Linux and therefore don't have any mouse grid modes available at all! So 5 | # I've been implementing my own voice based mouse grid system that works on Linux and is fine-grained enough to select 6 | # individual characters and yet is still fast to use. I don't know how portable it will be outside of aenea & Linux or 7 | # even to other Linux machines since it relies very heavily on the font configuration of the computer. But I'm hoping 8 | # it can eventually support Aenea on Linux, Caster on Windows, and probably Talon on Mac. 9 | # By Shervin Emami, 2019. http://shervinemami.info/ 10 | 11 | 12 | # Make sure the grid isn't already running in the background 13 | pkill -f -9 standalone_grids.py 14 | pkill -f -9 invisibleWindow.py 15 | 16 | 17 | # Default to args "x -s", but allow it to be overriden, such as: 18 | # "x -v -u -c 30" 19 | AXIS='x' 20 | SERVICE_MODE='-s' 21 | KEYBOARD_MODE='-u' 22 | if [[ $# -ge 1 ]]; then 23 | AXIS=$1 24 | SERVICE_MODE='-v' 25 | fi 26 | if [[ $# -ge 2 ]]; then 27 | SERVICE_MODE=$2 28 | fi 29 | if [[ $# -ge 3 ]]; then 30 | KEYBOARD_MODE=$3 31 | fi 32 | if [[ $# -ge 4 ]]; then 33 | CELL_SIZE="$4 $5" 34 | fi 35 | 36 | if [[ $AXIS != "cancel" ]]; then 37 | 38 | # Start the grid, running in service mode in the background 39 | #python plugins/showMouseGrid.py $ARGS 40 | 41 | # Note that we create 2 windows, but it doesn't matter which order they get executed in. 42 | # Keypresses will go to the InvisibleWindow app, and mouseclicks will go to the MouseGrid app. 43 | 44 | # Create a background process to start running our invisible window and return here straight away 45 | python plugins/invisibleWindow.py $SERVICE_MODE & 46 | 47 | # Create a background process to start running our grid script and return here straight away 48 | python plugins/standalone_grids.py -g $AXIS $SERVICE_MODE $KEYBOARD_MODE $CELL_SIZE & 49 | 50 | fi 51 | -------------------------------------------------------------------------------- /plugins/clickMouseGrid.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "In clickMouseGrid.sh $1" 4 | if [ $1 -ge 1 -a $1 -lt 6 ]; then 5 | # Send whichever click event was given 6 | xdotool click "$1" 7 | fi 8 | if [ $1 -eq 6 ]; then 9 | # Send a left-buttton mousedown event 10 | xdotool mousedown 1 11 | fi 12 | if [ $1 -eq 7 ]; then 13 | # Send many mouseup events 14 | xdotool mouseup 1 # left 15 | xdotool mouseup 2 # middle 16 | xdotool mouseup 3 # right 17 | xdotool keyup "ctrl" 18 | xdotool keyup "shift" 19 | xdotool keyup "alt" 20 | xdotool keyup "meta" 21 | fi 22 | if [ $1 -eq 8 ]; then 23 | # Send a middle-button mousedown event, to potentially scroll fast 24 | xdotool mousedown 2 25 | fi 26 | if [ $1 -eq 9 ]; then 27 | # Send a double-click 28 | xdotool click --repeat 2 --delay 100 1 29 | fi 30 | if [ $1 -eq 10 ]; then 31 | # Send a triple-click 32 | xdotool click --repeat 3 --delay 100 1 33 | fi 34 | if [ $1 -eq 11 ]; then 35 | # Hold down the control key while clicking 36 | xdotool keydown "ctrl" 37 | xdotool click "1" 38 | xdotool keyup "ctrl" 39 | fi 40 | if [ $1 -eq 12 ]; then 41 | # Hold down the shift key while clicking 42 | xdotool keydown "shift" 43 | xdotool click "1" 44 | xdotool keyup "shift" 45 | fi 46 | if [ $1 -eq 13 ]; then 47 | # Hold down the alt key while clicking 48 | xdotool keydown "meta" 49 | xdotool click "1" 50 | xdotool keyup "meta" 51 | fi 52 | if [ $1 -eq 14 ]; then 53 | # Hold down the control key while dragging 54 | xdotool keydown "ctrl" 55 | xdotool mousedown 1 56 | fi 57 | if [ $1 -eq 15 ]; then 58 | # Hold down the shift key while dragging 59 | xdotool keydown "shift" 60 | xdotool mousedown 1 61 | fi 62 | if [ $1 -eq 16 ]; then 63 | # Hold down the alt key while dragging 64 | xdotool keydown "meta" 65 | xdotool mousedown 1 66 | fi 67 | if [ $1 -eq 17 ]; then 68 | # Hold down the control key while right clicking 69 | xdotool keydown "ctrl" 70 | xdotool click "3" 71 | xdotool keyup "ctrl" 72 | fi 73 | if [ $1 -eq 18 ]; then 74 | # Hold down the shift key while right clicking 75 | xdotool keydown "shift" 76 | xdotool click "3" 77 | xdotool keyup "shift" 78 | fi 79 | if [ $1 -eq 19 ]; then 80 | # Hold down the alt key while right clicking 81 | xdotool keydown "meta" 82 | xdotool click "3" 83 | xdotool keyup "meta" 84 | fi 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BeamGrid 2 | By Shervin Emami (http://shervinemami.info), 2019 3 | 4 | BeamGrid lets you control your computer mouse by using speech recognition or just a keyboard on Linux. It contains several grid modes for controlling the mouse in different ways. 5 | It's based on the "DouglasGrid" and "Rainbow" mouse grid modes of Caster on Windowd, but instead of just limiting the possibe options to numbers and colors, it uses numbers, alphabet, nnon-alphabet keyboard symbols, and non-keyboard unicode symbols, so that there are a large number of possible symbols that can be displayed with just 1 or 2 symbols, therefore allowing fine grained selection with just a few utterances. It also supports a keyboard-only mode, as a much faster alternative to MouseKeys that many Linux systems come with. But keyboard-only mode doesn't have the extra unicode symbols and therefore is not as fine-grained as the unicode mode. 6 | 7 | * "Beam" mode is generally the fastest way to left-click anywhere on the screen using your voice. It moves to a rough position then performs a left mouse click. 8 | * "Glide" lets you quickly move left or right with high accuracy and range, such as to select some text characters using "drag" & "release". 9 | * "Sink" lets you quickly move up or down with high accuracy and range, such as to select a row of characters. 10 | * "Ladder" shows the X grid followed by the Y grid, letting you move the mouse to a 2D position with high accuracy. 11 | * Other modes include "Grid" mode that moves the mouse in a 2D grid just like Beam mode, but doesn't click the mouse, allowing you to say "Grid 5 6 psychic", etc. 12 | 13 | 2D Beam mode: 14 | ![Screenshot of the 2D "Beam" mouse grid](https://raw.githubusercontent.com/shervinemami/BeamGrid/master/Screenshots/2D_grid.png "Screenshot of the 2D Beam mouse grid") 15 | 16 | 1D "Glide" mode (X-axis): 17 | ![Screenshot of the 1D "Glide" X-axis mouse grid](https://raw.githubusercontent.com/shervinemami/BeamGrid/master/Screenshots/X_grid.png "Screenshot of the 1D Glide X-axis mouse grid") 18 | 19 | 1D "Sink" mode (Y-axis): 20 | ![Screenshot of the 1D "Sink" Y-axis mouse grid](https://raw.githubusercontent.com/shervinemami/BeamGrid/master/Screenshots/Y_grid.png "Screenshot of the 1D Sink Y-axis mouse grid") 21 | 22 | Configuring BeamGrid is tedious, but once you've set it up, you can control the mouse in Linux by voice using Aenea + Dragonfly + Dragon Naturally Speaking. 23 | It should be possible to port this to other voice coding systems such as Caster on Windows or Talon on Mac, but they haven't been ported so far. 24 | 25 | 26 | Video demo of BeamGrid: 27 | 28 | [![Video demo](https://img.youtube.com/vi/xbdwNQfrlKI/0.jpg "Video demo")](https://youtu.be/xbdwNQfrlKI) 29 | 30 | -------------------------------------------------------------------------------- /MacroSystem/_all.py: -------------------------------------------------------------------------------- 1 | # _all.py: main rule for DWK's grammar 2 | 3 | from natlink import setMicState 4 | from aenea import * 5 | 6 | # Allow calling aenea plugins 7 | try: 8 | import aenea.communications 9 | except ImportError: 10 | print 'Unable to import Aenea client-side modules.' 11 | raise 12 | 13 | 14 | import keyboard 15 | import words 16 | import programs 17 | 18 | import mousebuttons 19 | 20 | release = Key("shift:up, ctrl:up, alt:up") 21 | 22 | alternatives = [] 23 | alternatives.append(RuleRef(rule=keyboard.KeystrokeRule())) 24 | alternatives.append(RuleRef(rule=words.FormatRule())) 25 | #alternatives.append(RuleRef(rule=words.ReFormatRule())) 26 | alternatives.append(RuleRef(rule=words.NopeFormatRule())) 27 | alternatives.append(RuleRef(rule=programs.ProgramsRule())) 28 | alternatives.append(RuleRef(rule=mousebuttons.MouseButtonRule())) 29 | 30 | root_action = Alternative(alternatives) 31 | 32 | # Note: The value you set in "max" here is the most number of commands you can give between a pause. 33 | sequence = Repetition(root_action, min=1, max=25, name="sequence") 34 | 35 | 36 | class RepeatRule(CompoundRule): 37 | # Here we define this rule's spoken-form and special elements. 38 | spec = " [repeat that times]" 39 | extras = [ 40 | sequence, # Sequence of actions defined above. 41 | IntegerRef("n", 1, 101), # Times to repeat the sequence. 42 | ] 43 | defaults = { 44 | "n": 1, # Default repeat count. 45 | } 46 | 47 | def _process_recognition(self, node, extras): # @UnusedVariable 48 | sequence = extras["sequence"] # A sequence of actions. 49 | count = extras["n"] # An integer repeat count. 50 | print "In RepeatRule", sequence 51 | for i in range(count): # @UnusedVariable 52 | for action in sequence: 53 | action.execute() 54 | release.execute() 55 | 56 | words = node.words() 57 | print "In RepeatRule. spoken words:", words 58 | 59 | # Now that the main actions have been executed, check if there is a mouse action waiting to execute 60 | mousebuttons.possibleMouseAction(words) 61 | 62 | 63 | # This grammar is our main root grammar that we want to apply at all times, except when the mouse grid (invisible window) is open 64 | invisibleMouseWindowContext = ProxyAppContext(title="InvisibleWindow - ") 65 | notInvisibleMouseWindowContext = ~invisibleMouseWindowContext 66 | grammar = Grammar("root rule", context=notInvisibleMouseWindowContext) 67 | #grammar = Grammar("root rule") 68 | grammar.add_rule(RepeatRule()) # Add the top-level rule. 69 | grammar.load() # Load the grammar. 70 | 71 | def unload(): 72 | """Unload function which will be called at unload time.""" 73 | global grammar 74 | if grammar: 75 | grammar.unload() 76 | grammar = None 77 | -------------------------------------------------------------------------------- /MacroSystem/lettermap.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # Dictionary of my alphabet letter mappings. Saying the phrase on the left should generate the letter on the right. 3 | 4 | # Careful of any word that sounds similar to up, 8, 1, 2, 5 | # and also Dragon keywords "spell ", "click", "select", "correct that", "underline that", "read that", "close window", "close the list", "make that", "make command", "exit dragon"! 6 | 7 | letterMap = { 8 | "acid": "a", # alpha is a bit like up. axis is like backspace. Careful of 8, @, lace, lack. My "aim" sometimes gets picked up as "and". 9 | "bony": "b", # burn is like end, brain is like queen, brayo is like premier, "brown" is like down. "black" is like "ebike", "best" sometimes gets picked up as "this" or "guess". "B|the" sometimes gets picked up as "enter" 10 | "char": "c", 11 | "dozen": "d", # "does" is like "geez", "drax" is like "right". "dam" is like down. "dim" is like "ding". "dug" is like dot. "des" is like "this". "desk" is like "verse", "dose", "this". 12 | "ebike": "e", # "ebike" is like "end black", "evo" isn't getting picked up! careful of x, see, end, as, up 13 | "foxy": "f", # My "fox" is like "false" # careful of F1, F2 ... 14 | "golf": "g", # My "gang" is like "can" 15 | "hotel": "h", # careful of 8 and quote 16 | "itchy": "i", # itchy is like teach 17 | "julia": "j", 18 | "krife": "k", #kidding? krog? # krux is like plus. careful of equal, colon, capital queen, geez. My "kaput" is like "up". My "kilo" sometimes gets picked up as "killer" 19 | "lazy": "l", # lucy? My "lima" is like "clean" and "end". My L sometimes gets picked up as "help". "L" is like Dragon keyword "spell" :-( 20 | "miley": "m", # Mosfet is somehow like "plus" and "space"! # Mix is a bit like minus? # My "mike" is similar to "my" 21 | "nasal": "n", # newish sometimes isn't heard or is like unix. noosh is maybe like mosfet. niche is like unix. # nose? "Nippy" is like "up" 22 | "osez": "o", # omez is like home. orange is like end. oryx is like "echo". My "osh" is like "as". My "omar" is like "home up" 23 | "premier": "p", # please is like lazy and xray, pingu is like undo. "pom" is like "upon" and "up home" 24 | "queen": "q", # quelsh doesn't work. "queen" is like "clean" and brain 25 | "remo": "r", # rezone? rolex is like home. "rod" is like "right" 26 | "salty": "s", # "sook" is like "up", "size" is like "keys". careful of snake, space, 27 | "tosh": "t", # trish is like three. tricky is like keys. teach is like itchy 28 | "unix": "u", # "urge"? # careful of yang 29 | "video": "v", # My "vix" is like "mix". My "vax" is like "backspace". My "van" is a bit like "then" 30 | "wages": "w", # wintel is like hotel, end and enter. week is like queen. "wes" is like "worse" 31 | "x-ray": "x", 32 | "yeelax": "y", # yeeshim is like shift, yiddish is like trish, yazzam is like home or down, yeast is like left. yellow is like "end left", yoke is like black. # "yang" is like "end". Careful of letter "u", home. "why" is like "white" that is like "why tay" 33 | "zimeesi": "z", # zoobkoi is a bit like "close window" and "quotes"! zirconium? zircumference? zosepi? zidacious is like shift, zultani is like up home, zood is like undo and rude, zooki? zyxel is like click, zooch & zener are like insert! zulu, zoolex, zolex, zook and zeakbajived often aren't getting picked up! "zed" is like "said" and "set" 34 | } 35 | -------------------------------------------------------------------------------- /MacroSystem/mousebuttons.py: -------------------------------------------------------------------------------- 1 | # Mouse clicks that can be performed at anytime, by voice on a possibly remote machine and different OS, through Aenea. 2 | # This is being used with "_all.py" so that grammars can include a mouse button action at the end of a sequence of phrases. 3 | # By Shervin Emami (www.shervinemami.info), 2019. 4 | 5 | from mousegridutils import mouseActions 6 | from mousegridutils import smallNumbers 7 | import time 8 | 9 | from aenea import ( 10 | Grammar, 11 | MappingRule, 12 | Text, 13 | Key, 14 | Mimic, 15 | Function, 16 | Dictation, 17 | Choice, 18 | Window, 19 | Config, 20 | Section, 21 | Item, 22 | IntegerRef, 23 | Alternative, 24 | RuleRef, 25 | Repetition, 26 | CompoundRule, 27 | AppContext, 28 | ) 29 | 30 | # Allow calling aenea server-side plugins 31 | try: 32 | import aenea.communications 33 | except ImportError: 34 | print 'Unable to import Aenea client-side modules.' 35 | raise 36 | 37 | 38 | # Saying "kick" will click the left mouse button wherever the mouse cursor currently is. 39 | class MouseButtonRule(CompoundRule): 40 | spec = " []" 41 | extras = [ 42 | Choice("mouseAction", mouseActions), 43 | Choice("repetitions", smallNumbers), 44 | ] 45 | defaults = {"repetitions": 1} 46 | 47 | def value(self, node): 48 | # This file is imported by "_all.py" and needs to return something that has an "execute()" function. 49 | # But remote mouse actions don't have an "execute()" function, and if we directly perform mouse actions here, 50 | # they will get executed before the rest of the actions. 51 | # To support our mouse grid modes, we want the mouse action to happen at the end of the sequence, so the 52 | # user can move the mouse cursor first then click the mouse after the move. 53 | # So we'll do nothing in "value()", but we'll get "_all.py" to call "possibleMouseAction()" at the end of 54 | # the sequence. 55 | #print "MouseButtonRule is saving the words", node.words(), "to check for mouse button actions later." 56 | #self.words = node.words() 57 | return Text() 58 | 59 | 60 | def possibleMouseAction(words): 61 | # Search for a mouse button action, starting from the right-most word 62 | print "Testing mouse action in", words 63 | action = -1 64 | index = -1 65 | word = "" 66 | repetitions = 1 67 | 68 | # Check the last word for an action 69 | try: 70 | index = len(words)-1 71 | word = words[index] 72 | action = mouseActions[word] 73 | except: 74 | pass 75 | 76 | # Check the 2nd last word for an action 77 | try: 78 | index = len(words)-2 79 | word = words[index] 80 | action = mouseActions[word] 81 | except: 82 | pass 83 | 84 | # Find the repetition count 85 | try: 86 | # Grab all words to the right of the mouse action word 87 | repetition_string = words[index+1:][0] 88 | repetitions = smallNumbers[repetition_string] 89 | except: 90 | pass 91 | 92 | # For the special case of moving the mouse wheel, 93 | # set the repetition to be a large number, so the user doesn't have to keep repeating it so many times. 94 | # Asking for a repetition of 0 gets mapped to a scroll of 1. 95 | if action == 4 or action == 5: # Mouse wheel up & down button codes are 4 and 5. 96 | repetitions = repetitions * 3 + 1 # Set the amount to scroll 97 | 98 | #print "Calling showMouseGrid(-1) on the server to hide the MouseGrid" 99 | ## Run our aenea plugin script that shows the mouse grid fullscreen in Linux. 100 | #aenea.communications.server.showMouseGrid(-1) 101 | 102 | # Control the mouse 103 | if action >= 0 and repetitions >= 1: 104 | print "Calling clickMouseGrid(%d) on the server %d times" % (action, repetitions) 105 | # Run our aenea plugin script that moves and clicks the mouse in Linux. 106 | for i in range(repetitions): 107 | pid = aenea.communications.server.clickMouseGrid(action) 108 | time.sleep(0.01) 109 | 110 | # Majority of the time, there won't be any mouse actions being spoken and therefore 111 | # most of the time it will come here. 112 | -------------------------------------------------------------------------------- /MacroSystem/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 | import aenea 12 | import aenea.misc 13 | import aenea.vocabulary 14 | import aenea.configuration 15 | import aenea.format 16 | 17 | from aenea import ( 18 | AeneaContext, 19 | AppContext, 20 | Alternative, 21 | CompoundRule, 22 | Dictation, 23 | DictList, 24 | DictListRef, 25 | Grammar, 26 | IntegerRef, 27 | Literal, 28 | ProxyAppContext, 29 | MappingRule, 30 | NeverContext, 31 | Repetition, 32 | RuleRef, 33 | Sequence 34 | ) 35 | 36 | from aenea import ( 37 | Key, 38 | Text 39 | ) 40 | 41 | lastFormatRuleLength = 0 42 | lastFormatRuleWords = [] 43 | 44 | class NopeFormatRule(CompoundRule): 45 | spec = ('undo that') 46 | 47 | def value(self, node): 48 | global lastFormatRuleLength 49 | print "erasing previous format of length", lastFormatRuleLength 50 | return Key('backspace:' + str(lastFormatRuleLength)) 51 | 52 | #class ReFormatRule(CompoundRule): 53 | # spec = ('that was [upper | natural] ( proper | camel | rel-path | abs-path | score | sentence | ' 54 | # 'scope-resolve | jumble | dotword | dashword | natword | snakeword | brooding-narrative)') 55 | # 56 | # def value(self, node): 57 | # global lastFormatRuleWords 58 | # words = lastFormatRuleWords 59 | # words = node.words()[2:] + lastFormatRuleWords 60 | # print words 61 | # 62 | # uppercase = words[0] == 'upper' 63 | # lowercase = words[0] != 'natural' 64 | # 65 | # if lowercase: 66 | # words = [word.lower() for word in words] 67 | # if uppercase: 68 | # words = [word.upper() for word in words] 69 | # 70 | # words = [word.split('\\', 1)[0].replace('-', '') for word in words] 71 | # if words[0].lower() in ('upper', 'natural'): 72 | # del words[0] 73 | # 74 | # function = getattr(aenea.format, 'format_%s' % words[0].lower()) 75 | # formatted = function(words[1:]) 76 | # 77 | # global lastFormatRuleLength 78 | # lastFormatRuleLength = len(formatted) 79 | # return Text(formatted) 80 | 81 | class FormatRule(CompoundRule): 82 | # I significantly reduced the formatting options, since I rarely use most of them, and they kept being accidentally picked up. 83 | # "macro" means "upper score", such as "HELLO_WORLD". 84 | # "without spaces" means "jumble", such as "helloworld", since "jumble" sounds like "jump" that is used in my grammar. 85 | # "cat" means "natword", such as "hello world" 86 | # I also moved "upper" into the individual formatting rule, and made necessary, so they get randomly picked up less often. 87 | #spec = ('[upper | natural] ( proper | camel | rel-path | abs-path | score | sentence | ' 88 | # 'scope-resolve | jumble | dotword | dashword | natword | snakeword | brooding-narrative) [] [bomb]') 89 | spec = ('( proper | camel | macro | sentence | [uppercase] without spaces | [uppercase] (natword | cat) ) [bomb]') 90 | extras = [Dictation(name='dictation')] 91 | 92 | def value(self, node): 93 | words = node.words() 94 | print "format rule:", words 95 | 96 | #------------------------------------------- 97 | # Handle uppercase 98 | uppercase = words[0] == 'uppercase' 99 | #lowercase = words[0] != 'natural' 100 | 101 | #if lowercase: 102 | # words = [word.lower() for word in words] 103 | if uppercase: 104 | words = [word.upper() for word in words] 105 | 106 | words = [word.split('\\', 1)[0].replace('-', '') for word in words] 107 | if words[0].lower() in ('uppercase', 'natural'): 108 | del words[0] 109 | 110 | #------------------------------------------- 111 | # Handle 'without spaces' 112 | if 'without' in words[0] and 'spaces' in words[1]: 113 | # Use jumble mode 114 | words[0] = 'jumble' 115 | # Get rid of the "spaces" word 116 | del words[1] 117 | 118 | #------------------------------------------- 119 | # Handle 'macro' 120 | if 'macro' in words[0]: 121 | # Get macro to use underscore_formatting_mode 122 | words[0] = 'score' 123 | # Convert all the words to UPPERCASE 124 | words = [word.upper() for word in words] 125 | 126 | #------------------------------------------- 127 | # Handle 'cat' 128 | if 'cat' in words[0]: 129 | # Simply replace 'cat' with 'natword' 130 | words[0] = 'natword' 131 | 132 | #------------------------------------------- 133 | # Handle 'bomb' 134 | bomb = None 135 | if 'bomb' in words: 136 | bomb_point = words.index('bomb') 137 | if bomb_point+1 < len(words): 138 | bomb = words[bomb_point+1 : ] 139 | words = words[ : bomb_point] 140 | 141 | 142 | #------------------------------------------- 143 | # Process all the words 144 | function = getattr(aenea.format, 'format_%s' % words[0].lower()) 145 | formatted = function(words[1:]) 146 | global lastFormatRuleWords 147 | lastFormatRuleWords = words[1:] 148 | 149 | global lastFormatRuleLength 150 | lastFormatRuleLength = len(formatted) 151 | 152 | # empty formatted causes problems here 153 | print " ->", formatted 154 | if bomb != None: 155 | return Text(formatted) + Mimic(' '.join(bomb)) 156 | else: 157 | return Text(formatted) 158 | 159 | -------------------------------------------------------------------------------- /MacroSystem/symbolmap.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # Dictionary of my non-alphabet letter mappings. Saying the phrase on the left should generate the symbol on the right. 3 | # Use unicode symbols that are a single word (thus can be easy to say, and allows more symbols for mouse grid modes). 4 | # Symbols that are easier to use should be placed near the top of the list, since some screens won't need to show the symbols on the bottom of the list. 5 | # Note that it's still beneficial to put a few easy symbols near the end to line up with the bottom or right edges of your screen, for easy clicking on the taskbar and/or scroll bars. 6 | # Order is preserved, since we use the order as the way to find the index that directly controls the mouse position. 7 | # Just be careful that when you move these symbols up and down, you ensure that the symbolMap number is ordered correctly. 8 | # By Shervin Emami (www.shervinemami.info), 2019. 9 | 10 | from collections import OrderedDict # Allows ordered dictionaries even in Python 2 11 | symbolMap1 = OrderedDict() 12 | symbolMap2 = OrderedDict() 13 | symbolMap3 = OrderedDict() 14 | 15 | symbolMap1["flag"] = u"⚑" 16 | symbolMap1["root"] = u"√" 17 | symbolMap1["pi"] = u"π" 18 | symbolMap1["phone"] = u"☎" 19 | symbolMap1["sun"] = u"☼" 20 | symbolMap1["moon"] = u"☽" 21 | symbolMap1["star"] = u"★" 22 | symbolMap1["print"] = u"⎙" # Not showing up properly in Windows when using DejaVu Sans Mono font! 23 | symbolMap1["pen"] = u"✎" 24 | #symbolMap1["pen"] = u"✑" 25 | symbolMap1["alpha"] = u"α" 26 | symbolMap1["beta"] = u"β" 27 | symbolMap1["gamma"] = u"Ɣ" 28 | symbolMap1["delta"] = u"δ" 29 | symbolMap1["mail"] = u"✉" 30 | symbolMap1["pulse"] = u"⎍" # Not showing up properly in Windows when using DejaVu Sans Mono font! 31 | symbolMap1["fish"] = u"ᛟ" 32 | symbolMap1["hook"] = u"Ⴑ" 33 | symbolMap1["house"] = u"☖" 34 | symbolMap1["plane"] = u"✈" 35 | symbolMap1["music"] = u"♫" 36 | symbolMap1["note"] = u"♩" 37 | symbolMap1["peace"] = u"☮" 38 | symbolMap1["angle"] = u"⦞" # Not showing up properly in Windows when using DejaVu Sans Mono font! 39 | symbolMap1["theta"] = u"θ" 40 | symbolMap1["mew"] = u"μ" # mu is pronounced more like mew 41 | symbolMap1["row"] = u"ρ" # rho is pronounced more like row 42 | symbolMap1["taow"] = u"τ" # tau is pronounced more like taow 43 | symbolMap1["psi"] = u"ψ" 44 | symbolMap1["phee"] = u"φ" # some people refer to φ as "fee" and some as "fi", but "fi" sounds kind of like 5 45 | symbolMap1["half"] = u"½" 46 | symbolMap1["heart"] = u"♥" 47 | symbolMap1["diamond"] = u"♦" 48 | symbolMap1["ground"] = u"⏚" # Not showing up properly in Windows when using DejaVu Sans Mono font! 49 | symbolMap1["divide"] = u"÷" 50 | symbolMap1["square"] = u"□" 51 | symbolMap1["circle"] = u"○" 52 | symbolMap1["triangle"] = u"△" 53 | symbolMap1["rectangle"] = u"⌷" 54 | symbolMap1["oval"] = u"⬯" # Not showing up properly in Windows when using DejaVu Sans Mono font! 55 | 56 | # Slightly harder symbols pronounce or remember: 57 | symbolMap2["danger"] = u"☠" 58 | symbolMap2["anchor"] = u"⚓" 59 | symbolMap2["quarter"] = u"¼" 60 | symbolMap2["cents"] = u"¢" 61 | symbolMap2["coin"] = u"" # Not showing up properly in Windows when using DejaVu Sans Mono font! 62 | symbolMap2["euros"] = u"€" 63 | symbolMap2["pounds"] = u"£" 64 | symbolMap2["yen"] = u"¥" 65 | symbolMap2["rupees"] = u"₨" 66 | symbolMap2["lira"] = u"₺" 67 | symbolMap2["pesos"] = u"₱" 68 | symbolMap2["degrees"] = u"°" 69 | #symbolMap2["sum"] = u"Ʃ" # Too similar sounding to "sun" 70 | symbolMap2["infinity"] = u"∞" 71 | symbolMap2["integral"] = u"ʃ" 72 | symbolMap2["disabled"] = u"♿" 73 | symbolMap2["epsilon"] = u"ϵ" 74 | symbolMap2["zeta"] = u"ζ" 75 | #symbolMap2["and"] = u"∧" 76 | #symbolMap2["or"] = u"∨" # Too similar sounding to "4"? 77 | symbolMap2["clubs"] = u"♣" 78 | symbolMap2["spade"] = u"♠" 79 | symbolMap2["ace"] = u"A" 80 | symbolMap2["joker"] = u"J" 81 | symbolMap2["dashed"] = u"╏" 82 | symbolMap2["dotted"] = u"┋" 83 | symbolMap2["solid"] = u"│" # "line" is too similar to "nine" 84 | symbolMap2["perpendicular"] = u"⟂" 85 | symbolMap2["squared"] = u"²" 86 | symbolMap2["cubed"] = u"³" 87 | symbolMap2["intersect"] = u"∩" 88 | symbolMap2["union"] = u"∪" 89 | #symbolMap2["chi"] = u"χ" # Too hard to pronouce for some people? 90 | symbolMap2["umlaut"] = u"ö" 91 | symbolMap2["strike"] = u"Ø" 92 | symbolMap2["begin"] = u"⍃" 93 | symbolMap2["finish"] = u"⍄" 94 | symbolMap2["top"] = u"⍓" 95 | symbolMap2["bottom"] = u"⍌" 96 | symbolMap2["umbrella"] = u"☂" 97 | symbolMap2["cloud"] = u"☁" 98 | symbolMap2["smiley"] = u"☺" 99 | symbolMap2["sad"] = u"☹" 100 | symbolMap2["girl"] = u"♀" 101 | symbolMap2["guy"] = u"♂" 102 | symbolMap2["man"] = u"ᛉ" 103 | symbolMap2["bow"] = u"⦈" 104 | symbolMap2["arrow"] = u"➚" 105 | symbolMap2["target"] = u"⊙" 106 | symbolMap2["hammer"] = u"⚒" 107 | symbolMap2["scissors"] = u"✂" 108 | symbolMap2["swords"] = u"⚔" 109 | symbolMap2["ying"] = u"☯" 110 | symbolMap2["yang"] = u"☯" 111 | symbolMap2["recycle"] = u"♺" 112 | symbolMap2["flower"] = u"❀" 113 | #symbolMap2["pencil"] = u"✎" # Pencil sounds too much like "cancel" 114 | symbolMap2["dice"] = u"⚁" 115 | symbolMap2["cross"] = u"✝" 116 | symbolMap2["single"] = u"꠳" # Not showing up properly in Windows when using DejaVu Sans Mono font! 117 | symbolMap2["dual"] = u"꠴" # Not showing up properly in Windows when using DejaVu Sans Mono font! 118 | symbolMap2["triple"] = u"꠵" # Not showing up properly in Windows when using DejaVu Sans Mono font! 119 | symbolMap2["king"] = u"♔" 120 | #symbolMap2["queen"] = u"♛" # Identical to my word for "q" 121 | symbolMap2["rook"] = u"♜" 122 | symbolMap2["bishop"] = u"♗" 123 | symbolMap2["knight"] = u"♘" 124 | symbolMap2["pawn"] = u"♙" 125 | symbolMap2["check"] = u"✔" # "tick" sounds too much like "click" or "kick" 126 | 127 | # Symbols that are mostly less obvious. 128 | symbolMap3["serious"] = u"⁈" 129 | symbolMap3["trademark"] = u"™" 130 | symbolMap3["copyright"] = u"©" 131 | symbolMap3["registered"] = u"®" 132 | symbolMap3["omega"] = u"Ω" 133 | symbolMap3["sigma"] = u"σ" 134 | symbolMap3["thus"] = u"∴" 135 | symbolMap3["identical"] = u"≡" 136 | symbolMap3["proportional"] = u"∝" 137 | symbolMap3["roughly"] = u"≈" 138 | symbolMap3["corner"] = u"⌜" 139 | symbolMap3["eject"] = u"⏏" 140 | symbolMap3["record"] = u"●" 141 | symbolMap3["play"] = u"‣" 142 | symbolMap3["pause"] = u"‖" 143 | symbolMap3["rewind"] = u"◄" 144 | symbolMap3["stop"] = u"▪" 145 | symbolMap3["forward"] = u"►" 146 | symbolMap3["horizontal"] = u"─" 147 | symbolMap3["vertical"] = u"│" 148 | symbolMap3["slanted"] = u"╱" 149 | symbolMap3["subset"] = u"⊂" 150 | symbolMap3["superset"] = u"⊃" 151 | -------------------------------------------------------------------------------- /plugins/invisibleWindow.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # Open a normal window (including the full window frame) that's added to the OS window list and taskbar, but make this window invisible. 3 | # Allows a fullscreen frameless window to be displayed on top, with keyboard events being passed to this window, and being part of the OS window list. 4 | # By Shervin Emami 2019 (http://www.shervinemami.info) 5 | 6 | # This line is different for Python 2 vs 3: 7 | from Tkinter import Tk 8 | 9 | from datetime import datetime 10 | import subprocess 11 | import time 12 | import sys 13 | from threading import Timer 14 | from SimpleXMLRPCServer import SimpleXMLRPCServer 15 | from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler 16 | import xmlrpclib 17 | 18 | # Allow passing any keypresses to the mouse grid window, since it's a root window that can't receive keypresses 19 | MOUSE_SERVER_ADDRESS = 'http://192.168.56.1:8000' # Set up our client address 20 | # Allow bringing the invisible window to the front or back of the window stack 21 | INVISIBLEWINDOW_SERVER_ADDRESS = ("192.168.56.1", 8001) # Set up our server address 22 | 23 | 24 | # Create the window here, so it can be accessed easily by all functions 25 | root = Tk() 26 | 27 | 28 | try: 29 | print datetime.now(), "- InvisibleWindow connecting to XMLRPC mouse server at", MOUSE_SERVER_ADDRESS 30 | mouseServer = xmlrpclib.ServerProxy(MOUSE_SERVER_ADDRESS) 31 | except e, v: 32 | print e, v 33 | raise 34 | 35 | class KeyEvent: 36 | def __init__(self, char, keysym, keycode): 37 | self.char = char 38 | self.keysym = keysym 39 | self.keycode = keycode 40 | 41 | 42 | def key_callback(event): 43 | print 44 | print datetime.now(), "- InvisibleWindow keyboard event, pressing the key '%s'. keysym='%s', keycode=%s" % (event.char, event.keysym, event.keycode) 45 | 46 | # Some special keys like 'Esc' cause problems when sending over XMLRPC. Convert to a spacebar that will get ignored later. 47 | if event.keysym == 'Escape': 48 | event.char = ' ' 49 | event.keysym = 'escape' 50 | event.keycode = 65 51 | # If they said "And" but it sounded like "End", something we don't show in our mouse grid, convert "End" to "And" 52 | if event.keysym == 'End': 53 | event.char = '' 54 | event.keysym = 'and' 55 | event.keycode = -1 56 | 57 | # Print list of available methods 58 | #print "SERVER methods:" 59 | #print mouseServer.system.listMethods() 60 | 61 | if len(event.char) == 1: 62 | keyEvent = KeyEvent(event.char, event.keysym, event.keycode) 63 | mouseServer.keypress(keyEvent) 64 | else: 65 | print datetime.now(), "- Discarding this key event." 66 | print datetime.now(), "- InvisibleWindow keyboard event is done." 67 | 68 | # Kill the 2 grid windows, and wait till the signal has been dispatched. 69 | #subprocess.call("pkill -f -9 standalone_grids.py", shell=True) 70 | ##subprocess.call("pkill -f -9 createInvisibleWindow.py", shell=True) 71 | #import sys 72 | #sys.exit() 73 | 74 | 75 | def mouse_callback(event): 76 | print datetime.now(), "- InvisibleWindow mouse event at", event.x, event.y 77 | hide() 78 | print datetime.now(), "- InvisibleWindow mouse event is done." 79 | ## Kill the 2 grid windows, and wait till the signal has been dispatched. 80 | #subprocess.call("pkill -f -9 standalone_grids.py", shell=True) 81 | ##subprocess.call("pkill -f -9 createInvisibleWindow.py", shell=True) 82 | #import sys 83 | #sys.exit() 84 | 85 | 86 | def unhide(): 87 | print datetime.now(), "- In InvisibleWindow unhide()" 88 | root.deiconify() # Display the window 89 | root.lift() 90 | time.sleep(0.1) 91 | root.focus_force() 92 | root.focus_set() 93 | print datetime.now(), "- InvisibleWindow unhide is done." 94 | 95 | 96 | def hide(): 97 | print datetime.now(), "- In InvisibleWindow hide()" 98 | root.withdraw() # Stop displaying the window, but don't destroy it 99 | print datetime.now(), "- InvisibleWindow hide is done." 100 | 101 | 102 | def setWindowTitle(title = "InvisibleWindow"): 103 | print datetime.now(), "- InvisibleWindow setting the window title to", title 104 | root.wm_title(title) 105 | 106 | 107 | 108 | # Restrict XMLRPC server to a particular path. 109 | class RequestHandler(SimpleXMLRPCRequestHandler): 110 | rpc_paths = ('/RPC2',) 111 | 112 | 113 | def setup_xmlrpc_server(): 114 | server_quit = 0 115 | print datetime.now(), "- Setting up the InvisibleWindow XMLRPC server at", INVISIBLEWINDOW_SERVER_ADDRESS 116 | windowServer = SimpleXMLRPCServer(INVISIBLEWINDOW_SERVER_ADDRESS, requestHandler=RequestHandler, allow_none=True) 117 | windowServer.register_function(xmlrpc_kill, "kill") 118 | windowServer.register_function(hide, "hide") 119 | windowServer.register_function(unhide, "unhide") 120 | windowServer.register_function(setWindowTitle, "setWindowTitle") 121 | #TODO: Disable this for security when not debugging: 122 | #windowServer.register_introspection_functions() 123 | return windowServer 124 | 125 | 126 | def xmlrpc_kill(): 127 | print datetime.now(), "- XMLRPC InvisibleWindow server received kill event" 128 | after(2, die) 129 | 130 | def die(): 131 | print datetime.now(), "- Closing the InvisibleWindow" 132 | server_quit = 1 133 | destroy() 134 | # Kill the 2 grid windows, and wait till the signal has been dispatched. 135 | subprocess.call("pkill -f -9 standalone_grids.py", shell=True) 136 | #subprocess.call("pkill -f -9 createInvisibleWindow.py", shell=True) 137 | os.kill(os.getpid(), signal.SIGTERM) 138 | import sys 139 | sys.exit() 140 | 141 | 142 | def createInvisibleWindow(argv): 143 | #root = Tk() 144 | root.wm_title("InvisibleWindow") 145 | root.geometry("1x1+0+0") # Make a tiny window, that wouldn't be noticeable anyway (for systems that don't support transparent windows) 146 | root.wait_visibility(root) 147 | # Show this whole window as invisible. This line must be after "wait_visibility". 148 | #root.wm_attributes('-alpha', 0.0) 149 | #root.attributes("-alpha", 0.0) 150 | 151 | windowServer = setup_xmlrpc_server() 152 | 153 | # Close this window if any mouse or keyboard events happen. 154 | root.bind("", mouse_callback) 155 | root.bind("", mouse_callback) 156 | root.bind("", mouse_callback) 157 | root.bind("", key_callback) 158 | 159 | root.protocol("WM_DELETE_WINDOW", xmlrpc_kill) 160 | 161 | # Get the XMLRPC server to start in the background quite soon 162 | server_quit = 0 163 | def start_server(): 164 | while not server_quit: 165 | windowServer._handle_request_noblock() 166 | Timer(0.3, start_server).start() 167 | 168 | #serviceMode = False 169 | if len(argv) > 0 and argv[0] == '-s': 170 | # serviceMode = True 171 | # Hide this window from the system taskbar until we're actually needing it 172 | hide() 173 | 174 | print datetime.now(), "- Rendering invisible window now" 175 | root.mainloop() 176 | 177 | 178 | if __name__ == '__main__': 179 | print datetime.now(), "- Creating an invisible window that includes invisible frame borders" 180 | createInvisibleWindow(sys.argv[1:]) 181 | -------------------------------------------------------------------------------- /MacroSystem/_mousegrid_symbols.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # This grammar provides the symbols that can be spoken while the mouse grid is shown. It includes all keyboard 3 | # characters but also many unicode symbols that aren't available on keyboards and therefore don't have keycodes, 4 | # to allow fine-grained control of mouse grids while using only 1 or 2 characters on the screen. Since the whole 5 | # purpose of this is to control the mouse grids, this module will just send that info to the mouse grid XMLRPC 6 | # server directly instead of via keyboard events. 7 | # By Shervin Emami (www.shervinemami.info), 2019. 8 | 9 | 10 | from aenea import Grammar, MappingRule, Text, Key, Function, Choice, CompoundRule, ProxyAppContext 11 | from collections import OrderedDict # Allows ordered dictionaries even in Python 2 12 | 13 | # Allow calling aenea server-side plugins 14 | try: 15 | import aenea.communications 16 | except ImportError: 17 | print 'Unable to import Aenea client-side modules.' 18 | raise 19 | 20 | # Allow passing any keypresses to the mouse grid window, since it's a root window that can't receive keypresses. 21 | # We send them as direct input instead of as keycodes, since many of these symbols don't have corresponding key codes. 22 | import xmlrpclib 23 | 24 | 25 | from mousegridutils import loadAllSymbols 26 | from mousegridutils import mouseActions 27 | 28 | 29 | class SymbolEvent: 30 | def __init__(self, phrase, symbol): 31 | self.phrase = phrase 32 | self.symbol = symbol 33 | 34 | 35 | # Set the IP address & port of the mouse grid XMLRPC server 36 | MOUSE_SERVER_ADDRESS = 'http://192.168.56.1:8000' 37 | server = xmlrpclib.ServerProxy(MOUSE_SERVER_ADDRESS) 38 | 39 | 40 | # Create an ordered dictionary of all the symbols and phrases we might use in our mouse grids. 41 | # Symbols that are easier to use should be placed near the top of the list, since some screens won't need to show the symbols on the bottom of the list. 42 | # Rather than listen for all possible symbols in all cases, only listen for the actual symbols that can fit on the screen. 43 | # Make sure you update "TOTAL_SYMBOLS_FOR_MINI_GRID" depending on your screen resolution and font size settings in "standalone_grids.py"! 44 | TOTAL_SYMBOLS_FOR_2DGRID_X = 88 45 | TOTAL_SYMBOLS_FOR_2DGRID_Y = 61 46 | charsDict2D_X = loadAllSymbols(keyboardMode=False, extendedSymbolsLevel=3, extendedSymbolsCount=TOTAL_SYMBOLS_FOR_2DGRID_X) 47 | charsDict2D_Y = loadAllSymbols(keyboardMode=False, extendedSymbolsLevel=3, extendedSymbolsCount=TOTAL_SYMBOLS_FOR_2DGRID_Y) 48 | charsDict1D_X = loadAllSymbols(keyboardMode=False, extendedSymbolsLevel=3, extendedSymbolsCount=999999) 49 | # charsDict1D_Y = charsDict2D_X # 1D Y grid uses slightly less symbols than 2D X axis, so just re-use it 50 | #print "charsDict2D_X", charsDict2D_X 51 | #print "charsDict1D_X", charsDict1D_X 52 | 53 | 54 | # Handle the various grid mode grammars 55 | def doRecognitionLevel(node, extras, mode): 56 | # Get the input data 57 | symbol1 = "" 58 | symbol2 = "" 59 | mouseAction = -1 60 | try: 61 | symbol1 = extras["symbol1"] 62 | except: 63 | pass 64 | 65 | try: 66 | symbol2 = extras["symbol2"] 67 | except: 68 | pass 69 | 70 | try: 71 | mouseAction = extras["mouseAction"] 72 | except: 73 | pass 74 | 75 | #print node.pretty_string() 76 | words = node.words() 77 | print u"In doRecognitionLevel(), mode", mode, ". spoken words:", words, 78 | if len(symbol1) > 0: 79 | print symbol1.encode('utf-8'), 80 | if len(symbol2) > 0: 81 | print symbol2.encode('utf-8'), 82 | print u", mouse action", mouseAction 83 | 84 | # Move the mouse cursor by sending the symbol to the mouse grid RPC server. 85 | phrase1 = "" 86 | symbol1Event = None 87 | symbol2Event = None 88 | if len(symbol1) > 0: 89 | phrase1 = node.results[0][0] #.encode("utf-8") 90 | symbol1Event = SymbolEvent(phrase1, symbol1) 91 | phrase2 = "" 92 | if len(symbol2) > 0: 93 | phrase2 = node.results[1][0] 94 | symbol2Event = SymbolEvent(phrase2, symbol2) 95 | # If we have a symbol or possibly 2, send the 1 or 2 signals 96 | if symbol1Event: 97 | if symbol2Event: 98 | server.injectSymbols(symbol1Event, symbol2Event) 99 | else: 100 | server.injectSymbol(symbol1Event) 101 | 102 | # Possibly press a mouse button action 103 | button_int = int(mouseAction) 104 | if button_int >= 0: 105 | print "Calling clickMouseGrid(%d) on the server" % (button_int) 106 | # Run our aenea plugin script that moves and clicks the mouse in Linux. 107 | pid = aenea.communications.server.clickMouseGrid(button_int) 108 | 109 | 110 | class MouseActionGridRule(CompoundRule): 111 | spec = "" 112 | extras = [ 113 | Choice("mouseAction", mouseActions), 114 | ] 115 | 116 | def _process_recognition(self, node, extras): 117 | doRecognitionLevel(node, extras, "Action") 118 | 119 | 120 | class ExtraSymbolsMiniGridRule(CompoundRule): 121 | spec = " [] []" 122 | extras = [ 123 | Choice("symbol1", charsDict2D_X), 124 | Choice("symbol2", charsDict2D_Y), 125 | Choice("mouseAction", mouseActions), 126 | ] 127 | 128 | def _process_recognition(self, node, extras): 129 | print u"node", node 130 | print u"extras", extras 131 | doRecognitionLevel(node, extras, "Mini") 132 | 133 | 134 | class ExtraSymbolsFullGridRule(CompoundRule): 135 | spec = " []" 136 | extras = [ 137 | Choice("symbol1", charsDict1D_X), 138 | Choice("mouseAction", mouseActions), 139 | ] 140 | 141 | def _process_recognition(self, node, extras): 142 | doRecognitionLevel(node, extras, "Full") 143 | 144 | 145 | # Allow to easily close the mouse grid while it's displayed 146 | def cancelMouseGrid(): 147 | #print "Calling showMouseGrid(cancel) on the server to hide the MouseGrid" 148 | # Run our aenea plugin script that shows the mouse grid fullscreen in Linux. 149 | #aenea.communications.server.showMouseGrid("cancel") 150 | 151 | print "Sending Escape to cancel the mouse grid" 152 | action = Key("escape") 153 | action.execute() 154 | 155 | 156 | class MouseCancelRule(MappingRule): 157 | mapping = { 158 | "cancel": Function(cancelMouseGrid), 159 | } 160 | 161 | 162 | contextMini = ProxyAppContext(title="InvisibleWindow - Mini") 163 | grammarMini = Grammar('extra symbols mini grammar', context=contextMini) 164 | grammarMini.add_rule(ExtraSymbolsMiniGridRule()) 165 | grammarMini.add_rule(MouseCancelRule()) 166 | grammarMini.add_rule(MouseActionGridRule()) 167 | grammarMini.load() 168 | 169 | contextFull = ProxyAppContext(title="InvisibleWindow - Full") 170 | grammarFull = Grammar('extra symbols full grammar', context=contextFull) 171 | grammarFull.add_rule(ExtraSymbolsFullGridRule()) 172 | grammarFull.add_rule(MouseCancelRule()) 173 | grammarFull.add_rule(MouseActionGridRule()) 174 | grammarFull.load() 175 | 176 | 177 | def unload(): 178 | global grammarMini 179 | if grammarMini: 180 | grammarMini.unload() 181 | grammarMini = None 182 | 183 | global grammarFull 184 | if grammarFull: 185 | grammarFull.unload() 186 | grammarFull = None 187 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /MacroSystem/_mouse_start.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # Use mouse grids to control the mouse by voice on a possibly remote machine and different OS, through Aenea. 3 | # Based on Caster's DouglasGrid 4 | # By Shervin Emami (www.shervinemami.info), 2019. 5 | 6 | 7 | from aenea import Grammar, MappingRule, CompoundRule, Text, Key, Mouse, Function, Playback, Choice, ProxyAppContext, IntegerRef 8 | #import json 9 | 10 | import xmlrpclib 11 | import time 12 | 13 | MOUSE_SERVER_ADDRESS = 'http://192.168.56.1:8000' 14 | INVISIBLEWINDOW_SERVER_ADDRESS = 'http://192.168.56.1:8001' 15 | 16 | mouseServer = xmlrpclib.ServerProxy(MOUSE_SERVER_ADDRESS) 17 | windowServer = xmlrpclib.ServerProxy(INVISIBLEWINDOW_SERVER_ADDRESS) 18 | 19 | try: 20 | import aenea.communications 21 | #import aenea.config 22 | except ImportError: 23 | print 'Unable to import Aenea client-side modules.' 24 | raise 25 | 26 | from mousegridutils import mouseActions 27 | from mousegridutils import mostNumbers 28 | 29 | 30 | grammar = Grammar('aenea mouse start grammar') 31 | 32 | 33 | class CenterMouseRule(CompoundRule): 34 | spec = "center []" 35 | extras = [ 36 | Choice("mouseAction", mouseActions), 37 | ] 38 | defaults = { 39 | # Set the default to -1 if you want "center" to just move the cursor, 40 | # or set it to 1 if you want "center" to perform a left mouse click by default. 41 | "mouseAction": 1, 42 | } 43 | 44 | def _process_recognition(self, node, extras): 45 | mouseAction = extras["mouseAction"] 46 | 47 | print "Centering the mouse." 48 | try: 49 | mouseServer.moveToCenter() 50 | except: 51 | print "Couldn't access the mouse grid server, is it running?" 52 | return 53 | 54 | # Possibly press a mouse button action 55 | action = int(mouseAction) 56 | if action >= 0: 57 | repetitions = 1 58 | # For the special case of moving the mouse wheel, 59 | # set the repetition to be a large number, so the user doesn't have to keep repeating it so many times. 60 | # Asking for a repetition of 0 gets mapped to a scroll of 1. 61 | if action == 4 or action == 5: # Mouse wheel up & down button codes are 4 and 5. 62 | repetitions = repetitions * 3 + 1 # Set the amount to scroll 63 | 64 | print "Calling clickMouseGrid(%d) on the server" % (action) 65 | # Run our aenea plugin script that moves and clicks the mouse in Linux. 66 | for i in range(repetitions): 67 | pid = aenea.communications.server.clickMouseGrid(action) 68 | time.sleep(0.01) 69 | 70 | 71 | class NudgeMouseRule(CompoundRule): 72 | spec = "nudge [] []" 73 | extras = [ 74 | Choice("mouseAction", mouseActions), 75 | Choice("direction", { # Allow to say either up or North 76 | "North": 1, 77 | "East": 2, 78 | "South": 3, 79 | "West": 4, 80 | "North East": 5, 81 | "South East": 6, 82 | "South West": 7, 83 | "North West": 8, 84 | "up": 1, 85 | "right": 2, 86 | "down": 3, 87 | "left": 4, 88 | "up right": 5, 89 | "down right": 6, 90 | "down left": 7, 91 | "up left": 8, 92 | }), 93 | Choice("distance", mostNumbers), 94 | ] 95 | defaults = { 96 | "mouseAction": -1, # Don't click unless they ask for it 97 | } 98 | 99 | def _process_recognition(self, node, extras): 100 | direction = extras["direction"] 101 | mouseAction = extras["mouseAction"] 102 | 103 | # Figure out how much to move by 104 | distance = 7 # Default distance to nudge by 105 | try: 106 | distance = extras["distance"] 107 | except: 108 | pass 109 | 110 | print "Nudging the mouse in direction", direction, "by", distance 111 | try: 112 | if direction == 1: 113 | mouseServer.move_mouse_relative(0, -distance) 114 | elif direction == 2: 115 | mouseServer.move_mouse_relative(distance, 0) 116 | elif direction == 3: 117 | mouseServer.move_mouse_relative(0, distance) 118 | elif direction == 4: 119 | mouseServer.move_mouse_relative(-distance, 0) 120 | elif direction == 5: 121 | mouseServer.move_mouse_relative(distance, -distance) 122 | elif direction == 6: 123 | mouseServer.move_mouse_relative(distance, distance) 124 | elif direction == 7: 125 | mouseServer.move_mouse_relative(-distance, distance) 126 | elif direction == 8: 127 | mouseServer.move_mouse_relative(-distance, -distance) 128 | except: 129 | print "Couldn't access the mouse grid server, is it running?" 130 | return 131 | 132 | # Possibly press a mouse button action 133 | button_int = int(mouseAction) 134 | if button_int >= 0: 135 | print "Calling clickMouseGrid(%d) on the server" % (button_int) 136 | # Run our aenea plugin script that moves and clicks the mouse in Linux. 137 | pid = aenea.communications.server.clickMouseGrid(button_int) 138 | 139 | 140 | def showMouseGrid(mode, mouseClick=-1): 141 | print "Calling showMouseGrid(%s) on the server" % (mode) 142 | 143 | # We need to specify whether the mouse mode we are controlling in later commands. 144 | #aenea.mouse_grid_mode = "x" 145 | 146 | # Run our aenea plugin script that shows the mouse grid fullscreen in Linux. 147 | #pid = aenea.communications.server.showMouseGrid("x0") 148 | 149 | # Display the mouse grid 150 | try: 151 | mouseServer.showMouseGrid(mode, mouseClick) 152 | except: 153 | print "Couldn't access the mouse grid server, is it running?" 154 | return 155 | 156 | # Make sure the invisible window is brought to the front of the window stack, so it can have keyboard focus to be used with our extra characters. 157 | print "Showing the InvisibleWindow" 158 | try: 159 | windowServer.unhide() 160 | except: 161 | print "Couldn't access the InvisibleWindow server, is it running?" 162 | return 163 | 164 | print "Finished showMouseGrid(%s)" % (mode) 165 | 166 | 167 | def showMouseGridX0(): 168 | showMouseGrid("x0") 169 | 170 | def showMouseGridX1(): 171 | showMouseGrid("x1") 172 | 173 | def showMouseGridX2(): 174 | showMouseGrid("x2") 175 | 176 | def showMouseGridY0(): 177 | showMouseGrid("y0") 178 | 179 | # Show a sequence of 2 grids moving in a single direction at a time 180 | def showMouseGridX0Y0(): 181 | showMouseGrid("x0y0") 182 | 183 | def clickMouseGridG0(): 184 | # Show the mouse grid, but also schedule a left-mouse click when the user has finally moved the mouse pointer, 185 | # to save the user from needing a separate command to perform the mouse click. 186 | showMouseGrid("g0", mouseClick=1) 187 | 188 | def showMouseGridG0(): 189 | showMouseGrid("g0") 190 | 191 | #print "Calling showMouseGrid(2D) on the server" 192 | # We need to specify whether which mouse mode we are controlling in later commands. 193 | #aenea.mouse_grid_mode = "2D" 194 | 195 | # Run our aenea plugin script that shows the mouse grid fullscreen in Linux. 196 | #pid = aenea.communications.server.showMouseGrid("2D") 197 | #print "Returned pid", pid 198 | 199 | ## Reset the stored mouse status. 200 | #mouseState = {} 201 | #mouseState['x'] = -1 202 | #mouseState['y'] = -1 203 | #mouseState['status'] = 0 # Empty 204 | # 205 | ## Store the mouse status. 206 | #dir = "" 207 | #if len(aenea.config.PROJECT_ROOT) > 0: 208 | # dir = aenea.config.PROJECT_ROOT + "\\" 209 | #with open(dir + "mouse_state.json", 'wb') as outfile: 210 | # json.dump(mouseState, outfile) 211 | 212 | # We need to specify which mouse mode we are controlling in later commands. 213 | #aenea.mouse_grid_mode = "y" 214 | 215 | # Run our aenea plugin script that shows the mouse grid fullscreen in Linux. 216 | #pid = aenea.communications.server.showMouseGrid("y") 217 | #print "Returned pid", pid 218 | 219 | 220 | class StartMouseGridRule(MappingRule): 221 | mapping = { 222 | # Commands for starting the mouse grid. The grid might only cover part of a large screen, 223 | # or you might have multiple screens, so we have commands to show grids in multiple locations. 224 | "sink": Function(showMouseGridY0), # Slide down or up 225 | "glide": Function(showMouseGridX0), # Slide left or right 226 | "sled": Function(showMouseGridX0), # Slide to the left screen 227 | "sliddle": Function(showMouseGridX1), # Slide to the inner screen 228 | "slight": Function(showMouseGridX2), # Slide to the right screen 229 | "grid": Function(showMouseGridG0), # 2D grid 230 | "beam": Function(clickMouseGridG0), # 2D grid + left mouse click 231 | "ladder": Function(showMouseGridX0Y0), # Show a sequence of 2 grids moving in a single direction 232 | } 233 | 234 | 235 | grammar.add_rule(StartMouseGridRule()) 236 | grammar.add_rule(CenterMouseRule()) 237 | grammar.add_rule(NudgeMouseRule()) 238 | 239 | grammar.load() 240 | 241 | def unload(): 242 | global grammar 243 | if grammar: 244 | grammar.unload() 245 | grammar = None 246 | -------------------------------------------------------------------------------- /MacroSystem/programs.py: -------------------------------------------------------------------------------- 1 | # commands for controlling various programs 2 | 3 | import time 4 | from aenea import * 5 | import dragonfly 6 | 7 | from systemutils import changeToLinux 8 | from systemutils import changeToWindows 9 | 10 | 11 | gitcommand_array = [ 12 | 'add', 13 | 'branch', 14 | 'checkout', 15 | 'clone', 16 | 'commit', 17 | 'diff', 18 | 'fetch', 19 | 'init', 20 | 'log', 21 | 'merge', 22 | 'pull', 23 | 'push', 24 | 'rebase', 25 | 'reset', 26 | 'show', 27 | 'stash', 28 | 'status', 29 | 'tag', 30 | ] 31 | gitcommand = {} 32 | for command in gitcommand_array: 33 | gitcommand[command] = command 34 | 35 | 36 | # Use Linux AutoKey to switch to these programs when needed. 37 | # Since we might be jumping from the Windows VM directly to the program, make sure we also change to Linux OS and Dragon command mode. 38 | def changeWindow(command): 39 | print "Called changeWindow(" + command + ")" 40 | action = Key("ctrl:down/3, win:down/3, alt:down/3, " + command) + Key("ctrl:up, win:up, alt:up") 41 | action.execute() 42 | time.sleep(0.1) 43 | # Change to Linux, but don't add an extra update entry since we're already showing one for the application name. 44 | changeToLinux(showUpdate = False) 45 | #action = dragonfly.Mimic("switch", "to", "command", "mode") 46 | #action.execute() 47 | 48 | def changeToFirefox(): 49 | pid = aenea.communications.server.updateRecognition("") 50 | return changeWindow("f") 51 | 52 | def changeToConsole(): 53 | pid = aenea.communications.server.updateRecognition("") 54 | return changeWindow("c") 55 | 56 | def changeToText(): 57 | pid = aenea.communications.server.updateRecognition("") 58 | return changeWindow("t") 59 | 60 | def changeToSublime(): 61 | pid = aenea.communications.server.updateRecognition("") 62 | return changeWindow("s") 63 | 64 | def changeToDolphin(): 65 | pid = aenea.communications.server.updateRecognition("") 66 | return changeWindow("d") 67 | 68 | 69 | class ProgramsRule(MappingRule): 70 | mapping = { 71 | # Some custom shortcuts to open my fav applications. Use Linux AutoKey to switch to these programs when needed. 72 | "change to Firefox": Function(changeToFirefox), 73 | "change to console": Function(changeToConsole), 74 | "change to text": Function(changeToText), 75 | "change to sublime": Function(changeToSublime), 76 | "change to dolphin": Function(changeToDolphin), 77 | 78 | # Switching OSes, when Windows is in a VM on top of a Linux host: 79 | # (These functions have been moved to "_aenea.py") 80 | #"change to Linux": Key("ctrl:down/3, win:down/3, alt:down/3, l") + Key("ctrl:up, win:up, alt:up"), 81 | #"change to Windows": Key("ctrl:down/3, win:down/3, alt:down/3, w") + Key("ctrl:up, win:up, alt:up"), 82 | 83 | #"vim save": Key("escape, colon, w, enter"), 84 | #"vim quit": Key("escape, colon, q, enter"), 85 | #"vim really quit": Key("escape, colon, q, exclamation, enter"), 86 | #"vim save and quit": Key("escape, colon, w, q, enter"), 87 | #"vim split": Text(":sp "), 88 | #"vim vertical split": Text(":vs "), 89 | #"vim tab new": Text(":tabnew "), 90 | #"vim tab close": Text(":tabclose\n"), 91 | # 92 | #"vim open source": Text(":vs %<.c\n"), 93 | #"vim open source plus": Text(":vs %<.cpp\n"), 94 | #"vim open header": Text(":vs %<.h\n") + Key('c-w, c-w'), 95 | #"vim (switch|toggle|swap)": Key('c-w, c-w'), 96 | #"vim rotate": Key('c-w, r'), 97 | #"vim try that": Key('escape, colon, w, enter, a-tab/5, up, enter'), 98 | # 99 | #'screen': Key('c-a'), 100 | #'screen switch': Key('c-a, c-a'), 101 | #'screen scroll': Key('c-a, lbracket'), 102 | # 103 | #"just execute": Key("backspace, enter"), 104 | #"command (git|get)": Text("git "), 105 | #"command (git|get) ": Text("git %(gitcommand)s "), 106 | #"command vim": Text("vim "), 107 | #"command C D": Text("cd "), 108 | ##"command list": Text("ls "), 109 | #"command make": Text("make "), 110 | #"command make clean": Text("make clean "), 111 | ##"command cat": Text("cat "), 112 | #"command (grep|grip)": Text("grep "), 113 | ##"command background": Text("bg "), 114 | ##"command foreground": Text("fg "), 115 | 116 | 117 | # web browser 118 | 'browser location': Key('a-d'), 119 | 'browser refresh': Key('f5'), 120 | 'browser really refresh': Key('s-f5'), 121 | 'browser back []': Key('a-left:%(n)d'), 122 | 'browser forward []': Key('a-right:%(n)d'), 123 | 'browser previous []': Key('c-pgup:%(n)d'), 124 | 'browser next []': Key('c-pgdown:%(n)d'), 125 | #'browser new': Key('c-t'), 126 | 'browser close': Key('c-w'), 127 | 128 | ## Xfce-like desktop environment commands 129 | #'(desk|desktop) left []': Key('ca-left:%(n)d'), 130 | #'(desk|desktop) right []': Key('ca-right:%(n)d'), 131 | #'(desk|desktop) up []': Key('ca-up:%(n)d'), 132 | #'(desk|desktop) down []': Key('ca-down:%(n)d'), 133 | #'(desk|desktop) (top|upper) []': Key('c-f1, ca-left, ca-right:%(n)d'), 134 | #'(desk|desktop) (bottom|lower) []': Key('c-f1, ca-down, ca-left, ca-right:%(n)d'), 135 | #'switch window []': Key('a-tab:%(n)d'), 136 | #'really close window': Key('a-f4'), 137 | #'maximize window': Key('a-f10'), 138 | #'minimize window': Key('a-f9'), 139 | 140 | # For some universal operations whose shortcuts vary between applications, run a Linux AutoKey script that knows the correct keyboard shortcut. 141 | "find": Key("ctrl:down/3, win:down/3, alt:down/3, shift:down/3, f") + Key("ctrl:up, win:up, alt:up, shift:up"), 142 | "change to left tab [ times]": Key("ctrl:down/3, win:down/3, alt:down/3, shift:down/3, l/90:%(n)d") + Key("ctrl:up/3, win:up/3, alt:up/3, shift:up/3"), 143 | "change to right tab [ times]": Key("ctrl:down/3, win:down/3, alt:down/3, shift:down/3, r/90:%(n)d") + Key("ctrl:up/3, win:up/3, alt:up/3, shift:up/3"), 144 | "change to previous tab [ times]": Key("ctrl:down/3, win:down/3, alt:down/3, shift:down/3, p/50:%(n)d/50") + Key("ctrl:up, win:up, alt:up, shift:up"), 145 | "change to next tab [ times]": Key("ctrl:down/3, win:down/3, alt:down/3, shift:down/3, n/50:%(n)d/50") + Key("ctrl:up, win:up, alt:up, shift:up"), 146 | #"change to left times": Key("ctrl:down/3, win:down/3, alt:down/3, shift:down/3, l") + Key("ctrl:up, win:up, alt:up, shift:up"), 147 | #"change to right times": Key("ctrl:down/3, win:down/3, alt:down/3, shift:down/3, r") + Key("ctrl:up, win:up, alt:up, shift:up"), 148 | "close tab": Key("ctrl:down/3, win:down/3, alt:down/3, shift:down/3, c") + Key("ctrl:up, win:up, alt:up, shift:up"), 149 | 150 | # My current aenea proxy system isn't allowing to hold down shift or caps lock, so try using Linux AutoKey instead 151 | "press caps lock": Key("ctrl:down/3, win:down/3, alt:down/3, z") + Key("ctrl:up, win:up, alt:up"), 152 | 153 | # Moved to _aenea.py 154 | #"window list": Key("win:down/999, tab") + Key("win:up"), 155 | 156 | # I can rarely get Alt+Tab or Win+Tab working to switch windows using Aenea*(int*)0=0; // crash!, so I've mapped it to Ctrl+Shift+N as well. 157 | "swap window": Key("ctrl:down/3, shift:down/3, s") + Key("ctrl:up, shift:up"), 158 | 159 | # Konsole shortcuts 160 | #"search": Key("c-r"), # Ctrl+R 161 | #"find": Key("ctrl:down/3, shift:down/3, f") + Key("ctrl:up, shift:up"), # Ctrl+Shift+F 162 | 163 | "perforce": Text("p4"), 164 | "P 4": Text("p4"), 165 | "P 4 opened": Text("p4opened"), 166 | "P 4 diff": Text("p4diff"), 167 | "P 4 login": Text("p4 login"), 168 | "P 4 changes": Text("p4changes"), 169 | "P 4 changed": Text("p4changed"), 170 | "P 4 sync": Text("p4sync"), 171 | "SSH remote": Text("ssh -XC $REMOTE") + Key("enter"), 172 | "dollar sign remote": Text("$REMOTE"), 173 | "profile remote": Text("profile_remote whole"), 174 | "list files": Text("ls -oAFh --color=auto") + Key("enter"), 175 | 176 | #"1xA53": Text("1xA53"), 177 | #"2xA53s": Text("2xA53s"), 178 | #"3xA53s": Text("3xA53s"), 179 | #"4xA53s": Text("4xA53s"), 180 | #"1xA57": Text("1xA57"), 181 | #"2xA57s": Text("2xA57s"), 182 | #"inline": Text("SM_INLINE") + Key("f3"), 183 | #"force inline": Text("SM_FORCE_INLINE") + Key("f3"), 184 | 185 | "auto address": Key("ctrl:down/3, alt:down/3, shift:down/3, a") + Key("ctrl:up, alt:up, shift:up"), 186 | "auto gmail": Key("win:down/3, g") + Key("win:up"), 187 | "auto username": Key("win:down/3, u") + Key("win:up"), 188 | "auto mobile": Key("win:down/3, m") + Key("win:up"), 189 | "auto password": Key("ctrl:down/3, alt:down/3, shift:down/3, p") + Key("ctrl:up, alt:up, shift:up"), 190 | 191 | # Moved into DNS so it can also be used in Windows in Normal mode: 192 | #"JIRA link": Text("https://seeingmachines.atlassian.net/browse/", pause=0.05), 193 | } 194 | extras = [ 195 | Dictation("text"), 196 | IntegerRef("n", 1, 101), 197 | Choice('gitcommand', gitcommand), 198 | ] 199 | defaults = { 200 | "n": 1, 201 | } 202 | -------------------------------------------------------------------------------- /MacroSystem/mousegridutils.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # A few handy functions for using mouse grids. 3 | # By Shervin Emami (www.shervinemami.info), 2019. 4 | 5 | 6 | import operator 7 | from collections import OrderedDict # Allows ordered dictionaries even in Python 2 8 | from datetime import datetime 9 | 10 | # Our symbols are spread across multiple files. 11 | # Import the "letterMap" and similar dictionaries from the "lettermap.py" and similar files that are in the MacroSystem folder. 12 | from lettermap import letterMap 13 | from symbolmap import symbolMap1 14 | from symbolmap import symbolMap2 15 | from symbolmap import symbolMap3 16 | from punctuationmap import punctuationMap1 17 | from punctuationmap import punctuationMap2 18 | 19 | 20 | # Set the mouse button actions here, to be used in multiple places in grammars 21 | mouseActions = { 22 | "kick": 1, # Left mouseclick 23 | "middle": 2, # Middle mouseclick 24 | "psychic": 3, # Right mouseclick 25 | "high": 4, # Mouse wheel up 26 | "low": 5, # Mouse wheel down 27 | "drag-mouse": 6, # Left mousedown. "drag" sounds like "right", so it's better to say "drag-mouse" 28 | "release": 7, # Left mouseup + Middle mouseup + Right mouseup 29 | "scroll": 8, # Middle mousedown 30 | "double": 9, # Double-click left mouse button 31 | "triple": 10, # Triple-click left mouse button 32 | "control-kick": 11, # Hold down Control key while clicking the left mouse button 33 | "shift-kick": 12, # Hold down Shift key while clicking the left mouse button 34 | "alt-kick": 13, # Hold down Alt key while clicking the left mouse button 35 | "control-drag-mouse": 14, # Hold down Control key while dragging with the left mouse button 36 | "shift-drag-mouse": 15, # Hold down Shift key while dragging with the left mouse button 37 | "alt-drag-mouse": 16, # Hold down Alt key while dragging with the left mouse button 38 | "control-psychic": 17, # Hold down Control key while clicking the right mouse button 39 | "shift-psychic": 18, # Hold down Shift key while clicking the right mouse button 40 | "alt-psychic": 19, # Hold down Alt key while clicking the right mouse button 41 | } 42 | 43 | 44 | # Just a few generic dictionaries of the numbers that are likely to be used for fairly large sizes and repetition counts 45 | smallNumbers = { 46 | "zero": 0, 47 | "one": 1, 48 | "two": 2, 49 | "three": 3, 50 | "four": 4, 51 | "five": 5, 52 | "six": 6, 53 | "seven": 7, 54 | "eight": 8, 55 | "nine": 9, 56 | "ten": 10, 57 | "eleven": 11, 58 | "twelve": 12, 59 | "thirteen": 13, 60 | "fourteen": 14, 61 | "fifteen": 15, 62 | "sixteen": 16, 63 | "seventeen": 17, 64 | "eighteen": 18, 65 | "nineteen": 19, 66 | "twenty": 20, 67 | "twenty-one": 21, 68 | "twenty-two": 22, 69 | "twenty-three": 23, 70 | "twenty-four": 24, 71 | "twenty-five": 25, 72 | "twenty-six": 26, 73 | "twenty-eight": 28, 74 | "thirty": 30, 75 | "thirty-one": 31, 76 | "thirty-two": 32, 77 | "thirty-five": 35, 78 | "thirty-six": 36, 79 | "thirty-eight": 38, 80 | "forty": 40, 81 | "forty-two": 42, 82 | "forty-five": 45, 83 | "fifty": 50, 84 | } 85 | 86 | bigNumbers = { 87 | "fifty-five": 55, 88 | "sixty": 60, 89 | "sixty-five": 65, 90 | "seventy": 70, 91 | "seventy-five": 75, 92 | "eighty": 80, 93 | "eighty-five": 85, 94 | "ninety": 90, 95 | "ninety-five": 95, 96 | "ninety-nine": 99, 97 | "one-hundred": 100, 98 | "one-hundred-and-ten": 110, 99 | "one-hundred-and-fifteen": 115, 100 | "one-hundred-and-twenty": 120, 101 | "one-hundred-and-twenty five": 125, 102 | "one-hundred-and-thirty": 130, 103 | "one-hundred-and-forty": 140, 104 | "one-hundred-and-fifty": 150, 105 | "one-hundred-and-sixty": 160, 106 | "one-hundred-and-seventy": 170, 107 | "one-hundred-and-seventy five": 175, 108 | "one-hundred-and-eighty": 180, 109 | "one-hundred-and-ninety": 190, 110 | "two-hundred": 200, 111 | "two-hundred-and-twenty five": 225, 112 | "two-hundred-and-fifty": 250, 113 | "two-hundred-and-seventy five": 275, 114 | "three-hundred": 300, 115 | "three-hundred-and-fifty": 350, 116 | "four-hundred": 400, 117 | "four-hundred-and-fifty": 450, 118 | "five-hundred": 500, 119 | "five-hundred-and-fifty": 550, 120 | "six-hundred": 600, 121 | "six-hundred-and-fifty": 650, 122 | "seven-hundred": 700, 123 | "seven-hundred-and-fifty": 750, 124 | "eight-hundred": 800, 125 | "eight-hundred-and-fifty": 850, 126 | "nine-hundred": 900, 127 | "nine-hundred-and-fifty": 950, 128 | "one-thousand": 1000, 129 | "one-thousand-five-hundred": 1500, 130 | "two-thousand": 2000, 131 | } 132 | 133 | # Create a combination of small and big numbers 134 | mostNumbers = smallNumbers.copy() 135 | mostNumbers.update(bigNumbers) # merge both dictionaries 136 | 137 | 138 | 139 | # Create an ordered dictionary of all the symbols and phrases we might use in our mouse grids. 140 | # Symbols that are easier to use should be placed near the top of the list, since some screens won't need to show the symbols on the bottom of the list. 141 | def loadAllSymbols(keyboardMode, includeUpperCase = False, extendedSymbolsLevel = 1, extendedSymbolsCount = 9999999): 142 | print datetime.now(), "Loading all symbols up to level", extendedSymbolsLevel, ", count", extendedSymbolsCount, ". keyboardMode is", keyboardMode, ".", 143 | charsDict = OrderedDict() 144 | 145 | #for c in charsStart: 146 | # charsDict[c] = c 147 | #print letterMap 148 | 149 | # Make sure we don't load more symbols than what is asked 150 | c = 0 151 | 152 | # Add all the numbers first 153 | for n in range(10): 154 | if c < extendedSymbolsCount: 155 | c += 1 156 | charsDict[str(n)] = str(n) 157 | 158 | # Then add the English alphabet, using the letter mapping phrases I've stored in letterMap. 159 | # Sort the letters first, since dictionaries in Python 2 aren't sorted. 160 | #print "letterMap:", letterMap 161 | sorted_letters = sorted(letterMap.items(), key=operator.itemgetter(1)) 162 | for key, val in sorted_letters: 163 | if c < extendedSymbolsCount: 164 | c += 1 165 | #print u"(", key, u", ", val 166 | charsDict[key] = val 167 | 168 | # Now optionally add the uppercase version of every letter. 169 | if includeUpperCase: 170 | for key, val in sorted_letters: 171 | if c < extendedSymbolsCount: 172 | c += 1 173 | #print u"(maxo ", key, u", ", val.upper() 174 | charsDict["maxo " + key] = val.upper() 175 | 176 | 177 | # Then add some extra symbols that are on keyboards and are a single word (thus can be easy to say, and allows more symbols for mouse grid modes): 178 | for key, val in punctuationMap1.iteritems(): 179 | if c < extendedSymbolsCount: 180 | c += 1 181 | charsDict[key] = val 182 | 183 | # Then add many extra Unicode symbols that aren't on keyboards but are a single word (thus can be easy to say, and allows more symbols for mouse grid modes): 184 | if not keyboardMode: 185 | for key, val in symbolMap1.iteritems(): 186 | if c < extendedSymbolsCount: 187 | c += 1 188 | charsDict[key] = val 189 | 190 | # Then add the slightly harder Unicode symbols that aren't on keyboards: 191 | if not keyboardMode and extendedSymbolsLevel >= 2: 192 | for key, val in symbolMap2.iteritems(): 193 | if c < extendedSymbolsCount: 194 | c += 1 195 | charsDict[key] = val 196 | 197 | # Then add the harder symbols that are on keyboards and might not be a single word: 198 | for key, val in punctuationMap2.iteritems(): 199 | if c < extendedSymbolsCount: 200 | c += 1 201 | charsDict[key] = val 202 | 203 | # Then add the harder Unicode symbols that aren't on keyboards and might not be a single word: 204 | if not keyboardMode and extendedSymbolsLevel >= 3: 205 | for key, val in symbolMap3.iteritems(): 206 | if c < extendedSymbolsCount: 207 | c += 1 208 | charsDict[key] = val 209 | 210 | #print charsDict 211 | print "Loaded", len(charsDict), "symbols." 212 | return charsDict 213 | -------------------------------------------------------------------------------- /MacroSystem/_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 | import time 25 | 26 | import dragonfly 27 | from dragonfly.grammar.recobs import RecognitionObserver 28 | 29 | try: 30 | # Internal NatLink module for reloading grammars. 31 | import natlinkmain 32 | except ImportError: 33 | natlinkmain = None 34 | 35 | try: 36 | import aenea 37 | import aenea.proxy_contexts 38 | import aenea.configuration 39 | import aenea.communications 40 | import aenea.config 41 | import aenea.configuration 42 | except ImportError: 43 | print 'Unable to import Aenea client-side modules.' 44 | raise 45 | 46 | print 'Aenea client-side modules loaded successfully' 47 | print 'Settings:' 48 | print '\tHOST:', aenea.config.DEFAULT_SERVER_ADDRESS[0] 49 | print '\tPORT:', aenea.config.DEFAULT_SERVER_ADDRESS[1] 50 | print '\tPLATFORM:', aenea.config.PLATFORM 51 | print '\tUSE_MULTIPLE_ACTIONS:', aenea.config.USE_MULTIPLE_ACTIONS 52 | print '\tSCREEN_RESOLUTION:', aenea.config.SCREEN_RESOLUTION 53 | 54 | try: 55 | aenea.proxy_contexts._get_context() 56 | print 'Aenea: Successfully connected to server.' 57 | except: 58 | print 'Aenea: Unable to connect to server.' 59 | 60 | 61 | # Try to load the OS functions if they exist, otherwise use empty functions. 62 | def changeToLinux(): 63 | pass 64 | def changeToWindows(): 65 | pass 66 | def microphoneStateCallback(cbType, args): 67 | pass 68 | def logRecognition(msg): 69 | pass 70 | try: 71 | from systemutils import changeToLinux 72 | from systemutils import changeToWindows 73 | from systemutils import microphoneStateCallback 74 | from systemutils import logRecognition 75 | except: 76 | pass 77 | 78 | 79 | # Commands that can be rebound. 80 | command_table = [ 81 | 'set proxy server to ', 82 | 'disable proxy server', 83 | 'enable proxy server', 84 | 'force natlink to reload all grammars', 85 | 'disable keyboard', 86 | 'enable keyboard', 87 | 'shervs test', 88 | 'pause', 89 | 'play music', 90 | 'change to Linux', 91 | 'change to Windows', 92 | 'window list', 93 | 'shelf list', 94 | 'show history', # Note that "Show Recognition History" is already a native command in Dragon 95 | ] 96 | command_table = aenea.configuration.make_grammar_commands( 97 | 'aenea', 98 | dict(zip(command_table, command_table)) 99 | ) 100 | 101 | 102 | def topy(path): 103 | if path.endswith == ".pyc": 104 | return path[:-1] 105 | 106 | return path 107 | 108 | 109 | #---------------------------------------------------------------------------------------------------------- 110 | # Send all Dragon recognitions to Linux using Aenea so it can be displayed on the host machine. 111 | class LinuxRecognitionEcho(RecognitionObserver): 112 | """Handler that echoes the recognition in Linux""" 113 | 114 | #def __init__(self): 115 | # self.wordsList = [] 116 | 117 | def on_begin(self): 118 | logRecognition("...") 119 | 120 | def on_recognition(self, words): 121 | wordsString = ' '.join(words) 122 | logRecognition(wordsString) 123 | print "SPOKEN:", wordsString 124 | print 125 | 126 | def on_failure(self): 127 | logRecognition("") 128 | print "SPOKEN: " 129 | print 130 | 131 | linux_echo_handler = LinuxRecognitionEcho() 132 | 133 | # Register our recognition observer with Dragon. Note that we will also unregister it when grammars are unloaded, 134 | # otherwise there will be multiple echo handlers running at the same time! 135 | linux_echo_handler.register() 136 | 137 | 138 | #---------------------------------------------------------------------------------------------------------- 139 | # Natlink callback function for whenever the Dragon microphone changes state between awake and sleep! 140 | def changeCallback(cbType, args): 141 | print cbType, # 'mic' or 'user' 142 | print "=", 143 | print args # 'off', 'on', 'disabled' or 'sleeping'. 144 | if cbType == "mic": 145 | microphoneStateCallback(cbType, args) 146 | 147 | #---------------------------------------------------------------------------------------------------------- 148 | 149 | class DisableRule(dragonfly.CompoundRule): 150 | spec = command_table['disable proxy server'] 151 | 152 | def _process_recognition(self, node, extras): 153 | logRecognition("disable proxy server") 154 | aenea.config.disable_proxy() 155 | 156 | 157 | class EnableRule(dragonfly.CompoundRule): 158 | spec = command_table['enable proxy server'] 159 | 160 | def _process_recognition(self, node, extras): 161 | logRecognition("enable proxy server") 162 | aenea.config.enable_proxy() 163 | 164 | 165 | def unload_code(optional_blacklist = []): 166 | print "Unloading all aenea code" 167 | 168 | # Do not reload anything in these directories or their subdirectories. 169 | #blacklist_list = list("core") + optional_blacklist 170 | dir_reload_blacklist = set(["core"]) 171 | if len(optional_blacklist): 172 | dir_reload_blacklist.add(optional_blacklist) 173 | print "Blacklist: ", dir_reload_blacklist 174 | macro_dir = "C:\\NatLink\\NatLink\\MacroSystem" 175 | 176 | # Unload all grammars if natlinkmain is available. 177 | if natlinkmain and not len(optional_blacklist): 178 | natlinkmain.unloadEverything() 179 | 180 | # Unload all modules in macro_dir except for those in directories on the 181 | # blacklist. 182 | # Consider them in sorted order to try to make things as predictable as possible to ease debugging. 183 | for name, module in sorted(sys.modules.items()): 184 | if module and hasattr(module, "__file__"): 185 | # Some builtin modules only have a name so module is None or 186 | # do not have a __file__ attribute. We skip these. 187 | path = module.__file__ 188 | 189 | # Convert .pyc paths to .py paths. 190 | path = topy(path) 191 | 192 | # Do not unimport this module! This will cause major problems! 193 | if (path.startswith(macro_dir) and 194 | not bool(set(path.split(os.path.sep)) & dir_reload_blacklist) 195 | and path != topy(os.path.abspath(__file__))): 196 | 197 | print "removing %s from cache (in module %s)" % (name, module) 198 | 199 | # Remove the module from the cache so that it will be reloaded 200 | # the next time # that it is imported. The paths for packages 201 | # end with __init__.pyc so this # takes care of them as well. 202 | del sys.modules[name] 203 | 204 | def load_code(): 205 | print "Loading all aenea code" 206 | try: 207 | # Reload the top-level modules in macro_dir if natlinkmain is available. 208 | if natlinkmain: 209 | natlinkmain.findAndLoadFiles() 210 | except Exception as e: 211 | print "reloading failed: {}".format(e) 212 | else: 213 | print "finished reloading" 214 | 215 | def reload_code(): 216 | logRecognition("force natlink to reload all grammars") 217 | unload_code() 218 | load_code() 219 | 220 | 221 | def disableKeyboard(): 222 | print "Disabling just the keyboard grammar." 223 | logRecognition("disable keyboard") 224 | # Unload the modules except for all the "core" and "aenea" modules 225 | unload_code("aenea") 226 | 227 | print "Switching Dragon to Normal mode." 228 | action = dragonfly.Mimic("switch", "to", "normal", "mode") 229 | #action = dragonfly.Playback([(["switch", "to", "normal", "mode"], 0.0)]) 230 | action.execute() 231 | 232 | 233 | def enableKeyboard(): 234 | print "Enabling keyboard." 235 | logRecognition("enable keyboard") 236 | load_code() 237 | 238 | print "Switching Dragon to Command mode." 239 | action = dragonfly.Mimic("switch", "to", "command", "mode") 240 | #action = dragonfly.Playback([(["switch", "to", "command", "mode"], 0.0)]) 241 | action.execute() 242 | 243 | 244 | def shervstest(): 245 | print "Running Shervs Test!" 246 | logRecognition("shervs test") 247 | 248 | #from six.moves import xmlrpc_client 249 | #server = xmlrpc_client.ServerProxy("http://127.0.0.1:12400", allow_none=False) 250 | #remote_title = server.GetActiveWindowTitle() 251 | #print "Remote ", remote_title 252 | 253 | 254 | def pauseDragon(): 255 | print "Pausing Dragon" 256 | logRecognition("pause") 257 | # Pause Dragon, similar to saying "Stop Listening" 258 | action = dragonfly.Key("npdiv") # Numpad "/" key 259 | action.execute() 260 | 261 | # Also pause my music player 262 | #action = aenea.Key("ctrl:down, shift:down, f12") + aenea.Key("ctrl:up, shift:up") 263 | #action.execute() 264 | pid = aenea.communications.server.controlMusic("pause") 265 | 266 | 267 | def playMusic(): 268 | print "Playing music" 269 | #logRecognition("play music") 270 | # Play my music player 271 | #action = aenea.Key("ctrl:down, shift:down, f12") + aenea.Key("ctrl:up, shift:up") 272 | #action.execute() 273 | pid = aenea.communications.server.controlMusic("play") 274 | 275 | 276 | def showWindowList(): 277 | print "Showing the Linux window list." 278 | logRecognition("window list") 279 | 280 | #"show window list": Key("win:down/999, tab") + Key("win:up"), 281 | #"show window list": Key("w-l") + Key("tab") + Key("down"), 282 | #action = aenea.Key("ctrl:down, alt:down") + aenea.Key("ctrl:up, alt:up") 283 | #action = aenea.Key("ctrl:down") + aenea.Key("o") 284 | #action.execute() 285 | #time.sleep(0.3) 286 | #action = aenea.Key("tab") 287 | #action.execute() 288 | #time.sleep(0.1) 289 | #action = aenea.Key("down") 290 | #action.execute() 291 | 292 | # Run our aenea plugin script that shows the Linux window list. 293 | aenea.communications.server.showWindowList() 294 | 295 | 296 | def showHistory(): 297 | print "Showing the recognition history." 298 | 299 | # Run our aenea plugin script that shows the Dragon recognition history 300 | aenea.communications.server.showHistory() 301 | 302 | 303 | def showShelfList(): 304 | print "Showing the Linux shelf list." 305 | logRecognition("shelf list") 306 | 307 | # Run our aenea plugin script that shows the Linux clipboard shelf list. 308 | # Value -1 means show the graphical list. 309 | aenea.communications.server.shelfCommand(-1) 310 | 311 | def shelfNumber(val): 312 | print "Running shelf number", val 313 | logRecognition("shelf " + val) 314 | 315 | # Run our aenea plugin script that pastes the shelf item 316 | aenea.communications.server.shelfCommand(val) 317 | 318 | 319 | class DisableKeyboard(dragonfly.MappingRule): 320 | mapping = {command_table['disable keyboard']: dragonfly.Function(disableKeyboard)} 321 | 322 | class EnableKeyboard(dragonfly.MappingRule): 323 | mapping = {command_table['enable keyboard']: dragonfly.Function(enableKeyboard)} 324 | 325 | class ShervsTest(dragonfly.MappingRule): 326 | mapping = {command_table['shervs test']: dragonfly.Function(shervstest)} 327 | 328 | class PauseDragon(dragonfly.MappingRule): 329 | mapping = {command_table['pause']: dragonfly.Function(pauseDragon)} 330 | 331 | class PlayMusic(dragonfly.MappingRule): 332 | mapping = {command_table['play music']: dragonfly.Function(playMusic)} 333 | 334 | class ChangeToLinux(dragonfly.MappingRule): 335 | mapping = {command_table['change to Linux']: dragonfly.Function(changeToLinux)} 336 | 337 | class ChangeToWindows(dragonfly.MappingRule): 338 | mapping = {command_table['change to Windows']: dragonfly.Function(changeToWindows)} 339 | 340 | class ShowWindowList(dragonfly.MappingRule): 341 | mapping = {command_table['window list']: dragonfly.Function(showWindowList)} 342 | 343 | class ShowShelfList(dragonfly.MappingRule): 344 | mapping = {command_table['shelf list']: dragonfly.Function(showShelfList)} 345 | 346 | class ShowHistory(dragonfly.MappingRule): 347 | mapping = {command_table['show history']: dragonfly.Function(showHistory)} 348 | 349 | class ShelfNumber(dragonfly.MappingRule): 350 | mapping = { 351 | # Call shelfNumber() 352 | "shelf ": dragonfly.Function(lambda n: shelfNumber(n)) 353 | } 354 | extras = [ 355 | dragonfly.IntegerRef("n", 0, 99), 356 | ] 357 | #defaults = { 358 | # "n": 0, 359 | #} 360 | 361 | # Note that you do not need to turn mic off and then on after saying this. This 362 | # also unloads all modules and packages in the macro directory so that they will 363 | # be reloaded the next time that they are imported. It even reloads Aenea! 364 | class ReloadGrammarsRule(dragonfly.MappingRule): 365 | mapping = {command_table['force natlink to reload all grammars']: dragonfly.Function(reload_code)} 366 | 367 | server_list = dragonfly.DictList('aenea servers') 368 | server_list_watcher = aenea.configuration.ConfigWatcher( 369 | ('grammar_config', 'aenea')) 370 | 371 | 372 | class ChangeServer(dragonfly.CompoundRule): 373 | spec = command_table['set proxy server to '] 374 | extras = [dragonfly.DictListRef('proxy', server_list)] 375 | 376 | def _process_recognition(self, node, extras): 377 | aenea.communications.set_server_address((extras['proxy']['host'], extras['proxy']['port'])) 378 | 379 | def _process_begin(self): 380 | if server_list_watcher.refresh(): 381 | server_list.clear() 382 | for k, v in server_list_watcher.conf.get('servers', {}).iteritems(): 383 | server_list[str(k)] = v 384 | 385 | grammar = dragonfly.Grammar('aenea') 386 | 387 | grammar.add_rule(EnableRule()) 388 | grammar.add_rule(DisableRule()) 389 | grammar.add_rule(ReloadGrammarsRule()) 390 | grammar.add_rule(ChangeServer()) 391 | grammar.add_rule(DisableKeyboard()) 392 | grammar.add_rule(EnableKeyboard()) 393 | grammar.add_rule(ShervsTest()) 394 | grammar.add_rule(PauseDragon()) 395 | grammar.add_rule(PlayMusic()) 396 | grammar.add_rule(ChangeToLinux()) 397 | grammar.add_rule(ChangeToWindows()) 398 | grammar.add_rule(ShowWindowList()) 399 | grammar.add_rule(ShowShelfList()) 400 | grammar.add_rule(ShowHistory()) 401 | grammar.add_rule(ShelfNumber()) 402 | 403 | grammar.load() 404 | 405 | 406 | # Unload function which will be called at unload time. 407 | def unload(): 408 | global grammar 409 | if grammar: 410 | grammar.unload() 411 | grammar = None 412 | 413 | global linux_echo_handler 414 | linux_echo_handler.unregister() 415 | -------------------------------------------------------------------------------- /MacroSystem/keyboard.py: -------------------------------------------------------------------------------- 1 | # Low-level keyboard input module 2 | # Words that are recommend to manually train Dragon with: 3 | # ! exclamationmark (sometimes Dragon has trouble with the short space between words when I say "exclamation mark") 4 | # ? questionmark (sometimes Dragon has trouble with the short space between words when I say "question mark") 5 | # krife 6 | # osez 7 | # yeelax 8 | # zimeesi 9 | # 10 | # 11 | # Based on the work done by the creators of the Dictation Toolbox 12 | # https://github.com/dictation-toolbox/dragonfly-scripts 13 | # 14 | # and _multiedit-en.py found at: 15 | # http://dragonfly-modules.googlecode.com/svn/trunk/command-modules/documentation/mod-_multiedit.html 16 | # 17 | # Modifications by: Tony Grosinger and Shervin Emami. 18 | # 19 | # Licensed under LGPL 20 | 21 | from natlink import setMicState 22 | from aenea import ( 23 | Grammar, 24 | MappingRule, 25 | Text, 26 | Key, 27 | Mimic, 28 | Function, 29 | Dictation, 30 | Choice, 31 | Window, 32 | Config, 33 | Section, 34 | Item, 35 | IntegerRef, 36 | Alternative, 37 | RuleRef, 38 | Repetition, 39 | CompoundRule, 40 | AppContext, 41 | ) 42 | 43 | from lettermap import letterMap 44 | import copy 45 | 46 | from dragonfly.actions.keyboard import keyboard 47 | from dragonfly.actions.typeables import typeables 48 | if 'semicolon' not in typeables: 49 | typeables["semicolon"] = keyboard.get_typeable(char=';') 50 | 51 | 52 | # Note that this doesn't include the less common keys such as the "window" key. 53 | release = Key("shift:up, ctrl:up, alt:up") 54 | 55 | 56 | def cancel_and_sleep(text=None, text2=None): 57 | """Used to cancel an ongoing dictation and puts microphone to sleep. 58 | 59 | This method notifies the user that the dictation was in fact canceled, 60 | a message in the Natlink feedback window. 61 | Then the the microphone is put to sleep. 62 | Example: 63 | "'random mumbling go to sleep'" => Microphone sleep. 64 | 65 | """ 66 | print("* Dictation canceled. Going to sleep. *") 67 | setMicState("sleeping") 68 | 69 | 70 | # For repeating of characters. 71 | specialCharMap = { 72 | "pipe": "|", 73 | "minus": "-", 74 | "dot": ".", 75 | "comma": ",", 76 | "backslash": "\\", 77 | "underscore": "_", 78 | "(asterisk|Asterix)": "*", 79 | "colon": ":", 80 | "(semicolon|semi-colon)": ";", 81 | "at symbol": "@", 82 | #"[double] quote": '"', 83 | "quotes": '"', 84 | "single quote": "'", 85 | "apostrophe": "'", 86 | "hash": "#", 87 | "dollar sign": "$", 88 | "percentage": "%", 89 | "ampersand": "&", 90 | "slash": "/", 91 | "equals": "=", 92 | "plus": "+", 93 | "space": " ", 94 | "exclamationmark": "!", # "bang" sounds like "aim" that I might use for "a" 95 | #"bang": "!", 96 | "questionmark": "?", 97 | "caret": "^", 98 | "tilde": "~", 99 | "back tick": "`", 100 | 101 | 102 | # some other symbols I haven't imported yet, lazy sorry 103 | # 'ampersand': Key('ampersand'), 104 | # 'apostrophe': Key('apostrophe'), 105 | # 'asterisk': Key('asterisk'), 106 | # 'at': Key('at'), 107 | # 'backslash': Key('backslash'), 108 | # 'backtick': Key('backtick'), 109 | # 'bar': Key('bar'), 110 | # 'caret': Key('caret'), 111 | # 'colon': Key('colon'), 112 | # 'comma': Key('comma'), 113 | # 'dollar': Key('dollar'), 114 | # #'(dot|period)': Key('dot'), 115 | # 'double quote': Key('dquote'), 116 | # 'equal': Key('equal'), 117 | # 'bang': Key('exclamation'), 118 | # 'hash': Key('hash'), 119 | # 'hyphen': Key('hyphen'), 120 | # 'minus': Key('minus'), 121 | # 'percent': Key('percent'), 122 | # 'plus': Key('plus'), 123 | # 'question': Key('question'), 124 | # # Getting Invalid key name: 'semicolon' 125 | # #'semicolon': Key('semicolon'), 126 | # 'slash': Key('slash'), 127 | # '[single] quote': Key('squote'), 128 | # 'tilde': Key('tilde'), 129 | # 'underscore | score': Key('underscore'), 130 | } 131 | 132 | # All the keys that can be pressed with the Window key down. 133 | #windowCharMap = { 134 | # "space": Key("space"), 135 | # "up": Key("up"), 136 | # "down": Key("down"), 137 | # "left": Key("left"), 138 | # "right": Key("right"), 139 | # "enter": Key("enter"), 140 | # "tab": Key("tab"), 141 | # "insert": Key("insert"), 142 | # "1": Text("1"), 143 | # "2": Text("2"), 144 | # "3": Text("3"), 145 | # "4": Text("4"), 146 | # "5": Text("5"), 147 | #} 148 | 149 | 150 | ## Modifiers for the press-command. 151 | #modifierMap = { 152 | # "alt": "a", 153 | # "control": "c", 154 | # "shift": "s", 155 | # "super": "w", 156 | #} 157 | # 158 | ## Modifiers for the press-command, if only the modifier is pressed. 159 | #singleModifierMap = { 160 | # "alt": "alt", 161 | # "control": "ctrl", 162 | # "shift": "shift", 163 | # "super": "win", 164 | #} 165 | 166 | # letterMap = dictionary in separate "letterMap.py" file, so it can also be used by the "practice_mappings.py" tool. 167 | 168 | # generate uppercase versions of every letter 169 | upperLetterMap = {} 170 | for letter in letterMap: 171 | upperLetterMap["maxo " + letter] = letterMap[letter].upper() # 172 | #upperLetterMap["roof " + letter] = letterMap[letter].upper() # My "roof zimeesi" fails 173 | #upperLetterMap["biggie " + letter] = letterMap[letter].upper() # My "biggie" is like video 174 | #upperLetterMap["buzz " + letter] = letterMap[letter].upper() # My "buzz" is like "plus" 175 | #upperLetterMap["fig " + letter] = letterMap[letter].upper() # My "fig char" fails 176 | #upperLetterMap["gross " + letter] = letterMap[letter].upper() # My "gross" is like "quotes" 177 | #upperLetterMap["bam " + letter] = letterMap[letter].upper() # My "bam" is like "end" 178 | #upperLetterMap["big " + letter] = letterMap[letter].upper() # My "big" is pretty good, but usually fails "big yeelax" 179 | #upperLetterMap["case " + letter] = letterMap[letter].upper() # My "case" is too much like "plus" 180 | #upperLetterMap["capital " + letter] = letterMap[letter].upper() # My "cap" is too much like "up" 181 | #upperLetterMap["sky " + letter] = letterMap[letter].upper() # My "sky" is too much like "score" :-( 182 | 183 | # Generate a dictionary containing both the lowercase and uppercase alphabet 184 | letterMapBothCases = copy.deepcopy(letterMap) 185 | letterMapBothCases.update(upperLetterMap) 186 | 187 | 188 | def handle_word(text): 189 | #words = map(list, text) 190 | #print text 191 | words = str(text).split() 192 | print 'word (', words, ')' 193 | if len(words) > 0: 194 | Text(words[0]).execute() 195 | if len(words) > 1: 196 | Mimic(' '.join(words[1:])).execute() 197 | 198 | 199 | grammarCfg = Config("multi edit") 200 | grammarCfg.cmd = Section("Language section") 201 | grammarCfg.cmd.map = Item( 202 | { 203 | # Navigation keys. 204 | "up [ times]": Key("up:%(n)d/10"), 205 | "down [ times]": Key("down:%(n)d/10"), 206 | "left [ times]": Key("left:%(n)d/10"), 207 | "right [ times]": Key("right:%(n)d/10"), 208 | "page up [ times]": Key("pgup:%(n)d"), 209 | "page down [ times]": Key("pgdown:%(n)d"), 210 | "jump [ times]": Key("pgup:%(n)d"), 211 | "drop [ times]": Key("pgdown:%(n)d"), 212 | "jump half": Key("up:30/10"), # Half of a page up/down. When I say "jump half" quickly, Dragon hears it as a single word, so we also support that. 213 | "jumpuff": Key("up:30/10"), 214 | "drop half": Key("down:30/10"), 215 | "drohpuff": Key("down:30/10"), 216 | #"up (page|pages)": Key("pgup:%(n)d"), 217 | #"down (page|pages)": Key("pgdown:%(n)d"), 218 | #"left (word|words)": Key("c-left/3:%(n)d/10"), 219 | #"right (word|words)": Key("c-right/3:%(n)d/10"), 220 | "home": Key("home"), 221 | "end": Key("end"), 222 | "insert": Key("insert"), 223 | 224 | # Other special keys that could be nice to have, but might not be supported so far: 225 | #Caps_Lock 226 | #Alt_R 227 | #KP_Insert 228 | #Redo 229 | #XF86AudioPlay 230 | #XF86AudioNext 231 | #XF86AudioForward 232 | #XF86AudioPause 233 | #XF86AudioRaiseVolume 234 | #XF86AudioLowerVolume 235 | #XF86Back 236 | #KP_Next 237 | #Scroll_Lock 238 | #XF86MonBrightnessUp 239 | #XF86MonBrightnessDown 240 | #XF86ScrollUp 241 | #XF86ScrollDown 242 | #Insert 243 | #Next 244 | #XF86Next 245 | #XF86AudioMute 246 | #f1 ... f24 247 | #Print 248 | #Pause 249 | #"doc home": Key("c-home/3"), 250 | #"doc end": Key("c-end/3"), 251 | # Functional keys. 252 | #"space": release + Key("space"), 253 | "space [ times]": release + Key("space:%(n)d"), 254 | "(enter) [ times]": release + Key("enter:%(n)d"), 255 | "tab [ times]": Key("tab:%(n)d"), 256 | #"delete this line": Key("home, s-end, del"), # @IgnorePep8 257 | "backspace [ times]": release + Key("backspace:%(n)d"), 258 | #"application key": release + Key("apps/3"), 259 | #"paste [that]": Function(paste_command), 260 | #"copy [that]": Function(copy_command), 261 | #"cut [that]": release + Key("c-x/3"), 262 | #"select all": release + Key("c-a/3"), 263 | #"[(hold|press)] met": Key("alt:down/3"), 264 | 265 | # Function keys. For some reason the functionKeyMap above isn't working for me. 266 | 'F one': Key('f1'), 267 | 'F two': Key('f2'), 268 | 'F three': Key('f3'), 269 | 'F four': Key('f4'), 270 | 'F five': Key('f5'), 271 | 'F six': Key('f6'), 272 | 'F seven': Key('f7'), 273 | 'F eight': Key('f8'), 274 | 'F nine': Key('f9'), 275 | 'F ten': Key('f10'), 276 | 'F eleven': Key('f11'), 277 | 'F twelve': Key('f12'), 278 | 279 | #"window": Key("win:down/3"), 280 | #"win key": release + Key("win/3"), 281 | #"window ": Key("win:down") + Text("%(windowChars)s") + Key("win:up"), 282 | #"window run": Key("win:down/3") + Text("r") + Key("win:up"), 283 | #"release window": Key("win:up"), 284 | #"window []": Key("win:down/3") + Text("%(num)d") + Key("win:up"), # Allow to say "window 2" to switch to the 2nd window 285 | "window 1": Key("win:down/3") + Text("1") + Key("win:up"), # Allow to say "window 2" to switch to the 2nd window 286 | "window 2": Key("win:down/3") + Text("2") + Key("win:up"), # Allow to say "window 2" to switch to the 2nd window 287 | "window 3": Key("win:down/3") + Text("3") + Key("win:up"), # Allow to say "window 2" to switch to the 2nd window 288 | "window 4": Key("win:down/3") + Text("4") + Key("win:up"), # Allow to say "window 2" to switch to the 2nd window 289 | "window 5": Key("win:down/3") + Text("5") + Key("win:up"), # Allow to say "window 2" to switch to the 2nd window 290 | "window 6": Key("win:down/3") + Text("6") + Key("win:up"), # Allow to say "window 2" to switch to the 2nd window 291 | "window 7": Key("win:down/3") + Text("7") + Key("win:up"), # Allow to say "window 2" to switch to the 2nd window 292 | "window space": Key("win:down") + Key("space") + Key("win:up"), 293 | "window up": Key("win:down") + Key("up") + Key("win:up"), 294 | "window down": Key("win:down") + Key("down") + Key("win:up"), 295 | "window left": Key("win:down") + Key("left") + Key("win:up"), 296 | "window right": Key("win:down") + Key("right") + Key("win:up"), 297 | "window enter": Key("win:down") + Key("enter") + Key("win:up"), 298 | "window tab": Key("win:down") + Key("tab") + Key("win:up"), 299 | #"window tab": Key("w-tab/30"), 300 | "window insert": Key("win:down") + Key("insert") + Key("win:up"), 301 | "window ": Key("win:down") + Text("%(letters)s") + Key("win:up"), 302 | # Moved to _aenea.py 303 | #"window list": Key("win:down/999, tab") + Key("win:up"), 304 | 305 | # Switch between Linux & Windows through the Synergy application 306 | "gravy": Key("ctrl:down/3") + Key("backtick") + Key("ctrl:up"), 307 | "porridge": Key("ctrl:down/3") + Key("tilde") + Key("ctrl:up"), 308 | 309 | "meta []": Key("alt:down/1") + Text("%(num)d") + release, # Allow to say "meta 2" to hit Alt+2 to switch to the 2nd tab of Firefox, etc 310 | #"meta tab": Key("a-tab/20"), 311 | #meta": Key("alt:down/3"), # Or do I prefer "alter"? 312 | "meta 1": Key("alt:down/3") + Text("1") + release, # Allow to say "meta 2" to hit Alt+2 to switch to the 2nd tab of Firefox, etc 313 | "meta 2": Key("alt:down/3") + Text("2") + release, # Allow to say "meta 2" to hit Alt+2 to switch to the 2nd tab of Firefox, etc 314 | "meta 3": Key("alt:down/3") + Text("3") + release, # Allow to say "meta 2" to hit Alt+2 to switch to the 2nd tab of Firefox, etc 315 | "meta 4": Key("alt:down/3") + Text("4") + release, # Allow to say "meta 2" to hit Alt+2 to switch to the 2nd tab of Firefox, etc 316 | "meta 5": Key("alt:down/3") + Text("5") + release, # Allow to say "meta 2" to hit Alt+2 to switch to the 2nd tab of Firefox, etc 317 | "meta 6": Key("alt:down/3") + Text("6") + release, # Allow to say "meta 2" to hit Alt+2 to switch to the 2nd tab of Firefox, etc 318 | "meta 7": Key("alt:down/3") + Text("7") + release, # Allow to say "meta 2" to hit Alt+2 to switch to the 2nd tab of Firefox, etc 319 | #"meta 8": Key("alt:down/3") + Text("8") + release, # Allow to say "meta 2" to hit Alt+2 to switch to the 2nd tab of Firefox, etc 320 | "meta 9": Key("alt:down/3") + Text("9") + release, # Allow to say "meta 2" to hit Alt+2 to switch to the 2nd tab of Firefox, etc 321 | #"hold met": Key("alt:down/3"), 322 | #"release met": Key("alt:up"), 323 | 324 | # My current aenea proxy system isn't allowing to hold down shift or caps lock, so I'm using Linux AutoKey instead. See "programs.py" 325 | "shift": Key("shift:down/3"), 326 | #"hold shift": Key("shift:down"), 327 | #"release shift": Key("shift:up"), 328 | "control": Key("ctrl:down/3"), 329 | #"hold control": Key("ctrl:down"), 330 | #"release control": Key("ctrl:up"), 331 | "release all": Key("shift:up, ctrl:up, alt:up, win:up, shift:down, shift:up, ctrl:down, ctrl:up, alt:down, alt:up, win:down, win:up"), 332 | #"press key ": Key("%(pressKey)s"), 333 | 334 | # Closures. 335 | #"angle brackets": Key("langle, rangle, left/3"), 336 | #"[square] brackets": Key("lbracket, rbracket, left/3"), 337 | #"[curly] braces": Key("lbrace, rbrace, left/3"), 338 | #"(parens|parentheses)": Key("lparen, rparen, left/3"), 339 | #"quotes": Key("dquote/3, dquote/3, left/3"), 340 | #"backticks": Key("backtick:2, left"), 341 | #"single quotes": Key("squote, squote, left/3"), 342 | # Shorthand multiple characters. 343 | #"double ": Text("%(char)s%(char)s"), 344 | #"triple ": Text("%(char)s%(char)s%(char)s"), 345 | #"double escape": Key("escape, escape"), # Exiting menus. 346 | # Punctuation and separation characters, for quick editing. 347 | "colon": Key("colon"), 348 | "semi-colon": Key("semicolon"), 349 | "comma": Key("comma"), 350 | "dot": Key("dot"), # cannot be followed by a repeat count 351 | "dotdot": Key("dot") + Key("dot"), # Added because Dragon often doesn't recognize my "dot dot dot" sequences 352 | "dotdotdot": Key("dot") + Key("dot") + Key("dot"), 353 | "full stop": Key("dot"), # cannot be followed by a repeat count 354 | "point []": Key("dot") + Text("%(digit)d") + Text("%(digit2)d"), # allow to say "number 1.23" 355 | "(dash|minus)": Key("hyphen"), 356 | "underscore": Key("underscore"), 357 | 358 | # These are needed by this grammar, otherwise many of these rules won't work! 359 | "": Text("%(letters)s"), 360 | "": Text("%(char)s"), 361 | 362 | 'less than': Key('langle'), # angle bracket 363 | #'langle': Key('langle:%(n)d'), 364 | 'curly brace': Key('lbrace'), # curly brace 365 | 'square bracket': Key('lbracket'), # square bracket 366 | 'round bracket': Key('lparen'), # round parenthesis 367 | 'greater than': Key('rangle'), 368 | #'rangle': Key('rangle'), 369 | 'close curly': Key('rbrace'), 370 | 'close square': Key('rbracket'), 371 | 'close round': Key('rparen'), 372 | 'close bracket': Key('rparen'), # Only included here because otherwise, it causes "Close Dragon"! 373 | 374 | "escape": Key("escape"), 375 | #"escape 2 ": Key("escape") + Key("escape"), 376 | "cancel": Key("escape"), 377 | 378 | 'delete [ times]': Key('del:%(n)d'), 379 | #'chuck []': Key('del:%(n)d'), 380 | #'scratch []': Key('backspace:%(n)d'), 381 | 382 | #"visual": Key("v"), 383 | #"visual line": Key("s-v"), 384 | #"visual block": Key("c-v"), 385 | #"doc save": Key("c-s"), 386 | #"(arrow|pointer)": Text("->"), 387 | 388 | #'fly []': Key('pgup:%(n)d'), 389 | #'drop []': Key('pgdown:%(n)d'), 390 | 391 | #'lope []': Key('c-left:%(n)d'), 392 | #'(yope|rope) []': Key('c-right:%(n)d'), 393 | #'(hill scratch|hatch) []': Key('c-backspace:%(n)d'), 394 | 395 | #'hexadecimal': Text("0x"), 396 | #'suspend': Key('c-z'), 397 | #'undo': Key('c-z'), # Sounds too much like "end" 398 | "geez [ times]": Key('c-z:%(n)d'), 399 | 400 | #'word ': Function(handle_word), 401 | 'number ': Text("%(num)d"), 402 | #'zero': Text("0"), # Allowed to say "zero" instead of "number zero", since it's common to need many zeros 403 | '': Text("%(digit)d"), # Allowed to say "zero" ... "nine" instead of "number zero" ..., since it's common to need a single digit 404 | #'change to ': Key("home, slash") + Text("%(text)s") + Key("enter, c, e") + Text("%(text2)s") + Key("escape"), 405 | 406 | # Text corrections. 407 | #"again": Key("ctrl:down/3, shift:down/3, left") + Key("ctrl:up, shift:up"), # Type over a word 408 | #"fix missing space": Key("c-left/3, space, c-right/3"), 409 | #"remove extra space": Key("c-left/3, backspace, c-right/3"), # @IgnorePep8 410 | #"remove extra character": Key("c-left/3, del, c-right/3"), # @IgnorePep8 411 | # Microphone sleep/cancel started dictation. 412 | #"[] (go to sleep|cancel and sleep) []": Function(cancel_and_sleep), # @IgnorePep8 413 | }, 414 | namespace={ 415 | "Key": Key, 416 | "Text": Text, 417 | } 418 | ) 419 | 420 | 421 | class KeystrokeRule(MappingRule): 422 | exported = False 423 | mapping = grammarCfg.cmd.map 424 | extras = [ 425 | IntegerRef("n", 1, 101), 426 | IntegerRef("num", 0, 101), 427 | IntegerRef("digit", 0, 10), 428 | IntegerRef("digit2", 0, 10), 429 | Dictation("text"), 430 | #Dictation("text2"), 431 | Choice("char", specialCharMap), 432 | Choice("letters", letterMapBothCases), 433 | #Choice("modifier1", modifierMap), 434 | #Choice("modifier2", modifierMap), 435 | #Choice("modifierSingle", singleModifierMap), 436 | #Choice("windowChars", windowCharMap), 437 | ] 438 | defaults = { 439 | "n": 1, 440 | } 441 | -------------------------------------------------------------------------------- /plugins/standalone_grids.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # Modified version of Caster's grids.py, to include a "BeamGrid" mode for Aenea on Linux. 3 | # Modified by Shervin Emami (www.shervinemami.info), 2019. 4 | 5 | from __future__ import division 6 | 7 | from SimpleXMLRPCServer import SimpleXMLRPCServer 8 | from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler 9 | import xmlrpclib 10 | 11 | import getopt 12 | import signal 13 | import sys 14 | import os 15 | from threading import Timer 16 | import time 17 | #import curses 18 | from datetime import datetime 19 | import subprocess 20 | from collections import OrderedDict # Allows ordered dictionaries even in Python 2 21 | import operator 22 | 23 | #import win32api 24 | 25 | # These 2 lines are different for Python 2 vs 3 26 | import Tkinter as tk 27 | import tkFont as tkfont 28 | 29 | from pynput.mouse import Button, Controller 30 | mouse = Controller() 31 | 32 | # Import the "letterMap" and similar dictionaries from the "lettermap.py" and similar files that are in the MacroSystem folder. 33 | # Make sure you adjust this path to where it's located on your machine, relative to this script. 34 | sys.path.append('../../../my_dragonfly_grammar/MacroSystem') 35 | from mousegridutils import loadAllSymbols 36 | 37 | 38 | # Set the IP address & port of the mouse grid XMLRPC server and the invisible window XMLRPC server 39 | MOUSE_SERVER_ADDRESS = ("192.168.56.1", 8000) # Set up our server address 40 | INVISIBLEWINDOW_SERVER_ADDRESS = 'http://192.168.56.1:8001' # Set up our client address 41 | windowServer = xmlrpclib.ServerProxy(INVISIBLEWINDOW_SERVER_ADDRESS) 42 | 43 | # Set the desired font of the 1D grids. It's very beneficial if this can be the same size as the font you use in your main apps & IDEs, 44 | # so you can select every single character on your screen or in your IDE or console terminal. 45 | fontFamily = "DejaVu Sans Mono" #"Liberation Mono" 46 | fontSize = 10 47 | fontWeight = "normal" 48 | 49 | # For the 1D grids, set these to the fixed character size of your chosen font. 50 | charWidth = 10 51 | charHeight = 15 52 | 53 | 54 | TRANSPARENCY_LEVEL = 0.8 55 | 56 | # If there aren't enough characters to cover your whole screen, it can use different colored grids of text 57 | textColors = ['Blue', 'Green', 'Red', 'Dark Violet', 'Brown4', 'Black'] 58 | textNames = ['Blue', 'Green', 'Red', 'Purple', 'Brown', 'Black'] 59 | 60 | 61 | #from dragonfly import monitors 62 | 63 | #try: # Style C -- may be imported into Caster, or externally 64 | # BASE_PATH = os.path.realpath(__file__).rsplit(os.path.sep + "caster", 1)[0] 65 | # if BASE_PATH not in sys.path: 66 | # sys.path.append(BASE_PATH) 67 | #finally: 68 | # from caster.lib import settings, utilities 69 | # from caster.lib.dfplus.communication import Communicator 70 | 71 | #try: 72 | # from PIL import ImageGrab, ImageTk, ImageDraw, ImageFont 73 | #except ImportError: 74 | # print("Douglas Grid / Rainbow Grid / Beam Grid", "PIL") 75 | 76 | 77 | #def wait_for_death(title, timeout=5): 78 | # t = 0.0 79 | # inc = 0.1 80 | # while t < timeout: 81 | # if not utilities.window_exists(None, title): 82 | # break 83 | # t += inc 84 | # time.sleep(inc) 85 | # if t >= timeout: 86 | # print("wait_for_death()" + " timed out (" + title + ")") 87 | 88 | 89 | class KeyEvent: 90 | def __init__(self, char, keysym, keycode): 91 | self.char = char 92 | self.keysym = keysym 93 | self.keycode = keycode 94 | 95 | class MouseButtonEvent: 96 | def __init__(self, x_root, y_root, button): 97 | self.x_root = x_root 98 | self.y_root = y_root 99 | self.button = button 100 | 101 | # Allow receiving input as direct RPC data instead of as keycodes, since many extra symbols don't have corresponding key codes. 102 | class SymbolEvent: 103 | def __init__(self, phrase, symbol): 104 | self.phrase = phrase 105 | self.symbol = symbol 106 | 107 | class Dimensions: 108 | def __init__(self, w, h, x, y): 109 | self.width = w 110 | self.height = h 111 | self.x = x 112 | self.y = y 113 | 114 | # Restrict XMLRPC server to a particular path. 115 | class RequestHandler(SimpleXMLRPCRequestHandler): 116 | rpc_paths = ('/RPC2',) 117 | 118 | 119 | # rewrite dp grid using this 120 | class TkTransparent(tk.Tk): 121 | def reset_xs_ys(self): 122 | self.xs = [] 123 | self.ys = [] 124 | 125 | def xs_ys_filled(self): 126 | return len(self.xs) > 0 or len(self.ys) > 0 127 | 128 | def get_dimensions_fullscreen(self): 129 | return Dimensions(self.winfo_screenwidth(), self.winfo_screenheight(), 0, 0) 130 | 131 | def get_dimensions_string(self): 132 | return "%dx%d+%d+%d" % (self.dimensions.width, self.dimensions.height, 133 | self.dimensions.x, self.dimensions.y) 134 | 135 | def __init__(self, name, dimensions=None, canvas=True, axis='Ignored', serviceMode=False, keyboardMode=False): 136 | tk.Tk.__init__(self, baseName="TkTransparent") 137 | self.setup_xmlrpc_server() 138 | if not dimensions: 139 | dimensions = self.get_dimensions_fullscreen() 140 | self.wm_geometry("1x1+0+0") # Start by showing a tiny pixel sized window that isn't obvious 141 | self.dimensions = dimensions 142 | 143 | # Load all symbols including the hard ones. 144 | print 145 | self.charsDict = loadAllSymbols(keyboardMode, extendedSymbolsLevel=100000) 146 | 147 | self.reset_xs_ys() 148 | self.overrideredirect(True) # Disable window borders, but also hide from the window manager 149 | self.resizable(False, False) 150 | self.wm_attributes("-topmost", True) 151 | self.title(name) 152 | self.wm_title(name) 153 | #self.client(name) 154 | self.transient() # Give further hint that this window shouldn't be shown in the Windows taskbar 155 | self.wait_visibility(self) 156 | self.attributes("-alpha", TRANSPARENCY_LEVEL) # Show this whole window as semi-transparent. This line must be after "wait_visibility". 157 | self.wm_geometry(self.get_dimensions_string()) # Make this window fullscreen. 158 | self.mouseGridMode = "" 159 | self.highlightColumn = -1 160 | self.timerMidway = None 161 | self.queueMouseClick = -1 162 | self.keyboardMode = keyboardMode 163 | 164 | # Generate the unicode charString that can be rendered easily. 165 | self.charsString = u"" 166 | for v in self.charsDict.values(): 167 | self.charsString = self.charsString + v 168 | #print u"String of characters:", self.charsString 169 | 170 | if canvas: 171 | self._canvas = tk.Canvas( 172 | master=self, 173 | width=dimensions.width, 174 | height=dimensions.height, 175 | bg='white', 176 | bd=-2) 177 | #self._canvas.pack() 178 | self.protocol("WM_DELETE_WINDOW", self.xmlrpc_kill) 179 | 180 | # Close this window if any mouse or keyboard events happen. (Not expected conditions) 181 | self.bind("", self.mouseButton1_callback) 182 | self.bind("", self.mouseButton2_callback) 183 | self.bind("", self.mouseButton3_callback) 184 | self.bind("", self.key_callback) 185 | self._canvas.pack() 186 | 187 | # self.mainloop() #do this in the child classes 188 | 189 | # Get the XMLRPC server to start in the background quite soon 190 | def start_server(): 191 | while not self.server_quit: 192 | self.server._handle_request_noblock() 193 | Timer(0.1, start_server).start() 194 | 195 | # Possibly get the window to hide in the background on startup, to be used as an XMLRPC service 196 | self.hideForServiceMode = serviceMode 197 | 198 | 199 | def mouseButton1_callback(self, event): 200 | self.mouse_callback(event, 1) 201 | 202 | def mouseButton2_callback(self, event): 203 | self.mouse_callback(event, 2) 204 | 205 | def mouseButton3_callback(self, event): 206 | self.mouse_callback(event, 3) 207 | 208 | def mouse_callback(self, event, button): 209 | #print event 210 | #hide unhide 211 | #self._canvas.focus_set() 212 | print 213 | print datetime.now(), "BeamGrid base clicked at (%d,%d) using button %d" % (event.x_root, event.y_root, button) 214 | #TkTransparent.die(self) 215 | self.hide() 216 | windowServer.hide() 217 | 218 | # Now that our windows are hidden, pass the mouse click to the window underneath 219 | def forward_mouse_click(): 220 | print datetime.now(), "Forwarding the mouse button click", button 221 | btnCode = Button.left 222 | if button == 2: 223 | btnCode = Button.middle 224 | elif button == 3: 225 | btnCode = Button.right 226 | mouse.click(btnCode, 1) 227 | Timer(0.1, forward_mouse_click).start() 228 | 229 | self.clearGridHighlight() 230 | 231 | 232 | def setup_xmlrpc_server(self): 233 | self.server_quit = 0 234 | print datetime.now(), "Setting up the base XMLRPC mouse grid server at", MOUSE_SERVER_ADDRESS 235 | self.server = SimpleXMLRPCServer(MOUSE_SERVER_ADDRESS, requestHandler=RequestHandler, allow_none=True) 236 | self.server.register_function(self.xmlrpc_kill, "kill") 237 | #TODO: Disable this for security when not debugging: 238 | #self.server.register_introspection_functions() 239 | self.server.register_function(self.xmlrpc_injectSymbol, "injectSymbol") 240 | self.server.register_function(self.xmlrpc_injectSymbols, "injectSymbols") 241 | self.server.register_function(self.xmlrpc_move_mouse, "move_mouse") 242 | self.server.register_function(self.xmlrpc_move_mouse_relative, "move_mouse_relative") 243 | self.server.register_function(self.xmlrpc_keypress, "keypress") 244 | self.server.register_function(self.xmlrpc_showMouseGrid, "showMouseGrid") 245 | self.server.register_function(self.xmlrpc_moveToCenter, "moveToCenter") 246 | 247 | 248 | def xmlrpc_moveToCenter(self): 249 | print 250 | print datetime.now(), "BeamGrid base XMLRPC mouse grid server in moveToCenter()" 251 | self.moveMouse(self.dimensions.width/2, self.dimensions.height/2) 252 | 253 | def xmlrpc_move_mouse(self, x, y): 254 | print 255 | print datetime.now(), "BeamGrid base XMLRPC mouse grid server in move_mouse(%d,%d)" % (x, y) 256 | self.moveMouse(x, y) 257 | 258 | def xmlrpc_move_mouse_relative(self, dx, dy): 259 | print 260 | print datetime.now(), "BeamGrid base XMLRPC mouse grid server in move_mouse_relative(%d,%d)" % (dx, dy) 261 | # Read the current mouse cursor position 262 | oldMousePosition = mouse.position 263 | print datetime.now(), "Mouse currently is at: ", oldMousePosition 264 | mx = oldMousePosition[0] + dx 265 | my = oldMousePosition[1] + dy 266 | 267 | # Move the actual mouse cursor, using pynput 268 | print datetime.now(), "Moving mouse cursor to position (%d,%d)" % (mx, my) 269 | mouse.position = (mx, my) 270 | 271 | 272 | def xmlrpc_showMouseGrid(self, gridMode, mouseClick): 273 | self.mouseGridMode = gridMode 274 | self.queueMouseClick = mouseClick 275 | print 276 | print datetime.now(), "BeamGrid base XMLRPC mouse grid server in showMouseGrid(mode='%s', click=%s)" % (self.mouseGridMode, mouseClick) 277 | 278 | # Allow to show the mouse grid along X, Y or both directions 279 | self.axis = '' 280 | if len(self.mouseGridMode) > 0: 281 | self.axis = self.mouseGridMode[0] 282 | 283 | # Reset the 2D grid stage 284 | self.gridStage = 0 285 | self.gridStageX = -1 286 | self.gridStageY = -1 287 | 288 | # Allow to show the mouse grid on different parts of the screen or different screens 289 | self.level = 0 290 | if len(self.mouseGridMode) > 1: 291 | self.level = int(self.mouseGridMode[1]) # Can be x0, x1, etc 292 | 293 | # Show the mouse grid 294 | if self.axis == 'x' or self.axis == 'y' or self.axis == 'g': 295 | # The 2D grid is extremely slow (~1 second!) to render while it's visible, but is fast to render while it's not visible. 296 | # Clearing the background is also slow. 297 | # So only clear & render it from scratch if we actually need to modify the content. 298 | if (self.previousContent != self.axis) or (self.axis == 'g' and self.previousContent == 'g' and self.highlightColumn >= 0): 299 | print datetime.now(), "Clearing the rendering" 300 | self.withdraw() # Stop displaying the window, but don't destroy it 301 | # Possibly clear the grid display, if a section was being highlighted 302 | alreadyCleared = self.clearGridHighlight() 303 | if not alreadyCleared: 304 | self._canvas.delete("all") 305 | self.draw() 306 | if self.axis == 'x': 307 | t = "Full" # X grid shows lots of symbols 308 | else: 309 | t = "Mini" # Y grid and 2D only show about half of the symbols 310 | windowServer.setWindowTitle("InvisibleWindow - " + t) 311 | self.unhide() # Display the window onto the screen 312 | self.deiconify() # Display the window 313 | else: 314 | # This axis is ready for display, so simply display it 315 | #self.draw() 316 | self.unhide() # Display the window onto the screen 317 | else: 318 | print datetime.now(), "Unknown axis!", self.axis 319 | 320 | 321 | def xmlrpc_injectSymbol(self, event): 322 | print 323 | print datetime.now(), "BeamGrid base XMLRPC mouse grid server received symbol injection", event 324 | #print "PHRASE:", event['phrase'] 325 | self.processCharacter(symbol=event['symbol']) 326 | #print "DONE." 327 | 328 | def xmlrpc_injectSymbols(self, event1, event2): 329 | print 330 | print datetime.now(), "BeamGrid base XMLRPC mouse grid server received symbols injection", event1, event2 331 | self.processCharacter(symbol=event1['symbol']) 332 | # Only process the 2nd symbol if we know we're in a 2D grid mode and are accepting 2 inputs 333 | if event2 and self.axis == 'g' and self.gridStage == 1: 334 | print datetime.now(), "Processing 2nd symbol that was injected" 335 | self.processCharacter(symbol=event2['symbol']) 336 | else: 337 | print datetime.now(), "Skipping 2nd symbol that was injected, since we're not expecting 2 inputs" 338 | 339 | 340 | def xmlrpc_keypress(self, event): 341 | print datetime.now(), "XMLRPC base mouse grid server received keypress event", event 342 | # If the user is trying to cancel the grid, make sure we don't queue a mouse click at the end of it 343 | if event['keysym'] == 'escape': 344 | self.queueMouseClick = -1 345 | else: 346 | # If it's in keyboard mode, allow to click mouse buttons by hitting special keys, since there's no other way to mouse click on a keyboard. 347 | if self.keyboardMode: 348 | btnCode = 0 349 | if event['keysym'] == 'Return': 350 | print datetime.now(), "Adding a left mouse click to be performed after the mouse move" 351 | btnCode = 1 # Left mouse click 352 | elif event['keysym'] == 'space': 353 | print datetime.now(), "Adding a right mouse click to be performed after the mouse move" 354 | btnCode = 3 # Right mouse click 355 | if btnCode > 0: 356 | # Queue the mouse click for later 357 | self.queueMouseClick = btnCode 358 | return 359 | 360 | # Send a key event 361 | event2 = KeyEvent(event['char'], event['keysym'], event['keycode']) 362 | self.key_callback(event2) 363 | 364 | def key_callback(self, event): 365 | print datetime.now(), "BeamGrid base keyboard event, pressing the key '%s'. keysym='%s', keycode=%s" % (event.char, event.keysym, event.keycode) 366 | # Send the keyboard character. Except for the rare case such as the "and" (∧) character that InvisibleWindow is sending as a keysym. 367 | if len(event.char) > 0: 368 | self.processCharacter(symbol=event.char) 369 | else: 370 | self.processCharacter(phrase=event.keysym) 371 | 372 | 373 | def hideBothWindows(self): 374 | # Hide this mouse grid window 375 | self.hide() 376 | 377 | # Hide the InvisibleWindow (so it doesn't steal keyboard focus) very soon but not yet, 378 | # otherwise the InvisibleWindow RPC for sending keyboard events might get stuck here! 379 | def hide_windowServer(): 380 | #windowServer = xmlrpclib.ServerProxy(INVISIBLEWINDOW_SERVER_ADDRESS) 381 | # Sometimes this causes a "CannotSendRequest" error in xmlrpc lib, even if it works fine, 382 | # so we wrap it in a try/except block 383 | try: 384 | windowServer.hide() 385 | except: 386 | pass 387 | Timer(0.15, hide_windowServer).start() 388 | 389 | 390 | # Possibly clear the grid display, if a section was being highlighted 391 | def clearGridHighlight(self): 392 | if self.timerMidway: 393 | self.timerMidway.cancel() 394 | self.timerMidway = None 395 | print datetime.now(), "Cancelling the midway highlighting" 396 | if self.highlightColumn >= 0: 397 | print datetime.now(), "Clearing the highlighting display" 398 | self.highlightColumn = -1 399 | self._canvas.delete("all") 400 | if self.axis == 'g': 401 | self.draw_grid2D() 402 | return True 403 | return False 404 | 405 | 406 | # Handle a character, that was either typed on a keyboard or spoken by voice 407 | def processCharacter(self, char="", symbol=""): 408 | #print datetime.now(), "In processCharacter(), axis", self.axis 409 | closeGrid = False 410 | if self.axis == 'x' or self.axis == 'y': 411 | 412 | # Calculate the X and Y symbol number for the given typed character or symbol 413 | c = self.calc1DIndex(symbol=symbol) 414 | 415 | # Move the actual mouse pointer 416 | (mx, my) = self.getPixelFromChar1D(c) 417 | self.moveMouse(mx, my) 418 | 419 | # We have processed one axis, now see if we should process another axis. 420 | print datetime.now(), "Finished processing axis", self.axis 421 | 422 | # Either switch to another mouse grid axis, or hide our 2 windows 423 | if len(self.mouseGridMode) > 2 and c >= 0: # mouseGridMode can be something like "x0y0" 424 | print datetime.now(), "Since mouseGridMode is", self.mouseGridMode, ", we will process the remaining mode now." 425 | def processNextMode(): 426 | # Remove the 2 characters of the grid mode that was just processed. 427 | self.mouseGridMode = self.mouseGridMode[2:] 428 | #self.axis = 'y' 429 | #self.axis = 'None' 430 | # Occasionally the invisible window seems to hide() and lose focus before switching grid modes. 431 | # So let's make sure the invisible window has focus just before we switch modes. 432 | windowServer.unhide() 433 | print datetime.now(), "Changing to mouseGridMode '%s'" % (self.mouseGridMode) 434 | self.xmlrpc_showMouseGrid(self.mouseGridMode, False) 435 | Timer(0.15, processNextMode).start() 436 | else: 437 | closeGrid = True 438 | 439 | elif self.axis == 'g': 440 | 441 | # Calculate the X and Y symbol number for the given typed character or symbol 442 | c = self.calc1DIndex(symbol=symbol) 443 | 444 | if c >= 0: 445 | # The first letter is for the X axis, the 2nd letter is for the Y axis. 446 | if self.gridStage == 0: 447 | print datetime.now(), "Now that we have the X axis for the 2D grid, prepare for the Y axis." 448 | self.gridStageX = c 449 | self.gridStage = 1 # Prepare for the next axis 450 | 451 | # If the user sits in this temporary middle state for a while, highlight the grid to 452 | # show that we're in the middle state and they should either cancel, or say one more symbol. 453 | def highlightMidway(): 454 | self._canvas.delete("all") 455 | self.highlightColumn = self.gridStageX 456 | print datetime.now(), "Highlighting column", self.highlightColumn, "to make sure the user knows they should either cancel or say another symbol" 457 | self.draw_grid2D() 458 | #self.attributes("-alpha", 0.4) # Show this whole window as semi-transparent. This line must be after "wait_visibility". 459 | self.deiconify() # Display the window 460 | self.timerMidway = None 461 | # Keep hold of the threading.Timer object so we can potentially cancel the highlighting later, 462 | # if the user does something before we need to highlight it. 463 | self.timerMidway = Timer(1.0, highlightMidway) 464 | self.timerMidway.start() 465 | print datetime.now(), "Starting a timer to eventually show highlighting" 466 | elif self.gridStage == 1: 467 | print datetime.now(), "Now that we have both the X axis and Y axis for the 2D grid, move the actual mouse pointer." 468 | self.gridStageY = c 469 | 470 | (mx, my) = self.getPixelFromChar2D(self.gridStageX, self.gridStageY) 471 | 472 | # Now that we have both the X and Y value, move the actual mouse pointer 473 | self.moveMouse(mx, my) 474 | 475 | closeGrid = True 476 | else: 477 | # They probably cancelled the grid or made a mistake, so close the grid. 478 | closeGrid = True 479 | 480 | if closeGrid: 481 | print datetime.now(), "Finished processing axis", self.axis 482 | self.hideBothWindows() 483 | self.gridStage = 0 # Reset 484 | 485 | # Possibly clear the grid display, if a section was being highlighted 486 | self.clearGridHighlight() 487 | 488 | # Possibly press a mouse button that has been scheduled to happen after the mouse has been moved 489 | if self.queueMouseClick > 0: 490 | print datetime.now(), "Performing the queued mouse button", self.queueMouseClick, "click" 491 | btnCode = Button.left 492 | if self.queueMouseClick == 2: 493 | btnCode = Button.middle 494 | elif self.queueMouseClick == 3: 495 | btnCode = Button.right 496 | mouse.click(btnCode, 1) 497 | self.queueMouseClick = -1 498 | 499 | 500 | def pre_redraw(self): 501 | '''gets the window ready to be redrawn''' 502 | self.deiconify() # Displays the window. Only needed if you have used iconify or withdraw 503 | self._canvas.delete("all") 504 | 505 | def unhide(self): 506 | '''''' 507 | self.deiconify() # Display the window 508 | self.lift() 509 | time.sleep(0.1) 510 | self.focus_force() 511 | self.focus_set() 512 | self.focus() 513 | 514 | def hide(self): 515 | self.withdraw() # Stop displaying the window, but don't destroy it 516 | 517 | def xmlrpc_kill(self): 518 | print datetime.now(), "XMLRPC base mouse grid server received kill event" 519 | self.after(2, self.die) 520 | 521 | def die(self): 522 | print datetime.now(), "Closing the base grid" 523 | self.server_quit = 1 524 | self.destroy() 525 | # Kill the 2 grid windows, and wait till the signal has been dispatched. 526 | #subprocess.call("pkill -f -9 standalone_grids.py", shell=True) 527 | subprocess.call("pkill -f -9 invisibleWindow.py", shell=True) 528 | os.kill(os.getpid(), signal.SIGTERM) 529 | import sys 530 | sys.exit() 531 | 532 | @staticmethod 533 | def move_mouse(mx, my): 534 | win32api.SetCursorPos((mx, my)) 535 | 536 | 537 | class RainbowGrid(TkTransparent): 538 | def __init__(self, grid_size=None, square_size=None, square_alpha=None, axis='Ignored', serviceMode=False, keyboardMode=False): 539 | '''square_size is an integer''' 540 | TkTransparent.__init__(self, "Rainbow", grid_size) 541 | self.attributes("-alpha", 0.5) 542 | self.square_size = 37 #square_size if square_size else 37 543 | self.square_alpha = 125 #square_alpha if square_alpha else 125 544 | print "RainbowGrid using a square_size of", self.square_size 545 | self.colors = [ 546 | (255, 0, 0, self.square_alpha), # red 547 | (187, 122, 0, self.square_alpha), # orange 255, 165, 0 548 | (255, 255, 0, self.square_alpha), # yellow 549 | (0, 128, 0, self.square_alpha), # green 550 | (0, 0, 125, self.square_alpha), # blue 551 | (128, 0, 128, self.square_alpha) # purple 552 | ] 553 | self.position_index = None 554 | 555 | self.info_pre = 0 556 | self.info_color = 0 557 | self.info_num = 0 558 | 559 | self.refresh() 560 | self.mainloop() 561 | 562 | def refresh(self): 563 | '''thread safe''' 564 | self.hide() 565 | self.after(10, self.draw) 566 | 567 | def finalize(self): 568 | self.imgtk = ImageTk.PhotoImage(self.img) 569 | self._canvas.create_image( 570 | self.dimensions.width/2, self.dimensions.height/2, image=self.imgtk) 571 | 572 | def setup_xmlrpc_server(self): 573 | TkTransparent.setup_xmlrpc_server(self) 574 | #self.server.register_function(self.xmlrpc_move_mouse, "move_mouse") 575 | 576 | def xmlrpc_move_mouse(self, pre, color, num): 577 | if pre > 0: 578 | pre -= 1 579 | selected_index = self.position_index[color + pre*len(self.colors)][num] 580 | self.move_mouse(selected_index[0] + self.dimensions.x, 581 | selected_index[1] + self.dimensions.y) 582 | 583 | def fill_xs_ys(self): 584 | # only figure out the coordinates of the lines once 585 | if not self.xs_ys_filled(): 586 | for x in range(0, int(self.dimensions.width/self.square_size) + 2): 587 | self.xs.append(x*self.square_size) 588 | for y in range(0, int(self.dimensions.height/self.square_size) + 2): 589 | self.ys.append(y*self.square_size) 590 | self.position_index = [] 591 | # add first "color": 592 | self.position_index.append([]) 593 | 594 | def draw(self): 595 | self.pre_redraw() 596 | #self.img = ImageGrab.grab([ 597 | # self.dimensions.x, self.dimensions.y, 598 | # self.dimensions.x + self.dimensions.width, 599 | # self.dimensions.y + self.dimensions.height 600 | #]) # .filter(ImageFilter.BLUR) 601 | self.draw_squares() 602 | self.finalize() 603 | self.unhide() 604 | 605 | def draw_squares(self): 606 | self.fill_xs_ys() 607 | # 608 | 609 | text_background_buffer = int(self.square_size/6) 610 | xs_size = len(self.xs) 611 | ys_size = len(self.ys) 612 | box_number = 0 613 | colors_index = 0 614 | font = ImageFont.truetype("arialbd.ttf", 15) 615 | draw = ImageDraw.Draw(self.img, 'RGBA') 616 | 617 | for ly in range(0, ys_size - 1): 618 | for lx in range(0, xs_size - 1): 619 | txt = str(box_number) 620 | tw, th = draw.textsize(txt, font) 621 | text_x = int((self.xs[lx] + self.xs[lx + 1] - tw)/2) + 1 622 | text_y = int((self.ys[ly] + self.ys[ly + 1] - th)/2) - 1 623 | draw.rectangle( 624 | [ 625 | self.xs[lx] + text_background_buffer, 626 | self.ys[ly] + text_background_buffer, 627 | self.xs[lx + 1] - text_background_buffer, 628 | self.ys[ly + 1] - text_background_buffer 629 | ], 630 | fill=self.colors[colors_index], 631 | outline=False) 632 | 633 | draw.text((text_x + 1, text_y + 1), txt, (0, 0, 0), font=font) 634 | draw.text((text_x - 1, text_y + 1), txt, (0, 0, 0), font=font) 635 | draw.text((text_x + 1, text_y - 1), txt, (0, 0, 0), font=font) 636 | draw.text((text_x - 1, text_y - 1), txt, (0, 0, 0), font=font) 637 | draw.text((text_x, text_y), txt, (255, 255, 255), font=font) 638 | # index the position 639 | self.position_index[len(self.position_index) - 1].append( 640 | (int((self.xs[lx] + self.xs[lx + 1])/2), 641 | int((self.ys[ly] + self.ys[ly + 1])/2))) 642 | 643 | # update for next iteration 644 | box_number += 1 645 | if box_number == 100: 646 | # next color 647 | box_number = 0 648 | colors_index += 1 649 | colors_index %= len(self.colors) # cycle colors 650 | self.position_index.append([]) 651 | 652 | del draw 653 | 654 | 655 | class DouglasGrid(TkTransparent): 656 | def __init__(self, grid_size=None, square_size=None, axis='Ignored', serviceMode=False, keyboardMode=False): 657 | TkTransparent.__init__(self, "DouglasGrid", grid_size) 658 | self.square_size = square_size if square_size else 25 659 | print "DouglasGrid using a square_size of", self.square_size 660 | 661 | self.draw() 662 | self.mainloop() 663 | 664 | def setup_xmlrpc_server(self): 665 | TkTransparent.setup_xmlrpc_server(self) 666 | #self.server.register_function(self.xmlrpc_move_mouse, "move_mouse") 667 | 668 | def xmlrpc_move_mouse(self, x, y): 669 | DouglasGrid.move_mouse( 670 | x*self.square_size + int(self.square_size/2) + self.dimensions.x, 671 | y*self.square_size + int(self.square_size/2) + self.dimensions.y) 672 | 673 | def draw(self): 674 | self.pre_redraw() 675 | self.draw_lines_and_numbers() 676 | self.unhide() 677 | 678 | def fill_xs_ys(self): 679 | # only figure out the coordinates of the lines once 680 | if not self.xs_ys_filled(): 681 | for x in range(0, int(self.dimensions.width/self.square_size) + 2): 682 | self.xs.append(x*self.square_size) 683 | for y in range(0, int(self.dimensions.height/self.square_size)): 684 | self.ys.append(y*self.square_size) 685 | 686 | def draw_lines_and_numbers(self): 687 | 688 | self.fill_xs_ys() 689 | 690 | text_background_buffer = int(self.square_size/10) 691 | xs_size = len(self.xs) 692 | for lx in range(0, xs_size): 693 | fill = "black" 694 | if lx % 3: 695 | fill = "gray" 696 | self._canvas.create_line( 697 | self.xs[lx], 0, self.xs[lx], self.dimensions.height, fill=fill) 698 | if lx + 1 < xs_size: 699 | self._canvas.create_rectangle( 700 | self.xs[lx] + text_background_buffer, 701 | 0 + text_background_buffer, 702 | self.xs[lx + 1] - text_background_buffer, 703 | self.square_size - text_background_buffer, 704 | fill='Black') 705 | self._canvas.create_rectangle( 706 | self.xs[lx] + text_background_buffer, 707 | self.dimensions.height - self.square_size + text_background_buffer, 708 | self.xs[lx + 1] - text_background_buffer, 709 | self.dimensions.height - text_background_buffer, 710 | fill='Black') 711 | text_x = int((self.xs[lx] + self.xs[lx + 1])/2) 712 | self._canvas.create_text( 713 | text_x, 714 | int(self.square_size/2), 715 | text=str(lx), 716 | font="Arial 10 bold", 717 | fill='White') 718 | self._canvas.create_text( 719 | text_x, 720 | self.dimensions.height - int(self.square_size/2), 721 | text=str(lx), 722 | font="Arial 10 bold", 723 | fill='White') 724 | 725 | ys_size = len(self.ys) 726 | for ly in range(0, ys_size): 727 | fill = "black" 728 | if ly % 3: 729 | fill = "gray" 730 | self._canvas.create_line( 731 | 0, self.ys[ly], self.dimensions.width, self.ys[ly], fill=fill) 732 | if ly + 1 < ys_size and ly != 0: 733 | self._canvas.create_rectangle( 734 | 0 + text_background_buffer, 735 | self.ys[ly] + text_background_buffer, 736 | self.square_size - text_background_buffer, 737 | self.ys[ly + 1] - text_background_buffer, 738 | fill='Black') 739 | self._canvas.create_rectangle( 740 | self.dimensions.width - self.square_size + text_background_buffer, 741 | self.ys[ly] + text_background_buffer, 742 | self.dimensions.width - text_background_buffer, 743 | self.ys[ly + 1] - text_background_buffer, 744 | fill='Black') 745 | text_y = int((self.ys[ly] + self.ys[ly + 1])/2) 746 | self._canvas.create_text( 747 | int(self.square_size/2), 748 | text_y, 749 | text=str(ly), 750 | font="Arial 10 bold", 751 | fill='White') 752 | self._canvas.create_text( 753 | self.dimensions.width - int(self.square_size/2), 754 | text_y, 755 | text=str(ly), 756 | font="Arial 10 bold", 757 | fill='White') 758 | 759 | 760 | # For 1D grids: Show a fullscreen grid with single character wide columns or rows, so you say something like "five" for column 5. 761 | # For 2D grids: Show a fullscreen grid with 2-character boxes, so you say something like "5 3" for cell 5 across and 3 down. 762 | class BeamGrid(TkTransparent): 763 | def __init__(self, grid_size=None, square_size=None, level=0, axis='x', serviceMode=False, keyboardMode=False): 764 | TkTransparent.__init__(self, "BeamGrid", grid_size, serviceMode=serviceMode, keyboardMode=keyboardMode) 765 | self.axis = axis 766 | self.level = level 767 | self.previousContent = 'None' 768 | self.keyboardMode = keyboardMode 769 | 770 | # Reset the 2D grid stage 771 | self.gridStage = 0 772 | self.gridStageX = -1 773 | self.gridStageY = -1 774 | 775 | # Set up the geometry 776 | if square_size == None: 777 | square_size = 22 # Default for 2D grids. Not used for 1D grids. 778 | self.cellWidth = square_size 779 | self.cellHeight = square_size - 4 # Squeeze a bit more cells vertically 780 | self.fontString = fontFamily + " " + str(fontSize) + " " + fontWeight 781 | self.fontObject = tkfont.Font(family=fontFamily, size=fontSize, weight=fontWeight) 782 | self.charWidth = self.fontObject.measure("0") 783 | self.charHeight = self.fontObject.metrics("linespace") 784 | print datetime.now(), "BeamGrid using a square_size of", self.cellWidth, "x", self.cellHeight, "on a screen size of", self.dimensions.width, "x", self.dimensions.height 785 | 786 | # If the measurements above don't work on your system, you can hardcode charWidth & charHeight here. 787 | # eg: On Linux Mint 19 Cinnamon, font 'Monospace 10 normal' is 10x19 pixels for each character. 788 | #charWidth = 10 789 | #charHeight = 19 790 | self.charHeight = self.charHeight - 4 791 | print datetime.now(), "Character size for font (", self.fontString, ") is", self.charWidth, "x", self.charHeight, "pixels" 792 | 793 | # Get the number of characters that would fit on the screen (rounded up) 794 | self.xs_size = int((self.dimensions.width + self.charWidth) / self.charWidth) 795 | self.ys_size = int((self.dimensions.height + self.charHeight) / self.charHeight) 796 | 797 | # Render 798 | self.pre_redraw() 799 | self.draw() 800 | windowServer.setWindowTitle("InvisibleWindow - 1D") # Initialize the titlebar for the initial grid mode, to support cached info in showMouseGrid() 801 | #self.unhide() # Display the window onto the screen 802 | print datetime.now(), "Rendering the BeamGrid window" 803 | self.mainloop() 804 | 805 | 806 | def draw(self): 807 | #self.pre_redraw() 808 | if self.axis == 'x': 809 | self.draw_gridX() 810 | elif self.axis == 'y': 811 | self.draw_gridY() 812 | elif self.axis == 'g': 813 | self.draw_grid2D() 814 | 815 | # Possibly get the window to hide in the background on startup, to be used as an XMLRPC service 816 | if self.hideForServiceMode: 817 | self.deiconify() # Display the window 818 | self.hide() 819 | self.hideForServiceMode = False 820 | 821 | # Keep track of the axis that was last displayed, so we won't need to clear it out to render it again. 822 | self.previousContent = self.axis 823 | 824 | 825 | def draw_gridX(self): 826 | # Rendering all rows as a single multi-line string is likely to run faster than rendering each line one at a time, 827 | # but since TK on some OSes (eg: Linux X Windows) adds 1 or 2 pixels of extra padding above and below each line, 828 | # we will get more densely packed text if we render each line separately, without the padding. 829 | print datetime.now(), "Number of characters that fit on the screen:", self.xs_size-1, "x", self.ys_size-1 830 | self.gridWidth = len(self.charsString) * self.charWidth 831 | self.gridHeight = self.ys_size * self.charHeight 832 | numGridsWide = int((self.dimensions.width + self.gridWidth - 1) / self.gridWidth) 833 | numGridsHigh = int((self.dimensions.height + self.gridHeight - 1) / self.gridHeight) 834 | print datetime.now(), "Number of grids:", numGridsWide, "x", numGridsHigh, ". grid level: ", self.level 835 | if numGridsWide > len(textColors): 836 | print datetime.now(), "ERROR: Screen is too big for your font size! Please use smaller font size (charWidth and charHeight)!" 837 | #sys.exit(1) 838 | 839 | # Render the whole grid of text, one row at a time 840 | gx = self.level 841 | #for gx in range(0, numGridsWide): 842 | if gx >= 0 and gx < numGridsWide: 843 | gy = 0 844 | offsetX = 0 845 | offsetY = -3 846 | for ly in range(0, self.ys_size-1): 847 | text_x = (offsetX) + (gx * self.gridWidth ) 848 | text_y = (offsetY) + (gy * self.gridHeight) + (ly * self.charHeight) 849 | self._canvas.create_text( 850 | text_x, 851 | text_y, 852 | text=self.charsString, 853 | #font="Arial 10 bold", 854 | font=self.fontObject, 855 | anchor=tk.NW, # Measure from the top-left 856 | #anchor=tk.CENTER, 857 | fill=textColors[gx]) 858 | 859 | 860 | def draw_gridY(self): 861 | # Rendering all rows as a single multi-line string is likely to run faster than rendering each line one at a time, 862 | # but since TK on some OSes (eg: Linux X Windows) adds 1 or 2 pixels of extra padding above and below each line, 863 | # we will get more densely packed text if we render each line separately, without the padding. 864 | print datetime.now(), "Number of characters that fit on the screen:", self.xs_size-1, "x", self.ys_size-1 865 | numRows = min(self.ys_size, len(self.charsString)) # Try to fit all characters down the screen, but the screen might be too small 866 | self.gridWidth = self.xs_size * self.charWidth 867 | self.gridHeight = numRows * self.charHeight 868 | numGridsWide = int((self.dimensions.width + self.gridWidth - 1) / self.gridWidth) 869 | numGridsHigh = int((self.dimensions.height + self.gridHeight - 1) / self.gridHeight) 870 | print datetime.now(), "Number of grids:", numGridsWide, "x", numGridsHigh, ". grid level: ", self.level 871 | if numGridsHigh > len(textColors): 872 | print datetime.now(), "ERROR: Screen is too big for your font size! Please use smaller font size (charWidth and charHeight)!" 873 | #sys.exit(1) 874 | 875 | # Render the whole grid of text, one row at a time 876 | gy = self.level 877 | #for gy in range(0, numGridsHigh): 878 | if gy >= 0 and gy < numGridsHigh: 879 | gx = 0 880 | offsetX = 0 881 | offsetY = -3 882 | for ly in range(0, numRows-1): 883 | rowString = self.charsString[ly] * self.xs_size # Generate a row full of a single character 884 | text_x = (offsetX) + (gx * self.gridWidth ) 885 | text_y = (offsetY) + (gy * self.gridHeight) + (ly * self.charHeight) 886 | self._canvas.create_text( 887 | text_x, 888 | text_y, 889 | text=rowString, 890 | #font="Arial 10 bold", 891 | font=self.fontObject, 892 | anchor=tk.NW, # Measure from the top-left 893 | #anchor=tk.CENTER, 894 | fill=textColors[gy]) 895 | 896 | 897 | def fill_xs_ys(self): 898 | # only figure out the coordinates of the lines once 899 | if not self.xs_ys_filled(): 900 | for x in range(0, int(self.dimensions.width/self.cellWidth) + 2): 901 | self.xs.append(x*self.cellWidth) 902 | for y in range(0, int(self.dimensions.height/self.cellHeight) + 2): 903 | self.ys.append(y*self.cellHeight) 904 | 905 | def draw_grid2D(self): 906 | self.fill_xs_ys() 907 | #print "square_size = ", self.square_size, self.xs, self.ys, self.dimensions.width, self.dimensions.height 908 | 909 | gridColor = "gray" 910 | if self.gridStage == 1: 911 | gridColor = "green" 912 | 913 | self.gridWidth = self.cellWidth * len(self.charsString) 914 | self.gridHeight = self.cellHeight * len(self.charsString) 915 | 916 | # Draw the grid bars 917 | xs_size = len(self.xs) 918 | ys_size = len(self.ys) 919 | print datetime.now(), "Number of symbols we can shown on the screen:", xs_size-1, "x", ys_size-1 920 | 921 | for lx in range(0, xs_size): 922 | fill = "black" 923 | if lx % 3: 924 | fill = gridColor 925 | self._canvas.create_line( 926 | self.xs[lx], 0, self.xs[lx], self.dimensions.height, fill=fill) 927 | for ly in range(0, ys_size): 928 | fill = "black" 929 | if ly % 3: 930 | fill = gridColor 931 | self._canvas.create_line( 932 | 0, self.ys[ly], self.dimensions.width, self.ys[ly], fill=fill) 933 | 934 | xs_size = min(xs_size, len(self.charsString)) 935 | ys_size = min(ys_size, len(self.charsString)) 936 | 937 | #text_background_buffer = int(self.square_size/10) 938 | 939 | # Draw the text. Since we're calling 'create_text' for each cell, this code is quite slow, especially if 940 | # the window is visible (causing a screen repaint for each cell!!) 941 | for ly in range(0, ys_size): 942 | for lx in range(0, xs_size): 943 | if self.highlightColumn < 0 or self.highlightColumn == lx: 944 | if lx + 1 < xs_size and ly + 1 < ys_size: 945 | text_x = int((self.xs[lx] + self.xs[lx + 1])/2) + self.level * self.gridWidth 946 | text_y = int((self.ys[ly] + self.ys[ly + 1])/2) 947 | self._canvas.create_text( 948 | text_x, 949 | text_y, 950 | text=self.charsString[lx % len(self.charsString)] + self.charsString[ly % len(self.charsString)], 951 | #font="Arial 10 bold", 952 | font=self.fontObject, 953 | #anchor=tk.NW, # Measure from the top-left 954 | anchor=tk.CENTER, 955 | fill=textColors[0]) 956 | 957 | 958 | # Calculate the symbol index for the given character (if provided) or phrase 959 | def calc1DIndex(self, symbol="", phrase=""): 960 | #print datetime.now(), self.charsDict 961 | # Get the character position they said 962 | if len(symbol) > 0: 963 | # For plain keyboard letters such as 'a', search by the letter, not the spoken phrase. 964 | #c = self.charsString.find(char) 965 | try: 966 | c = self.charsDict.values().index(symbol) # Replace with list(self.charsDict.keys()).index(symbol) for Python 3 967 | except: 968 | print datetime.now(), u"Couldn't find symbol", symbol 969 | c = -1 970 | if c >= 0: 971 | print datetime.now(), u"They said character", symbol.encode('utf-8'), u"=", c 972 | else: 973 | print datetime.now(), u"Couldn't find character", symbol.encode('utf-8') 974 | return -1 975 | elif len(phrase) > 0: 976 | # For custom symbols, search for the spoken phrase. 977 | #c = self.charsString.find(phrase) 978 | try: 979 | c = self.charsDict.keys().index(phrase) # Replace with list(self.charsDict.keys()).index(phrase) for Python 3 980 | except: 981 | print datetime.now(), u"Couldn't find phrase", phrase 982 | return -1 983 | print datetime.now(), u"They said", phrase, u"which appears as", self.charsDict[phrase], u"at position", c 984 | else: 985 | print datetime.now(), "ERROR: Need either a symbol or a phrase!" 986 | return -1 987 | return c 988 | 989 | 990 | def getPixelFromChar1D(self, c): 991 | print datetime.now(), "Mouse grid mode is currently:", self.axis, ". Grid level is:", self.level 992 | 993 | # Move either in x or y direction 994 | mx = -1 995 | my = -1 996 | if self.axis == "y": 997 | # Get the screen pixel coord. 998 | my = int((c + 0.5) * self.charHeight + 0.5) + (self.level * self.gridHeight) 999 | return (mx, my) 1000 | elif self.axis == "x": 1001 | # Get the screen pixel coord. 1002 | mx = int((c + 0.5) * self.charWidth + 0.5) + (self.level * self.gridWidth) 1003 | return (mx, my) 1004 | else: 1005 | print datetime.now(), "ERROR in standalone_grids.py: Unknown mouse axis!" 1006 | return (-1, -1) 1007 | if mx < 0 and my < 0: 1008 | print datetime.now(), "ERROR in standalone_grids.py on server: Unknown mouse input!" 1009 | return (-1, -1) 1010 | 1011 | 1012 | def getPixelFromChar2D(self, cx, cy): 1013 | # Get the screen pixel coord for the cell with the 2 given characters 1014 | mx = int((cx + 0.5) * self.cellWidth + 0.5) + (self.level * self.gridWidth) # Assume level is only horizontal 1015 | my = int((cy + 0.5) * self.cellHeight + 0.5) + 0 1016 | return (mx, my) 1017 | 1018 | 1019 | def moveMouse(self, mx, my): 1020 | # Read the current mouse cursor position, to allow moving in just 1 direction 1021 | oldMousePosition = mouse.position 1022 | print datetime.now(), "Mouse currently is at: ", oldMousePosition 1023 | if mx < 0: 1024 | mx = int(oldMousePosition[0]) 1025 | if my < 0: 1026 | my = int(oldMousePosition[1]) 1027 | 1028 | # Move the actual mouse cursor, using pynput 1029 | print datetime.now(), "Moving mouse cursor to position (%d,%d)" % (mx, my) 1030 | mouse.position = (mx, my) 1031 | 1032 | 1033 | 1034 | def main(argv): 1035 | help_message = "Usage: grids.py -g [-m ] [-s|-v [-k|-u]] [-c ]\n" \ 1036 | "where is one of:\n" \ 1037 | " r\t rainbow grid\n" \ 1038 | " d\t douglas grid\n" \ 1039 | " g\t 2D grid\n" \ 1040 | " x or y\t 1D grid\n" \ 1041 | " x0y0\t 1D x grid followed by 1D y grid\n" \ 1042 | "where CELL_SIZE should be 10-100\n" \ 1043 | "other flags:\n" \ 1044 | "-s\t enables service mode\n" \ 1045 | "-v\t disables service mode\n" \ 1046 | "-k\t enables keyboard-only symbols\n" \ 1047 | "-u\t disables keyboard-only symbols\n" \ 1048 | "" 1049 | try: 1050 | opts, args = getopt.getopt(argv, "hg:m:svkuc:") 1051 | except getopt.GetoptError: 1052 | print(help_message) 1053 | sys.exit(2) 1054 | 1055 | g = None 1056 | m = 1 1057 | level = 0 1058 | axis = 'x' 1059 | serviceMode = False 1060 | keyboardMode = False 1061 | square_size = None 1062 | 1063 | #print "opts", opts 1064 | for opt, arg in opts: 1065 | #print "opt", opt 1066 | if opt == '-h': 1067 | print(help_message) 1068 | sys.exit() 1069 | elif opt == '-g': 1070 | if arg == "r": 1071 | g = RainbowGrid 1072 | elif arg == 'd': 1073 | g = DouglasGrid 1074 | elif arg[0] == 'g': 1075 | g = BeamGrid 1076 | axis = arg[0] 1077 | #if len(opts[0]) > 1 and len(opts[0][1]) > 1: 1078 | #opt = opts[0][1] 1079 | if len(arg) > 1: 1080 | level = int(arg[1]) # Can be g0, g1, etc 1081 | elif arg[0] == 'x': 1082 | g = BeamGrid 1083 | axis = arg[0] 1084 | if len(arg) > 1: 1085 | level = int(arg[1]) # Can be x0, x1, etc 1086 | elif arg[0] == 'y': 1087 | g = BeamGrid 1088 | axis = arg[0] 1089 | if len(arg) > 1: 1090 | level = int(arg[1]) # Can be y0, y1, etc 1091 | #print "done -g" 1092 | elif opt == '-m': 1093 | m = arg 1094 | elif opt == '-s': 1095 | print datetime.now(), "Enabling service mode" 1096 | serviceMode = True 1097 | elif opt == '-k': 1098 | print datetime.now(), "Enabling keyboard mode" 1099 | keyboardMode = True 1100 | elif opt == '-c': 1101 | square_size = int(arg) 1102 | 1103 | if g is None: 1104 | raise ValueError("Grid mode not specified.") 1105 | 1106 | grid_size = Dimensions(1920, 1080, 0, 0) 1107 | g(grid_size=grid_size, square_size=square_size, level=level, axis=axis, serviceMode=serviceMode, keyboardMode=keyboardMode) 1108 | 1109 | 1110 | if __name__ == '__main__': 1111 | print datetime.now(), "Creating a grid window" 1112 | main(sys.argv[1:]) 1113 | 1114 | --------------------------------------------------------------------------------