Module inputCore
[hide private]
[frames] | no frames]

Source Code for Module inputCore

  1  #inputCore.py 
  2  #A part of NonVisual Desktop Access (NVDA) 
  3  #This file is covered by the GNU General Public License. 
  4  #See the file COPYING for more details. 
  5  #Copyright (C) 2010 James Teh <jamie@jantrid.net> 
  6   
  7  import sys 
  8  import os 
  9  import itertools 
 10  import configobj 
 11  import baseObject 
 12  import scriptHandler 
 13  import queueHandler 
 14  import api 
 15  import speech 
 16  import characterProcessing 
 17  import config 
 18  import watchdog 
 19  from logHandler import log 
 20  import globalVars 
 21  import languageHandler 
 22   
 23  """Core framework for handling input from the user. 
 24  Every piece of input from the user (e.g. a key press) is represented by an L{InputGesture}. 
 25  The singleton L{InputManager} (L{manager}) manages functionality related to input from the user. 
 26  For example, it is used to execute gestures and handle input help. 
 27  """ 
 28   
29 -class NoInputGestureAction(LookupError):
30 """Informs that there is no action to execute for a gesture. 31 """
32
33 -class InputGesture(baseObject.AutoPropertyObject):
34 """A single gesture of input from the user. 35 For example, this could be a key press on a keyboard or Braille display or a click of the mouse. 36 At the very least, subclasses must implement L{_get_identifiers} and L{_get_displayName}. 37 """ 38 cachePropertiesByDefault = True 39
40 - def _get_identifiers(self):
41 """The identifier(s) which will be used in input gesture maps to represent this gesture. 42 These identifiers will be looked up in order until a match is found. 43 A single identifier should take the form: C{source:id} 44 where C{source} is a few characters representing the source of this gesture 45 and C{id} is the specific gesture. 46 If C{id} consists of multiple items with indeterminate order, 47 they should be separated by a + sign and they should be in Python set order. 48 Also, the entire identifier should be in lower case. 49 An example identifier is: C{kb(desktop):nvda+1} 50 Subclasses must implement this method. 51 @return: One or more identifiers which uniquely identify this gesture. 52 @rtype: list or tuple of str 53 """ 54 raise NotImplementedError
55
56 - def _get_logIdentifier(self):
57 """A single identifier which will be logged for this gesture. 58 This identifier should be usable in input gesture maps, but should be as readable as possible to the user. 59 For example, it might sort key names in a particular order 60 and it might contain mixed case. 61 This is in contrast to L{identifiers}, which must be normalized. 62 The base implementation returns the first identifier from L{identifiers}. 63 """ 64 return self.identifiers[0]
65
66 - def _get_displayName(self):
67 """The name of this gesture as presented to the user. 68 Subclasses must implement this method. 69 @return: The display name. 70 @rtype: str 71 """ 72 raise NotImplementedError
73 74 #: Whether this gesture should be reported when reporting of command gestures is enabled. 75 #: @type: bool 76 shouldReportAsCommand = True 77 78 SPEECHEFFECT_CANCEL = "cancel" 79 SPEECHEFFECT_PAUSE = "pause" 80 SPEECHEFFECT_RESUME = "resume" 81 #: The effect on speech when this gesture is executed; one of the SPEECHEFFECT_* constants or C{None}. 82 speechEffectWhenExecuted = SPEECHEFFECT_CANCEL 83 84 #: Whether this gesture is only a modifier, in which case it will not search for a script to execute. 85 #: @type: bool 86 isModifier = False 87
88 - def reportExtra(self):
89 """Report any extra information about this gesture to the user. 90 This is called just after command gestures are reported. 91 For example, it could be used to report toggle states. 92 """
93
94 - def _get_script(self):
95 """The script bound to this input gesture. 96 @return: The script to be executed. 97 @rtype: script function 98 """ 99 return scriptHandler.findScript(self)
100
101 - def send(self):
102 """Send this gesture to the operating system. 103 This is not possible for all sources. 104 @raise NotImplementedError: If the source does not support sending of gestures. 105 """ 106 raise NotImplementedError
107
108 - def _get_scriptableObject(self):
109 """An object which contains scripts specific to this gesture or type of gesture. 110 This object will be searched for scripts before any other object when handling this gesture. 111 @return: The gesture specific scriptable object or C{None} if there is none. 112 @rtype: L{baseObject.ScriptableObject} 113 """ 114 return None
115
116 -class GlobalGestureMap(object):
117 """Maps gestures to scripts anywhere in NVDA. 118 This is used to allow users and locales to bind gestures in addition to those bound by individual scriptable objects. 119 Map entries will most often be loaded from a file using the L{load} method. 120 See that method for details of the file format. 121 """ 122
123 - def __init__(self, entries=None):
124 """Constructor. 125 @param entries: Initial entries to add; see L{update} for the format. 126 @type entries: mapping of str to mapping 127 """ 128 self._map = {} 129 #: Indicates that the last load or update contained an error. 130 #: @type: bool 131 self.lastUpdateContainedError = False 132 if entries: 133 self.update(entries)
134
135 - def clear(self):
136 """Clear this map. 137 """ 138 self._map.clear() 139 self.lastUpdateContainedError = False
140
141 - def add(self, gesture, module, className, script,replace=False):
142 """Add a gesture mapping. 143 @param gesture: The gesture identifier. 144 @type gesture: str 145 @param module: The name of the Python module containing the target script. 146 @type module: str 147 @param className: The name of the class in L{module} containing the target script. 148 @type className: str 149 @param script: The name of the target script 150 or C{None} to unbind the gesture for this class. 151 @type script: str 152 @param replace: if true replaces all existing bindings for this gesture with the given script, otherwise only appends this binding. 153 @type replace: boolean 154 """ 155 gesture = normalizeGestureIdentifier(gesture) 156 try: 157 scripts = self._map[gesture] 158 except KeyError: 159 scripts = self._map[gesture] = [] 160 if replace: 161 del scripts[:] 162 scripts.append((module, className, script))
163
164 - def load(self, filename):
165 """Load map entries from a file. 166 The file is an ini file. 167 Each section contains entries for a particular scriptable object class. 168 The section name must be the full Python module and class name. 169 The key of each entry is the script name and the value is a comma separated list of one or more gestures. 170 If the script name is "None", the gesture will be unbound for this class. 171 For example, the following binds the "a" key to move to the next heading in virtual buffers 172 and removes the default "h" binding:: 173 [virtualBuffers.VirtualBuffer] 174 nextHeading = kb:a 175 None = kb:h 176 @param filename: The name of the file to load. 177 @type: str 178 """ 179 try: 180 conf = configobj.ConfigObj(filename, file_error=True, encoding="UTF-8") 181 except (configobj.ConfigObjError,UnicodeDecodeError), e: 182 log.warning("Error in gesture map '%s': %s"%(filename, e)) 183 self.lastUpdateContainedError = True 184 return 185 self.update(conf)
186
187 - def update(self, entries):
188 """Add multiple map entries. 189 C{entries} must be a mapping of mappings. 190 Each inner mapping contains entries for a particular scriptable object class. 191 The key in the outer mapping must be the full Python module and class name. 192 The key of each entry in the inner mappings is the script name and the value is a list of one or more gestures. 193 If the script name is C{None}, the gesture will be unbound for this class. 194 For example, the following binds the "a" key to move to the next heading in virtual buffers 195 and removes the default "h" binding:: 196 { 197 "virtualBuffers.VirtualBuffer": { 198 "nextHeading": "kb:a", 199 None: "kb:h", 200 } 201 } 202 @param entries: The items to add. 203 @type entries: mapping of str to mapping 204 """ 205 self.lastUpdateContainedError = False 206 for locationName, location in entries.iteritems(): 207 try: 208 module, className = locationName.rsplit(".", 1) 209 except: 210 log.error("Invalid module/class specification: %s" % locationName) 211 self.lastUpdateContainedError = True 212 continue 213 for script, gestures in location.iteritems(): 214 if script == "None": 215 script = None 216 if gestures == "": 217 gestures = () 218 elif isinstance(gestures, basestring): 219 gestures = [gestures] 220 for gesture in gestures: 221 try: 222 self.add(gesture, module, className, script) 223 except: 224 log.error("Invalid gesture: %s" % gesture) 225 self.lastUpdateContainedError = True 226 continue
227
228 - def getScriptsForGesture(self, gesture):
229 """Get the scripts associated with a particular gesture. 230 @param gesture: The gesture identifier. 231 @type gesture: str 232 @return: The Python class and script name for each script; 233 the script name may be C{None} indicating that the gesture should be unbound for this class. 234 @rtype: generator of (class, str) 235 """ 236 try: 237 scripts = self._map[gesture] 238 except KeyError: 239 return 240 for moduleName, className, scriptName in scripts: 241 try: 242 module = sys.modules[moduleName] 243 except KeyError: 244 continue 245 try: 246 cls = getattr(module, className) 247 except AttributeError: 248 continue 249 yield cls, scriptName
250
251 -class InputManager(baseObject.AutoPropertyObject):
252 """Manages functionality related to input from the user. 253 Input includes key presses on the keyboard, as well as key presses on Braille displays, etc. 254 """ 255
256 - def __init__(self):
257 #: Whether input help is enabled, wherein the function of each key pressed by the user is reported but not executed. 258 #: @type: bool 259 self.isInputHelpActive = False 260 #: The gestures mapped for the NVDA locale. 261 #: @type: L{GlobalGestureMap} 262 self.localeGestureMap = GlobalGestureMap() 263 #: The gestures mapped by the user. 264 #: @type: L{GlobalGestureMap} 265 self.userGestureMap = GlobalGestureMap() 266 self.loadLocaleGestureMap() 267 self.loadUserGestureMap()
268
269 - def executeGesture(self, gesture):
270 """Perform the action associated with a gesture. 271 @param gesture: The gesture to execute. 272 @type gesture: L{InputGesture} 273 @raise NoInputGestureAction: If there is no action to perform. 274 """ 275 if watchdog.isAttemptingRecovery: 276 # The core is dead, so don't try to perform an action. 277 # This lets gestures pass through unhindered where possible, 278 # as well as stopping a flood of actions when the core revives. 279 raise NoInputGestureAction 280 281 script = gesture.script 282 app = api.getFocusObject().appModule 283 if app and app.sleepMode and not getattr(script,'allowInSleepMode',False): 284 raise NoInputGestureAction 285 286 speechEffect = gesture.speechEffectWhenExecuted 287 if speechEffect == gesture.SPEECHEFFECT_CANCEL: 288 queueHandler.queueFunction(queueHandler.eventQueue, speech.cancelSpeech) 289 elif speechEffect in (gesture.SPEECHEFFECT_PAUSE, gesture.SPEECHEFFECT_RESUME): 290 queueHandler.queueFunction(queueHandler.eventQueue, speech.pauseSpeech, speechEffect == gesture.SPEECHEFFECT_PAUSE) 291 292 if log.isEnabledFor(log.IO) and not gesture.isModifier: 293 log.io("Input: %s" % gesture.logIdentifier) 294 295 if self.isInputHelpActive: 296 bypass = getattr(script, "bypassInputHelp", False) 297 queueHandler.queueFunction(queueHandler.eventQueue, self._handleInputHelp, gesture, onlyLog=bypass) 298 if not bypass: 299 return 300 301 if gesture.isModifier: 302 raise NoInputGestureAction 303 304 if config.conf["keyboard"]["speakCommandKeys"] and gesture.shouldReportAsCommand: 305 queueHandler.queueFunction(queueHandler.eventQueue, speech.speakMessage, gesture.displayName) 306 307 gesture.reportExtra() 308 309 if script: 310 scriptHandler.queueScript(script, gesture) 311 return 312 313 raise NoInputGestureAction
314
315 - def _handleInputHelp(self, gesture, onlyLog=False):
316 textList = [gesture.displayName] 317 script = gesture.script 318 runScript = False 319 logMsg = "Input help: gesture %s"%gesture.logIdentifier 320 if script: 321 scriptName = scriptHandler.getScriptName(script) 322 logMsg+=", bound to script %s" % scriptName 323 scriptLocation = scriptHandler.getScriptLocation(script) 324 if scriptLocation: 325 logMsg += " on %s" % scriptLocation 326 if scriptName == "toggleInputHelp": 327 runScript = True 328 else: 329 desc = script.__doc__ 330 if desc: 331 textList.append(desc) 332 333 log.info(logMsg) 334 if onlyLog: 335 return 336 337 import braille 338 braille.handler.message("\t\t".join(textList)) 339 # Punctuation must be spoken for the gesture name (the first chunk) so that punctuation keys are spoken. 340 speech.speakText(textList[0], reason=speech.REASON_MESSAGE, symbolLevel=characterProcessing.SYMLVL_ALL) 341 for text in textList[1:]: 342 speech.speakMessage(text) 343 344 if runScript: 345 script(gesture)
346
347 - def loadUserGestureMap(self):
348 self.userGestureMap.clear() 349 try: 350 self.userGestureMap.load(os.path.join(globalVars.appArgs.configPath, "gestures.ini")) 351 except IOError: 352 log.debugWarning("No user gesture map")
353
354 - def loadLocaleGestureMap(self):
355 self.localeGestureMap.clear() 356 lang = languageHandler.getLanguage() 357 try: 358 self.localeGestureMap.load(os.path.join("locale", lang, "gestures.ini")) 359 except IOError: 360 try: 361 self.localeGestureMap.load(os.path.join("locale", lang.split('_')[0], "gestures.ini")) 362 except IOError: 363 log.debugWarning("No locale gesture map for language %s" % lang)
364
365 - def emulateGesture(self, gesture):
366 """Convenience method to emulate a gesture. 367 First, an attempt will be made to execute the gesture using L{executeGesture}. 368 If that fails, the gesture will be sent to the operating system if possible using L{InputGesture.send}. 369 @param gesture: The gesture to execute. 370 @type gesture: L{InputGesture} 371 """ 372 try: 373 return self.executeGesture(gesture) 374 except NoInputGestureAction: 375 pass 376 try: 377 gesture.send() 378 except NotImplementedError: 379 pass
380
381 -def normalizeGestureIdentifier(identifier):
382 """Normalize a gesture identifier so that it matches other identifiers for the same gesture. 383 Any items separated by a + sign after the source are considered to be of indeterminate order 384 and are reordered into Python set ordering. 385 Then the entire identifier is converted to lower case. 386 """ 387 prefix, main = identifier.split(":", 1) 388 main = main.split("+") 389 # The order of the parts doesn't matter as far as the user is concerned, 390 # but we need them to be in a determinate order so they will match other gesture identifiers. 391 # We use Python's set ordering. 392 main = "+".join(frozenset(main)) 393 return u"{0}:{1}".format(prefix, main).lower()
394 395 #: The singleton input manager instance. 396 #: @type: L{InputManager} 397 manager = None 398
399 -def initialize():
400 """Initializes input core, creating a global L{InputManager} singleton. 401 """ 402 global manager 403 manager=InputManager()
404
405 -def terminate():
406 """Terminates input core. 407 """ 408 global manager 409 manager=None
410