"""natlinkutils.py
Python Macro Language for Dragon NaturallySpeaking
(c) Copyright 1999 by Joel Gould
Portions (c) Copyright 1999 by Dragon Systems, Inc.
This file contains utility classes and functions for grammar files.
Grammar classes
===============
GrammarBase - base class for all command and control grammars.
See documentation below just before the class definition.
DictGramBase - base class for all pure dictation grammars.
See documentation below just before the class definition.
SelectGramBase - base class for all selection grammars.
See documentation below just before the class definition.
Functions
==========
among others, see below:
buttonClick( btnName='left',count=1 )
This function simulates a button click or button double click. Pass
in the button name ('left','right' or 'middle') and the count (1 or
2)
matchWindow( moduleInfo, modName, wndText )
A utility function which determines whether moduleInfo matches a
specified module name and window title. Returns window handle on
match and None on mismatch. Note that moduleInfo may be ("","",0)
which we should handle cleanly.
"""
#pylint:disable=C0116, C0209, C0302, R0902, W0702, E1101, W0703, R1735
#pylint:disable=W0237 # inconsistent calling sequences for different classes
import os
import os.path
import copy
import struct
import sys
import traceback
import time
import natlink
from natlinkcore import gramparser
# was set in config program, and passed via natlinkstatus.py but now removed...
debugLoad = 0
# The following constants define the common windows message codes which
# are passed to playEvents.
wm_keydown = 0x0100
wm_keyup = 0x0101
wm_syskeydown = 0x0104
wm_syskeyup = 0x0105
wm_mousemove = 0x0200
wm_lbuttondown = 0x0201
wm_lbuttonup = 0x0202
wm_lbuttondblclk = 0x0203
wm_rbuttondown = 0x0204
wm_rbuttonup = 0x0205
wm_rbuttondblclk = 0x0206
wm_mbuttondown = 0x0207
wm_mbuttonup = 0x0208
wm_mbuttondblclk = 0x0209
# common keystroke constants used during mouse movement
vk_shift = 0x10
vk_control = 0x11
vk_menu = 0x12 # alt-key
# The following constants are the flag values for playString. The names
# come from the NatSpeak header files. Refer to natlink.txt for descriptions.
hook_f_shift = 0x01
hook_f_alt = 0x02
hook_f_ctrl = 0x04
hook_f_rightshift = 0x08
hook_f_rightalt = 0x10
hook_f_rightctrl = 0x20
hook_f_extended = 0x40
hook_f_defertermination = 0x100
hook_f_systemkeys = 0x200
hook_f_syskeys_checkshifts = 0x400
genkeys_f_scan_code = 0x10000
genkeys_f_uppercase = 0x20000
genkeys_f_lowercase = 0x40000
genkeys_f_capitalize = 0x80000
genkeys_f_virtkey = 0x200000
genkeys_f_usekeypad = 0x400000
# These are the flags which are returned from getWordInfo
dgnwordflag_useradded = 0x00000001
dgnwordflag_nodelete = 0x00000008
dgnwordflag_passive_cap_next= 0x00000010
dgnwordflag_active_cap_next = 0x00000020
dgnwordflag_uppercase_next = 0x00000040
dgnwordflag_lowercase_next = 0x00000080
dgnwordflag_no_space_next = 0x00000100
dgnwordflag_two_spaces_next = 0x00000200
dgnwordflag_cond_no_space = 0x00000400
dgnwordflag_cap_all = 0x00000800
dgnwordflag_uppercase_all = 0x00001000
dgnwordflag_lowercase_all = 0x00002000
dgnwordflag_no_space_all = 0x00004000
dgnwordflag_reset_no_space = 0x00008000
dgnwordflag_is_period = 0x00020000
dgnwordflag_no_formatting = 0x00040000
dgnwordflag_no_space_change = 0x00080000
dgnwordflag_no_cap_change = 0x00100000
dgnwordflag_no_space_before = 0x00200000
dgnwordflag_reset_uc_lc_caps= 0x00400000
dgnwordflag_new_line = 0x00800000
dgnwordflag_new_paragraph = 0x01000000
dgnwordflag_title_mode = 0x02000000
dgnwordflag_space_bar = 0x08000000
dgnwordflag_topicadded = 0x40000000
dgnwordflag_DNS8newwrdProp = 0x20000000
## temporary fix, remove as soon as natlinkmain is more secure QH (Febr 2021)
ApplicationFrameHostTitles = {}
ApplicationFrameHostTitles["Photos"] = "photos"
ApplicationFrameHostTitles["Foto's"] = "photos"
ApplicationFrameHostTitles["Calculator"] = "calc"
ApplicationFrameHostTitles["Rekenmachine"] = "calc"
## copy of function in natlinkmain, in order to get free from natlinkmain here:
[docs]
def getCurrentApplicationName(moduleInfo):
"""get the module name from currentModule info
normally: the application name from currentModule[0]
if this is applicationframehost, try from title in above dict, ApplicationFrameHostTitles
"""
# return os.path.splitext(
# os.path.split(self.currentModule[0]) [1]) [0].lower()
try:
curModule = os.path.splitext(os.path.split(moduleInfo[0])[1])[0]
except:
print(f'getCurrentApplicationName: invalid modulename, skipping moduleInfo: {moduleInfo}')
return ''
if not curModule:
return ''
progname = curModule.lower()
if curModule == 'ApplicationFrameHost':
title = moduleInfo[1]
for progname in title.split()[0], title.split()[-1]:
# print(f'ApplicationFrameHost, try title word: {progname}')
if progname in ApplicationFrameHostTitles:
progname = ApplicationFrameHostTitles[progname]
# print(f'ApplicationFrameHost, found progname: {progname}')
break
else:
print(f'ApplicationFrameHost with title: {title} not found in titles, enter in configuration')
return ''
return progname
#---------------------------------------------------------------------------
# matchWindow
#
# A utility function which determines whether moduleInfo matches a specified
# module name and window title. Returns window handle on match and None on
# mismatch.
#
# Note that moduleInfo may be ("","",0) which we should handle cleanly.
[docs]
def matchWindow(moduleInfo, modName, wndText):
"""A utility function which determines whether moduleInfo matches a specified
module name and window title.
Returns window handle on match and None on mismatch.
Note that moduleInfo may be `("", "", 0)` which we should handle cleanly.
"""
if len(moduleInfo) < 3 or not moduleInfo[0]:
return None
# copy of function from natlinkmain here (above)
curName = getCurrentApplicationName(moduleInfo)
if curName != modName:
return None
if moduleInfo[1].find(wndText) == -1:
return None
return moduleInfo[2]
#---------------------------------------------------------------------------
# buttonClick
#
#
[docs]
def getModifierKeyCodes(modifiers):
"""return a list with keycodes for modifiers
input can be a list of valid modifiers (ctrl, shift or menu == alt ),
either in a sequence or as single string. If string contains the + symbol or a space,
the string is split before searching the keycodes
if no modifiers are given, an empty list is returned
Invalid input raises a KeyError.
Testing in unittestNatLink, see testNatlinkUtilsFunctions
"""
modifier_dict = dict(ctrl=vk_control,
shift=vk_shift,
menu=vk_menu,
alt=vk_menu) # added alt == menu
if not modifiers:
return []
if isinstance(modifiers, str):
modifiers = modifiers.replace("+", " ").split()
return [modifier_dict[m] for m in modifiers]
[docs]
def playString(keys, hooks=None):
"""natlink.playString was deprecated, but is repaired via a python function with version 5.4.0
Therefore this function in natlinkutils is obsolete.
`playString` outputs a string of (keyboard) characters, including
control characters (like `{ctrl+c}` or `{right 3}`)
to the foreground window.
It is implemented via the `sendkeys` function in repository `dtactions`.
::
So you can either do:
from dtactions.sendkeys import sendkeys
(...)
sendkeys('hello world')
or:
from natlink import playString
(...)
playString('hello world again')
And forget about `natlinkutils.playString`!!
"""
natlink.playString(keys)
#Classes--------------------------------------------------------------------
[docs]
class GramClassBase:
"""shared base class for all Grammar base classes.
Do not use this class directly. See GrammarBase, DictGramBase or SelectGramBase.
"""
def __init__(self):
self.gramObj = natlink.GramObj()
self.grammarName = ''
def __del__(self):
try:
self.gramObj.unload()
except AttributeError:
pass
def load(self, gramSpec, allResults=0, hypothesis=0, grammarName=None):
self.grammarName = grammarName or self.grammarName
try:
self.gramObj.setBeginCallback(self.beginCallback)
self.gramObj.setResultsCallback(self.resultsCallback)
self.gramObj.setHypothesisCallback(self.hypothesisCallback)
except Exception as exc:
print("GramClassBase.load, Error at setting Callback functions")
raise Exception from exc
try:
self.gramObj.load(gramSpec, allResults, hypothesis)
except natlink.NatError as exc:
print(f'GramClassBase.load, Error at loading the grammar: {exc}')
raise natlink.BadGrammar from exc
def unload(self):
self.gramObj.unload()
self.gramObj.setBeginCallback(None)
self.gramObj.setResultsCallback(None)
self.gramObj.setHypothesisCallback(None)
def activate(self,window=0,exclusive=None):
self.gramObj.activate('',window)
if exclusive is not None:
self.setExclusive(exclusive)
def deactivate(self):
self.gramObj.deactivate('')
def setExclusive(self, exclusive):
self.gramObj.setExclusive(exclusive)
def beginCallback(self, moduleInfo):
self.callIfExists( "gotBegin", (moduleInfo,) )
def hypothesisCallback(self, words):
self.callIfExists( "gotHypothesis", (words,) )
def resultsCallback(self, wordsAndNums, resObj):
raise NotImplementedError
# This is a utility function. It calls a member function if and only
# if that member function is defined.
def callIfExists(self, funcName, argList):
try:
func = getattr(self, funcName)
except AttributeError:
return None
return func(*argList)
#---------------------------------------------------------------------------
# GrammarBase
#
# This is the basic grammar class. All user grammar classes should use this
# as the base class.
#
# Here are the functions which derived classes can call:
#
# load( gramSpec, allResults=0, hypothesis=0 )
# This function will takes a textual representation of a grammar,
# either as a single string or as a list of strings and load that
# grammar into Dragon NaturallySpeaking.
#
# allResults=1 will make it so this grammar see every recognition
# result even if it is for another grammar,
# hypothesis=1 means that the gotHypothesis callback will be made.
# Otherwise, that callback is not made to avoid too much overhead.
#
# unload()
# Unload reset the state of the grammar. Any SAPI objects will be
# freed and all callbacks will be terminated.
#
# You need to activate rules before they will be recognized. You activate
# rules by name. Any rule which is "exported" in the grammar can be
# activated. This base class offers a number of was to activate rules
# depending on what is convient.
#
# activate( ruleName, window=0, exclusive=None, noError=0 )
# Activate a single rule by name.
#
# noError=1 will suppress the error message when you try to activate
# a rule which is already active
# window=N will activate this rule conditionally when the window
# whose window handle is X is the foreground window. This is
# the normal case. Use window=0 to activate a global rule.
# exclusive=1 will make this grammar exclusive which means that no
# other grammars will be included in recognition when this
# grammar is active unless they are also exclusive
#
# activateSet( ruleNames, window=0, exclusive=None )
# This is the most efficient way to activate rules, it takes a list of
# rules and makes sure that all those rules and only those rules are
# active. Do not use this function to change the window handle if you
# have already activates some rules with a different window handle.
#
# activateAll( window=0, exclusive=None, exceptlist=None )
# This will activate every exported rule.
# can optionally add a list of rules NOT to activate (QH, 2010)
#
# deactivate( ruleName, noError=0 )
# Deactivates a single rule by name.
# noError=1 will suppress the error message when you try to deactivate
# a rule which is not active
#
# deactivateAll():
# This will deactivate every currently active rule.
#
# setExclusive( exclusive ):
# Set or reset the exclusive flag for this grammar (see comments under
# activate method).
#
# Lists are part of SAPI. They are list subrules except that they can be
# changed while the grammar is loaded. Also, the list a word comes from is
# not available in recognition results, you only see the innermost rule name
# in recognition results.
#
# emptyList( listName )
# This will remove all words currently in a names list.
#
# appendList( listName, words )
# This will add one word (phrase) or a list of words (phrases) to a
# named list.
#
# setList( listName, words )
# This function is an efficient way to set the contents of a list in
# one operation to a list of words or phrases.
#
# Derived classes should defined callback functions if they want recognition
# results. The following callback functions can be defined:
#
# gotBegin( moduleInfo )
# called when speech is detected before recognition begins. The
# parameter is the same value returned by getCurrentModule.
#
# gotResultsObject( recogType, resObj )
# called when results are available (before any of the other results
# callbacks). The first parameter is one of 'self','other' or 'reject'
# where the second two types are only possible if the allResults flag
# was set on the load call. The second parameter is the results object.
#
# gotResultsInit( words, fullResults )
# called when results are available for this grammar before any calls
# to gotResults_XXX. Parameters are a list of the recognized words
# and a list of word,ruleName pairs.
#
# gotResults_XXX( words, fullResults )
# called when results are available. The XXX refers to the rule name
# in the grammar. This function is called once for each sequential
# series of words in the results which come from the same rule in the
# grammar. The first parameter is the sequential series of words and
# the second parameter is a list of word,ruleName pairs.
#
# gotResults( words, fullResults )
# called when results are available for this grammar before any calls
# to gotResults_XXX. Parameters are a list of the recognized words
# and the same value a list of word,ruleName pairs.
#
# gotHypothesis( words)
# only called when the hypothesis flag is set on load, this callback
# contains the partial recognition hypothesis during recognition.
#
# Here is an example of how the callbacks work. Assume the following grammar:
#
# <start> exported = this <ruleOne> is {list}
# <ruleOne> = big <ruleTwo> object
# <ruleTwo> = red | blue
#
# And assume that {list} contains the words "good" and "bad".
#
# Now if "this big red object is good" is recognized, the following callbacks
# will be made (in the order indicated).
#
# (Note: The second parameter to gotResults_XXX and gotResults is the same as
# the second parameter to gotResultsInit. The example has been abbreviated
# to save space.)
#
# gotBegin( ??? )
# gotResultsObject( 'self', resObj )
# gotResultsInit(
# ['this','big','red','object','is','good'],
# [('this','start'),('big','ruleOne'),('red','RuleTwo'),
# ('object','ruleOne'),('is','start'),('good','start')] )
# gotResults_start( ['this'], ... )
# gotResults_ruleOne( ['big'], ... )
# gotResults_ruleTwo( ['red'], ... )
# gotResults_ruleOne( ['object'], ... )
# gotResults_start( ['is','good'], ... )
# gotResults( ['this','big','red','object','is','good'], ... )
#
[docs]
class GrammarBase(GramClassBase):
"""This is the basic grammar class.
All user grammar classes should use this as the base class.
Here are the functions which derived classes can call:
load( gramSpec, allResults=0, hypothesis=0 )
This function will takes a textual representation of a grammar,
either as a single string or as a list of strings and load that
grammar into Dragon NaturallySpeaking.
allResults=1 will make it so this grammar see every recognition
result even if it is for another grammar,
hypothesis=1 means that the gotHypothesis callback will be made.
Otherwise, that callback is not made to avoid too much overhead.
unload()
Unload reset the state of the grammar. Any SAPI objects will be
freed and all callbacks will be terminated.
You need to activate rules before they will be recognized. You activate
rules by name. Any rule which is "exported" in the grammar can be
activated. This base class offers a number of was to activate rules
depending on what is convient.
activate( ruleName, window=0, exclusive=None, noError=0 )
Activate a single rule by name.
noError=1 will suppress the error message when you try to activate
a rule which is already active
window=N will activate this rule conditionally when the window
whose window handle is X is the foreground window. This is
the normal case. Use window=0 to activate a global rule.
exclusive=1 will make this grammar exclusive which means that no
other grammars will be included in recognition when this
grammar is active unless they are also exclusive
activateSet( ruleNames, window=0, exclusive=None )
This is the most efficient way to activate rules, it takes a list of
rules and makes sure that all those rules and only those rules are
active. Do not use this function to change the window handle if you
have already activates some rules with a different window handle.
activateAll( window=0, exclusive=None, exceptlist=None )
This will activate every exported rule.
can optionally add a list of rules NOT to activate (QH, 2010)
deactivate( ruleName, noError=0 )
Deactivates a single rule by name.
noError=1 will suppress the error message when you try to deactivate
a rule which is not active
deactivateAll():
This will deactivate every currently active rule.
setExclusive( exclusive ):
Set or reset the exclusive flag for this grammar (see comments under
activate method).
Lists are part of SAPI. They are list subrules except that they can be
changed while the grammar is loaded. Also, the list a word comes from is
not available in recognition results, you only see the innermost rule name
in recognition results.
emptyList( listName )
This will remove all words currently in a names list.
appendList( listName, words )
This will add one word (phrase) or a list of words (phrases) to a
named list.
setList( listName, words )
This function is an efficient way to set the contents of a list in
one operation to a list of words or phrases.
Derived classes should defined callback functions if they want recognition
results. The following callback functions can be defined:
gotBegin( moduleInfo )
called when speech is detected before recognition begins. The
parameter is the same value returned by getCurrentModule.
gotResultsObject( recogType, resObj )
called when results are available (before any of the other results
callbacks). The first parameter is one of 'self','other' or 'reject'
where the second two types are only possible if the allResults flag
was set on the load call. The second parameter is the results object.
gotResultsInit( words, fullResults )
called when results are available for this grammar before any calls
to gotResults_XXX. Parameters are a list of the recognized words
and a list of word,ruleName pairs.
gotResults_XXX( words, fullResults )
called when results are available. The XXX refers to the rule name
in the grammar. This function is called once for each sequential
series of words in the results which come from the same rule in the
grammar. The first parameter is the sequential series of words and
the second parameter is a list of word,ruleName pairs.
gotResults( words, fullResults )
called when results are available for this grammar before any calls
to gotResults_XXX. Parameters are a list of the recognized words
and the same value a list of word,ruleName pairs.
gotHypothesis( words)
only called when the hypothesis flag is set on load, this callback
contains the partial recognition hypothesis during recognition.
Here is an example of how the callbacks work. Assume the following grammar:
<start> exported = this <ruleOne> is {list}
<ruleOne> = big <ruleTwo> object
<ruleTwo> = red | blue
And assume that {list} contains the words "good" and "bad".
Now if "this big red object is good" is recognized, the following callbacks
will be made (in the order indicated).
"""
#
# (Note: The second parameter to gotResults_XXX and gotResults is the same as
# the second parameter to gotResultsInit. The example has been abbreviated
# to save space.)
#
# gotBegin( ??? )
# gotResultsObject( 'self', resObj )
# gotResultsInit(
# ['this','big','red','object','is','good'],
# [('this','start'),('big','ruleOne'),('red','RuleTwo'),
# ('object','ruleOne'),('is','start'),('good','start')] )
# gotResults_start( ['this'], ... )
# gotResults_ruleOne( ['big'], ... )
# gotResults_ruleTwo( ['red'], ... )
# gotResults_ruleOne( ['object'], ... )
# gotResults_start( ['is','good'], ... )
# gotResults( ['this','big','red','object','is','good'], ... )
# """
def __init__(self):
GramClassBase.__init__(self)
self.is_loaded = False
self.exclusiveState = 0
self.activeRules = {}
self.validRules = []
self.validLists = []
self.ruleMap = {}
self.fullResults = [] # handled in resultsCallback
self.seqsAndRules = [] #
self.wordsByRule = {} #
self.doOnlyGotResultsObject = None # can rarely be set (QH, dec 2009)
self.prevRule, self.prevWords = None, [] # callRuleResultsFunctions
self.nextRule, self.nextWords = None, [] #
# self.scanObj = None # in case gramparser goes wrong and error messages fail to print
def load(self, gramSpec, allResults=0, hypothesis=0, grammarName=None):
try:
# print 'loading grammar %s, gramspec type: %s'% (grammarName, type(gramSpec))
# code upper ascii characters with latin1 if they were in the process entered as unicode
if not isinstance(gramSpec, (str, list)):
raise TypeError( "grammar definition of %s must be a string or a list of strings, not %s"% (grammarName, type(gramSpec)))
parser = gramparser.GramParser(gramSpec, grammarName=grammarName)
parser.doParse()
parser.checkForErrors()
gramBin = gramparser.packGrammar(parser)
# self.scanObj = parser.scanObj # for later error messages
# no mention to GrammarError after the loading now..
try:
GramClassBase.load(self,gramBin,allResults,hypothesis)
except natlink.BadGrammar:
self.is_loaded = False
return self.is_loaded
# we want to keep a list of the rules which can be activated and the
# known lists so we can catch errors earlier
self.validRules = list(parser.exportRules.keys())
self.validLists = list(parser.knownLists.keys())
# we reverse the rule dictionary so we can convert rule numbers back
# to rule names during recognition
for ruleNum, knownRule in parser.knownRules.items():
self.ruleMap[ knownRule ] = ruleNum
self.is_loaded = True
except:
print("Unexpected error loading grammar:", sys.exc_info())
print(traceback.print_exc())
self.is_loaded = False
return self.is_loaded
# these are wrappers for the GramObj base methods. We also keep track of
# legal rules, lists and active rules so we can do some first level error
# checking
def unload(self):
if not self.is_loaded:
return
GramClassBase.unload(self)
self.activeRules.clear()
while self.validRules:
self.validRules.pop()
while self.validLists:
self.validLists.pop()
self.exclusiveState = 0
self.is_loaded = False
def activate(self, ruleName, window=0, exclusive=None, noError=0):
#pylint:disable=W0221, W0613
debug_print(f'GrammarBase, activate ruleName "{ruleName}" in window {window}, exclusive: {exclusive}')
if ruleName not in self.validRules:
if not noError or debug_print:
print(f'rule "{ruleName}" was not exported in the grammar')
return
if ruleName in self.activeRules:
if window == self.activeRules[ruleName]:
if not noError or debug_print:
print(f'rule "{ruleName}" already active for window {window}')
return
debug_print(f'change rule "{ruleName}" from window {self.activeRules[ruleName]} to {window}')
self.gramObj.deactivate(ruleName)
# debug_print( print('activate rule %s (window: %s)'% (ruleName, window))
self.gramObj.activate(ruleName, window)
self.activeRules[ruleName] = window
if not exclusive is None:
debug_print(f'set exclusive mode to {exclusive} for rule "{ruleName}"')
self.setExclusive(exclusive)
def deactivate(self, ruleName, noError=0, dpi16trick=True):
#pylint:disable=W0221
debug_print(f'deactivate: {ruleName}, activeRules: {self.activeRules}')
if ruleName not in self.validRules:
if noError:
return
raise ValueError(f'rule "{ruleName}" was not exported in the grammar')
if ruleName not in self.activeRules:
if not noError:
print(f'rule "{ruleName}" is not active, no need to deactivate (activeRules: {set(self.activeRules.keys())})')
return
debug_print('deactivate rule %s'% ruleName)
if dpi16trick:
# now deactivate all and activate other rules again
# be sure, this one is not called recursive!!
active_rules = copy.copy(self.activeRules)
del active_rules[ruleName]
self.deactivateAll()
for rule, window in active_rules.items():
self.activate(rule, window)
return
# previous behaviour (as long as dpi16trick is not changed in call)
self.gramObj.deactivate(ruleName)
del self.activeRules[ruleName]
[docs]
def activateSet(self, ruleNames, window=0, exclusive=None):
"""activate a set of rules.
dpi16: deactivate all and activate the new rules.
"""
#pylint:disable=R0912
debug_print(f'activateSet: {ruleNames}, window: {window}')
active_rules = set(self.activeRules.keys())
if set(ruleNames) == active_rules:
for rule in ruleNames:
if not (rule in self.activeRules and self.activeRules[rule] == window):
break
else:
debug_print(f'activateSet {ruleNames} not needed, set is already active for window: {window}')
return
self.deactivateAll()
for x in ruleNames:
self.activate(x, window)
if not exclusive is None:
self.setExclusive(exclusive)
def deactivateSet(self, ruleNames):
debug_print(f'deactivateSet: {ruleNames}')
rule_names = set(ruleNames)
prev_rules = copy.copy(self.activeRules)
if prev_rules:
active_names = set(prev_rules.keys())
else:
debug_print(f'deactiveSet "{ruleNames}", no rules are active in this grammar')
return
if not active_names.intersection(ruleNames):
debug_print(f'deactiveSet, ruleNames {ruleNames} are not active in this grammar: {active_names}')
return
remain_names = active_names - rule_names
self.deactivateAll()
for x in remain_names:
window = prev_rules[x]
self.activate(x, window)
[docs]
def activateAll(self, window=0, exclusive=None, exceptlist=None):
"""activate all rules
as experiment first deactivate all rules before doing so
"""
all_rules = set(self.validRules)
if exceptlist:
for x in exceptlist:
if x in all_rules:
print(f'discard from allRules: {x}')
all_rules.discard(x)
print(f'activateAll except {exceptlist}: {all_rules}')
self.activateSet(all_rules, window=window, exclusive=exclusive)
if not exclusive is None:
self.setExclusive(exclusive)
def _deactivateAll(self):
"""deactivate all rules, no change of exclusive state
"""
activeRules = list(self.activeRules.keys())
for x in activeRules:
debug_print(f'deactivate rule {x}')
self.gramObj.deactivate(x)
self.activeRules = {}
[docs]
def deactivateAll(self):
"""deactivate all rules and reset explicit the exclusive state of the grammar
"""
self._deactivateAll()
self.setExclusive(0)
[docs]
def setExclusive(self, exclusive):
"""call into gramObj directly
maintain self.exclusiveState
"""
if exclusive is None:
return
if exclusive:
value = 1
else:
value = 0
self.gramObj.setExclusive(value)
self.exclusiveState = value
[docs]
def isExclusive(self):
"""return True if exclusive, False if non-exclusive (most of the time)
"""
if self.exclusiveState:
return True
return False
[docs]
def isActive(self):
"""return True is active (rules activated), False if loaded, but no rules active
return None if grammar is not loaded (yet)
"""
if self.activeRules:
return True
return False
[docs]
def isLoaded(self):
"""return True if grammar is loaded
"""
if self.validRules:
return True
return False
def emptyList(self, listName):
# if type(listName) == str:
# listName = utilsqh.convertToBinary(listName)
if listName not in self.validLists:
raise ValueError( 'list "%s" was not defined in the grammar, validLists: %s' % (listName, self.validLists))
self.gramObj.emptyList(listName)
def appendList(self, listName, words):
# listName = utilsqh.convertToBinary(listName)
if listName not in self.validLists:
raise ValueError( "list %s was not defined in the grammar" % listName)
if isinstance(words, str):
self.gramObj.appendList(listName, words)
else:
for x in words:
self.gramObj.appendList(listName,x)
def setList(self, listName, words):
self.emptyList(listName)
self.appendList(listName, words) # other way around?
[docs]
def resultsCallback(self, wordsAndNums, resObj):
"""when a recognition for this grammar occurs, this function gets called
by GramObj (it is set as the callback in GrammarBase.load).
if the allResults flag is set it is possible that the first
parameter will be a string instead of a data structure. We
compute the recognition type from this parameter
"""
#pylint:disable=R0912
if isinstance(wordsAndNums, str):
recogType = wordsAndNums
else:
recogType = 'self'
# make an optional callback which allows the clients to have access
# to the recognition object
self.callIfExists( 'gotResultsObject', (recogType,resObj) )
# do nothing more if the recog results were not for this grammar
if not isinstance(wordsAndNums, list):
return
# we first convert the passed array of word/ruleNumbers into an
# array of word/ruleNames and an array of only words
words = []
fullResults = []
wordsByRule = {}
if self.doOnlyGotResultsObject:
# can switch on in gotResultsObject, so rest of processing is not done.
# grammar kaiser_dictation, (voicedictation with exclusive mode catching)
# QH (dec, 2009)
#print 'skip rest of resultsCallback'
return
for x in wordsAndNums:
word, ruleNumber = x
words.append( word )
try:
ruleName = self.ruleMap[ruleNumber]
except KeyError:
# print("KeyError, %s"% ruleNumber)
if ruleNumber == 1000000 and 'dgndictation' in list(self.ruleMap.values()):
ruleName = 'dgndictation'
elif ruleNumber == 1000001 and 'dgnletters' in list(self.ruleMap.values()):
ruleName = 'dgnletters'
elif ruleNumber == 1000002 and 'dgnwords' in list(self.ruleMap.values()):
ruleName = 'dgnwords'
elif ruleNumber == 0 and word == '\\noise\\?':
continue
else:
print('='*50)
print('word: %s, ruleNumber: %s'% (word, ruleNumber))
print('wordsAndNums: %s'% wordsAndNums)
print('ruleMap: %s'% repr(self.ruleMap))
mess = 'Invalid key %s for ruleMap'% ruleNumber
print(mess)
print('===============================')
continue
fullResults.append( ( word, ruleName ) )
wordsByRule.setdefault(ruleName, []).append(word)
# we also compute a list similar to fullResults except that we group
# all words which are sequential and in the same rule together in a
# sublist. For example:
# [ ('red','color'), ('blue','color'), ('and','conj'), ('green','color') ]
# Becomes:
# [ (['red','blue'],'color'), (['and'],'conj'), (['green'],'color') ]
seqsAndRules = []
for x in fullResults:
if len(seqsAndRules) > 0 and seqsAndRules[-1:][0][1] == x[1]:
# duplicate rule, append previous entry
seqsAndRules[-1:][0][0].append(x[0])
else:
seqsAndRules.append( ([x[0]], x[1]) )
# provide fullResults and seqsAndRules also as instance variables:
self.fullResults = fullResults
self.seqsAndRules = seqsAndRules
self.wordsByRule = wordsByRule
# if wordsAndNums[0][0] == 'testtestrun':
# if wordsAndNums[0][0] == 'testtestrun' or True:
# print 'wordsAndNums: %s'% wordsAndNums
# ## print for debugging puposes, eg in the testIniGrammar.py module of Unimacro:
# print 'words = %s'% words
# print 'seqsAndRules = %s'% seqsAndRules
# print 'fullResults = %s'% fullResults
## printing for debug purposes...
# now we make the callbacks (in each case we only call the fucntion
# if it exists in the derived class)
# - we first call gotResultsInit
# - then we make one callback for each different rule found as we
# sequentially scan the results (see seqsAndRules example)
# - finally we call gotResults
self.callIfExists( 'gotResultsInit', (words, fullResults) )
self.callRuleResultsFunctions(seqsAndRules, fullResults)
self.callIfExists( 'gotResults', (words, fullResults) )
[docs]
def callRuleResultsFunctions(self, seqsAndRules, fullResults):
"""call the rule functions, can be overloaded (eg in DocstringGrammar)
Also give self.nextRule (the name) self.nextWords, self.prevRule, self.prevWords
so the result of the adjacent rules are known
"""
ruleName, ruleWords = None, None
lenSeqsAndRules = len(seqsAndRules)
for i, x in enumerate(seqsAndRules):
if i == 0:
self.prevRule, self.prevWords = None, []
else:
Prev = copy.copy(seqsAndRules[i-1])
self.prevRule, self.prevWords = Prev[1], Prev[0]
if i == lenSeqsAndRules - 1:
self.nextRule, self.nextWords = None, []
else:
Next = copy.copy(seqsAndRules[i+1])
self.nextRule, self.nextWords = Next[1], Next[0]
ruleName, ruleWords = x[1], copy.copy(x[0])
self.callIfExists( 'gotResults_'+ruleName, (ruleWords, fullResults) )
#---------------------------------------------------------------------------
# DictGramBase
#
[docs]
class DictGramBase(GramClassBase):
"""This base class is similar to GrammarBase except that it is used for real
dictation grammars. A real dictation grammar allows a client application
to get raw dictation results from the recognizer. It is very similat to
creating a command and control grammar with the <dgndictation> rule except
that you have the added ability to set the dictation context (words to
consider as having been spoken juct before the current recognition).
Here are the functions which derived classes can call:
load( allResults=0, hypothesis=0 )
Creates the dictation grammar. See GrammarBase class for definition
of flags.
unload()
Unload reset the state of the grammar. Any SAPI objects will be
freed and all callbacks will be terminated.
You need to activate the grammar before it can be recognized. Activation
of a dictation grammar is simplier than a command grammar because there
are no named rules.
activate( window=0, exclusive=None )
noError=1 will suppress the error message when you try to activate
a rule which is already active
window=N will activate this rule conditionally when the window
whose window handle is X is the foreground window. This is
the normal case. Use window=0 to activate a global rule.
deactivate()
Deactivates the grammar.
setExclusive( exclusive ):
Set or reset the exclusive flag for this grammar (see comments under
activate method of GrammarBase).
Dictation gramars have a special method which allows you to indicate what
text should be considered to been recognized just before this dictation
result, and after this dictation result.
setContext( beforeText='', afterText='' )
Note current version of Dragon NaturallySpeaking ignore the
afterText but the before text is critical to increasing recognition
accuracy.
Derived classes should defined callback functions if they want recognition
results. The following callback functions can be defined:
gotBegin( moduleInfo )
called when speech is detected before recognition begins. The
parameter is the same value returned by getCurrentModule.
gotResultsObject( recogType, resObj )
called when results are available (before any of the other results
callbacks). The first parameter is one of 'self','other' or 'reject'
where the second two types are only possible if the allResults flag
was set on the load call. The second parameter is the results object.
gotResults( words )
called when results are available for this grammar.
words = list of recognized words
gotHypothesis( words )
only called when the hypothesis flag is set on load, this callback
contains the partial recognition hypothesis during recognition.
"""
def load(self,allResults=0,hypothesis=0):
#pylint:disable=W0221
gramBin=self.makeGrammar()
GramClassBase.load(self,gramBin,allResults,hypothesis)
def setContext(self, beforeText='', afterText=''):
self.gramObj.setContext(beforeText,afterText)
# This code is very similar to the corresponding code in GrammarBase
def resultsCallback(self, wordsAndNums, resObj):
if isinstance(wordsAndNums, str):
recogType = wordsAndNums
else:
recogType = 'self'
self.callIfExists( 'gotResultsObject', (recogType,resObj) )
if not isinstance(wordsAndNums, list):
return
# convert the wordsAndNums array into simply words (ignore nums)
words = []
for word, _num in wordsAndNums:
words.append( word )
# Now make the callback
self.callIfExists( 'gotResults', (words,) )
# This makes a raw dictation grammar. The grammar is in SAPI binary
# format as defined by Microsoft.
def makeGrammar(self):
output = []
output.append(struct.pack("LL", 2, 0))
return b"".join(output)
#---------------------------------------------------------------------------
# SelectGramBase
[docs]
class SelectGramBase(GramClassBase):
"""This base class is used for select XYZ grammars.
This base class is similar to GrammarBase except that it is used for A select XYZ grammar is a special grammar which
recognizes an utterance of the form "<verb> <text> [ through <text> ]"
where <verb> is specified and <text> is an arbitrary sequence of words in
a specified text buffer.
Here are the functions which derived classes can call:
make default select and through words "select" and "through" (lower case)
Quintijn december 2010
adapt to throughWords as well, for future enhancement VoiceCode
Quintijn August, 2009
load( selectWords=['Select'], throughWord='Through', throughWords=None, allResults=0, hypothesis=0 )
selectWords is a list of words or phrases which can introduce a
comand of this type. For example, you can pass in a list like
['Select','Correct','Insert Before','Insert After'] to simulate
some of NatSpeak's behavior.
throughWord is a single word which is used between the two parts of
the command as in "Select <text> Through <text>". Pass in None
or an empty string to disable two part commands.
allResults=1 will make it so this grammar see every recognition
result even if it is for another grammar,
hypothesis=1 means that the gotHypothesis callback will be made.
Otherwise, that callback is not made to avoid too much overhead.
unload()
Unload reset the state of the grammar. Any SAPI objects will be
freed and all callbacks will be terminated.
You need to activate the grammar before it can be recognized. Activation
of a dictation grammar is simplier than a command grammar because there
are no named rules.
activate( window=0, exclusive=None )
noError=1 will suppress the error message when you try to activate
a rule which is already active
window=N will activate this rule conditionally when the window
whose window handle is X is the foreground window. This is
the normal case. Use window=0 to activate a global rule.
deactivate()
Deactivates the grammar.
setExclusive( exclusive ):
Set or reset the exclusive flag for this grammar (see comments under
activate method of GrammarBase).
Selection gramars work on a text buffer which you pass to the grammar.
The speech recognition engine maintains a copy of this text and performs
all selection on its copy. Then when the grammar is recognized, you can
find out the range of text which is effected.
setSelectText(text)
Pass in a block of text (string).
getSelectText()
Returns the text buffer contents currently stored in the speech
recognition engine.
Derived classes should defined callback functions if they want recognition
results. The following callback functions can be defined:
gotBegin( moduleInfo )
called when speech is detected before recognition begins. The
parameter is the same value returned by getCurrentModule.
gotResultsObject( recogType, resObj )
called when results are available (before any of the other results
callbacks). The first parameter is one of 'self','other' or 'reject'
where the second two types are only possible if the allResults flag
was set on the load call. The second parameter is the results object.
gotResults( words, start, end )
called when results are available for this grammar.
words = list of recognized words
the first word will be one of
the words or phrases in selectWords (passed in when the grammar
was created).
start = index into getSelectText of the start of the selection
end = index into getSelectText of the end of the selection
gotHypothesis( words )
only called when the hypothesis flag is set on load, this callback
contains the partial recognition hypothesis during recognition.
"""
def load( self, selectWords=None, throughWord='through',
throughWords=None,allResults=0, hypothesis=0 ):
#pylint:disable=W0221, R0913
if selectWords is None:
selectWords = ['select']
if throughWords is None:
throughWords = [throughWord]
# QH, throughWords also maybe a list of words, the default being
# ['Through'] (being the throughWord...
gramBin=self.makeGrammar(selectWords,throughWords)
GramClassBase.load(self,gramBin,allResults,hypothesis)
def setSelectText(self,text):
self.gramObj.setSelectText(text)
def getSelectText(self):
return self.gramObj.getSelectText()
# This code is very similar to the corresponding code in GrammarBase
def resultsCallback(self, wordsAndNums, resObj):
if isinstance(wordsAndNums, str):
recogType = wordsAndNums
else:
recogType = 'self'
self.callIfExists( 'gotResultsObject', (recogType,resObj) )
if not isinstance(wordsAndNums, list):
return
# convert the wordsAndNums array into simply words (ignore nums)
words = []
for word, _num in wordsAndNums:
words.append( word )
# query the text range which corresponds to this result
startPos,endPos = resObj.getSelectInfo(self.gramObj,0)
# Now make the callback
self.callIfExists( 'gotResults', (words, startPos, endPos) )
#;ydebug(lLoO9):::: (NL Verenigde Staten)
# A selection grammar is similar to a Microsoft SAPI grammar.
# SRHEADER
# dwType = DGNSRHDRTYPE_SELECT(10)
# dwFlags = 0
# SRCHUNK
# dwChunkID = DGNSRCKSELECT_INTROPHRASES(0x1017)
# dwChunkSize = size of SRWORDs
# data = series of SRWORDs for selectWords
# SRCHUNK (optional)
# dwChunkID = DGNSRCKSELECT_THRUWORD(0x1018)
# dwChunkSize = size of SRWORD
# data = single SRWORD for throughWord
[docs]
def makeGrammar(self, selectWords, throughWords):
"""make a selection grammar, which is similar to a Microsoft SAPI grammar.
Uses the routines in `gramparser.py` to build the grammar just like with
command grammars.
`selectWords` and `throughWords` are lists or words.
"""
output = []
output.append(struct.pack("LL", 10, 0))
if isinstance(selectWords, str):
selectWords = [selectWords]
if selectWords:
wordDict = {}
for word in selectWords:
wordDict[word] = 0
output.append(gramparser.packGrammarChunk(0x1017,wordDict))
# throughWords maybe more words now:
if throughWords:
if isinstance(throughWords, str):
throughWords = [throughWords]
wordDict = {}
for word in throughWords:
wordDict[word] = 0
output.append(gramparser.packGrammarChunk(0x1018,wordDict))
return b"".join(output)
#---------------------------------------------------------------------------
[docs]
def getBaseName(name):
"""Utility subroutine. This returns the base module name from a complete path
"""
return os.path.splitext(os.path.split(name)[1])[0]
[docs]
def convertResults(fullResults):
"""This utility routine converts a fullResults parameter into a dictionary
::
[ ('red','color'), ('flower','object'), ('and','conj'), ('green','color') ]
Becomes:
{ 'color':['red','green'], 'object':['flower'], 'conj':['and'] }
"""
_dict = {}
for x in fullResults:
if x[1] in _dict:
_dict[x[1]].append(x[0])
else:
_dict[x[1]] = [x[0]]
return _dict
[docs]
def debug_print(msg):
"""depends on variable debugLoad at top of module
"""
if debugLoad:
print(msg)
if __name__ == "__main__":
try:
natlink.natConnect()
buttonClick('right')
time.sleep(1)
natlink.playString('{esc}')
finally:
natlink.natDisconnect()