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

Source Code for Module BrlAPIDevice

  1  ''' 
  2  This device provides the braille interface to brlapi (brltty) 
  3   
  4  @var MAX_KEY_CHORD: brlapi only handles a key press event, thus making chords  
  5    impossible.  All key events coming back from braille devices are abstacted  
  6    by brlapi to a single key_cmd. 
  7  @type MAX_KEY_CHORD: integer 
  8   
  9  @author: Scott Haeger 
 10  @organization: IBM Corporation 
 11  @copyright: Copyright (c) 2005, 2007 IBM Corporation 
 12  @license: The BSD License 
 13   
 14  @author: Martina Weicht 
 15  @organization: IT Science Center Ruegen gGmbH, Germany 
 16  @copyright: Copyright (c) 2007, 2008 ITSC Ruegen 
 17  @license: The BSD License 
 18   
 19  All rights reserved. This program and the accompanying materials are made 
 20  available under the terms of the BSD license which accompanies 
 21  this distribution, and is available at 
 22  U{http://www.opensource.org/licenses/bsd-license.php} 
 23  ''' 
 24  import brlapi, gobject, time, logging, array 
 25  from AccessEngine import AEOutput, AEInput 
 26  import Braille 
 27  from Tools.i18n import _ 
 28  from AccessEngine.AEConstants import * 
 29   
 30  __uie__ = dict(kind='device') 
 31   
 32  MAX_KEY_CHORD = 1 
 33  TTY = 7 
 34  log = logging.getLogger('BrlAPIDevice') 
 35   
36 -def _keyCodeToKeyName(command):
37 ''' 38 Gets the name of the given key code by using the brlapi's module dictionary 39 to lookup its command value and return its command name. If the code is not 40 known, an empty string is returned. 41 42 @note: brlapi delivers commands in the tuple (type, command, argument, flags) 43 44 @param command: Hardware keycode command 45 @type command: integer 46 @return: Name of the key 47 @rtype: string 48 ''' 49 for name, value in brlapi.__dict__.items(): 50 if command == value: 51 return name 52 return ''
53 54
55 -class BrlAPIStyle(Braille.BrailleStyle):
56 ''' 57 Overrides the base L{Braille.BrailleStyle} class. All properties 58 currently reside in base class. 59 '''
60 - def getGroups(self):
61 ''' 62 Gets configurable absolute settings affecting all output from this device. 63 64 @return: Root group of all configurable settings 65 @rtype: L{AEState.Setting.Group} 66 ''' 67 root = self.newGroup() 68 if self.isDefault(): 69 # generate a group for standard braille settings 70 self._newBrailleGroup(root) 71 return root
72
73 -class BrlAPIDevice(Braille.Braille, AEInput.AEInput):
74 ''' 75 Braille output via Brlapi interface. 76 77 @ivar brlconn: connection object to brlapi 78 @type brlconn: object 79 @ivar writestruct: write structure for brlapi.write() 80 @type writestruct: object 81 @ivar inputmngr: Input listener thread reference 82 @type inputmngr: object 83 @ivar last_style: The last style object seen 84 @type last_style: integer 85 @ivar commands: List of output commands 86 @type commands: list 87 @ivar caretpos: The one-based cell position of the caret. 88 @type caretpos: integer 89 ''' 90 USE_THREAD = False 91 STYLE = BrlAPIStyle 92 # mw: roles in addition to capabilities - auslagern in Konstanten! 93 ROLE = 'output' 94 EllipsisStyles = [ (chr(brlapi.DOT4 + \ 95 brlapi.DOT5 + brlapi.DOT6)+ \ 96 chr(brlapi.DOT1 + brlapi.DOT2 + brlapi.DOT3+\ 97 brlapi.DOT4 + brlapi.DOT6)+chr(0),chr(0)+ \ 98 chr(brlapi.DOT4+brlapi.DOT5 + brlapi.DOT6)+\ 99 chr(brlapi.DOT1 + brlapi.DOT2 + brlapi.DOT3+\ 100 brlapi.DOT4 + brlapi.DOT6)), \ 101 (''.center(3,chr(brlapi.DOT3))+chr(0),\ 102 chr(0)+''.center(3,chr(brlapi.DOT3)))] 103
104 - def init(self):
105 ''' 106 Initializes the braille interface. 107 108 @raise AEOutput.InitError: When the device can not be initialized 109 ''' 110 AEInput.AEInput.__init__(self) 111 112 self.brlconn = None 113 self.writestruct = None 114 self.inputmngr = None 115 self.last_style = None 116 self.commands = [] 117 self.caretpos = 0 118 self.registered_keys = {} 119 120 #mw: Das Herstellen der Verbindung zur Braillezeile ist angelehnt 121 # an die Vorgehensweise von Orca. Die urspruengliche Version von LSR 122 # lief nur unzuverlaessig. 123 try: 124 self.brlconn = brlapi.Connection() 125 try: 126 ttymode = self.brlconn.enterTtyModeWithPath() 127 except: 128 ttymode = self.brlconn.enterTtyMode(TTY) 129 130 self.writestruct = brlapi.WriteStruct() 131 132 except brlapi.ConnectionError: 133 raise AEOutput.InitError 134 except TypeError: 135 raise AEOutput.BrlttyError 136 137 # load key command bindings and put in __class__.__dict__ 138 self._loadKeyCmdConstants() 139 140 # tell brlapi to not send me any key_cmds. Each task will register for key 141 # TODO fix when brltty API settles down 142 # self.brlconn.ignoreKeyRange([0, brlapi.KEY_MAX]) 143 self.brlconn.ignoreAllKeys() 144 145 # start input event manager 146 self.watcher = gobject.io_add_watch(self.brlconn.fileDescriptor, 147 gobject.IO_IN, 148 self._readInput) 149 150 # populate display size properties 151 self.default_style.DisplayColumns = self.brlconn.displaySize[0] 152 self.default_style.DisplayRows = self.brlconn.displaySize[1] 153 self.default_style.TotalCells = self.brlconn.displaySize[0] * \ 154 self.brlconn.displaySize[1]
155
156 - def postInit(self):
157 ''' 158 Called after the L{init} method and after either 159 L{AccessEngine.AEDevice.AEOutput.Base.AEOutput.loadStyles} or 160 L{AccessEngine.AEDevice.AEOutput.Base.AEOutput.createDistinctStyles}. 161 missingcellcnt is the number of missing (broken) cells defined by the user 162 and stored in the L{Setting} string "CellMask". 163 164 @raise Error.InitError: When a communication or state problem exists for 165 the specific device 166 ''' 167 self.default_style.missingcellcnt = self.default_style.CellMask.count('0')
168
169 - def getBrlConnection(self):
170 ''' 171 Returns the connection object to brltty 172 173 @return: A brlapi connection object 174 @rtype: brlapi.Connection 175 ''' 176 return self.brlconn
177 178 # ------------- begin input related methods -----------------------------
179 - def _loadKeyCmdConstants(self):
180 ''' 181 Reads brlapi constants and puts them in this class 182 ''' 183 for name, value in brlapi.__dict__.items(): 184 if name.startswith('KEY_CMD'): 185 setattr(self.__class__, name, value)
186
187 - def _readInput(self, arg1, arg2):
188 key = self.brlconn.readKey(wait=False) 189 if key is not None: 190 # Callback requires True/False return value. _onKeyClick returns True/False 191 # based on it's success 192 return self._onKeyClick(key)
193
194 - def _onKeyClick(self, key):
195 ''' 196 Handles a key click event by acting on the hardware key code in 197 key. 198 199 @param key: a brlapi structure representing a key click event 200 @type key: B{brlapi.key} 201 @return: successfully notified listeners 202 @rtype: boolean 203 ''' 204 try: 205 k = self.brlconn.expandKeyCode(key) 206 except brlapi.OperationError: 207 log.warn('braille input failed') 208 return False 209 print "Key %ld (%x %x %x %x) !" % (key, k["type"], k["command"], \ 210 k["argument"], k["flags"]) 211 print "keycode=", _keyCodeToKeyName(k["command"]) 212 213 gesture = AEInput.Gesture(self) 214 gesture.addActionCode(k['command']) 215 216 # use default arguments or create key_cmd specific ones 217 if k['command'] == brlapi.KEY_CMD_ROUTE: 218 if len(self.default_style.CellMask) == self.default_style.TotalCells: 219 # pressed a dead cell 220 if self.default_style.CellMask[k['argument']] == '0': 221 return True 222 # adjust cell number by number of missing cells between selected and left end 223 for i in range(k['argument']): 224 if self.default_style.CellMask[i] == '0': 225 k["argument"] -= 1 226 227 # notify listeners of the gesture with given args at the timestamp 228 self._notifyInputListeners(gesture, time.time(), \ 229 argument=k["argument"], flags=k["flags"]) 230 return True
231
232 - def getMaxActions(self):
233 ''' 234 Gets the maximum number of actions that can be in a L{Gesture} on this 235 input device. 236 237 @return: Maximum number of actions per L{Gesture} supported by this device 238 @rtype: integer 239 ''' 240 return MAX_KEY_CHORD
241
242 - def asString(self, gesture):
243 ''' 244 Gets a human readable representation of the given L{Gesture}. 245 246 @param gesture: L{Gesture} object to render as text 247 @type gesture: L{Gesture} 248 @return: Text representation of the L{Gesture} 249 @rtype: string 250 ''' 251 return ','.join([_keyCodeToKeyName(i) for i in gesture.getActionCodes()])
252
253 - def addKeyCmd(self, codes):
254 ''' 255 Registers each KEY_CMD within codes. 256 257 @param codes: list of lists of KEY_CMD* codes 258 @type codes: list 259 @raise NotImplementedError: When this method is not overridden by a subclass 260 ''' 261 for codelist in codes: 262 for code in codelist: 263 if self.registered_keys.has_key(code): 264 self.registered_keys[code] += 1 265 else: 266 self.registered_keys[code] = 1 267 self.brlconn.acceptKeys(brlapi.rangeType_command,[brlapi.KEY_TYPE_CMD|code])
268
269 - def removeKeyCmd(self, codes):
270 ''' 271 Unregisters each KEY_CMD within codes. 272 273 @param codes: list of lists of KEY_CMD* codes 274 @type codes: list 275 @raise NotImplementedError: When this method is not overridden by a subclass 276 ''' 277 for codelist in codes: 278 for code in codelist: 279 try: 280 self.registered_keys[code] -= 1 281 if self.registered_keys[code] == 0: 282 del self.registered_keys[code] 283 self.brlconn.ignoreKeys(brlapi.rangeType_command,[brlapi.KEY_TYPE_CMD|code]) 284 except KeyError: 285 pass
286 287 # ------------- begin output related methods ----------------------------
288 - def close(self):
289 ''' 290 Closes the braille device. 291 ''' 292 gobject.source_remove(self.watcher) 293 294 # this call has blocked in past if brltty is offline. 295 self.brlconn.leaveTtyMode() 296 297 del self.brlconn 298 299 self.commands = [] 300 self.brlconn = None
301
302 - def sendString(self, text, style):
303 ''' 304 Adds the given text and associated style to command list. Text will be 305 output to the braille device in sendTalk. 306 307 @param text: String to be output 308 @type text: string 309 @param style: Style with which this string should be output; None means no 310 style change should be applied 311 @type style: integer 312 ''' 313 self.commands.append((CMD_STRING, text, style))
314
315 - def sendTalk(self, style=None):
316 ''' 317 Iterates through list of commands and builds output string including user 318 selected ellipsis and caret. 319 320 @param style: Ignored 321 @type style: L{AccessEngine.AEOutput.AEOutput.Style} 322 ''' 323 stream = [] 324 truncateleft = False 325 truncateright = False 326 # iterate through commands and build pre-output string and set local vars 327 for command, value, style in self.commands: 328 if style is not None and (style.isDirty() or self.last_style != style): 329 self.last_style = style 330 if command is CMD_STRING: 331 if value is not None: 332 stream.append(value) 333 elif command is CMD_TRUNCATE: 334 truncateleft = value[0] 335 truncateright = value[1] 336 337 # get preliminary output string as an array of unicode 338 outarray = array.array('u', ''.join(stream)) 339 340 if len(self.default_style.CellMask) == self.default_style.TotalCells: 341 # insert space for missing cell. min for protection 342 for i in xrange(min(len(outarray),self.default_style.TotalCells)): 343 if self.default_style.CellMask[i] == '0': 344 outarray.insert(i, u' ') 345 if i < self.caretpos: 346 self.caretpos += 1 347 elif len(self.default_style.CellMask) != 0: 348 log.info("Missing cell mask not equal to display length") 349 350 # make sure length equals display length before sending to device 351 if len(outarray) < self.default_style.TotalCells: 352 for i in xrange(self.default_style.TotalCells - len(outarray)): 353 outarray.append(u' ') 354 elif len(outarray) > self.default_style.TotalCells: 355 outarray = outarray[0:self.default_style.TotalCells] 356 357 # initialize and/or masks 358 andmask = array.array('c', ''.center(self.default_style.TotalCells, \ 359 chr(brlapi.DOT1 + brlapi.DOT2 +\ 360 brlapi.DOT3 + brlapi.DOT4 +\ 361 brlapi.DOT5 + brlapi.DOT6 +\ 362 brlapi.DOT7 + brlapi.DOT8))) 363 ormask = array.array('c', ''.center(self.default_style.TotalCells, chr(0))) 364 365 # add ellipsis properties to masks 366 if truncateleft and self.default_style.EllipsisLeft: 367 for i in range(len(self.EllipsisStyles[self.default_style.EllipsisStyle][0])): 368 andmask[i] = chr(0) 369 ormask[i] = self.EllipsisStyles[self.default_style.EllipsisStyle][0][i] 370 outarray[i] = u' ' # corrects brltty/xwindows bug 371 if truncateright and self.default_style.EllipsisRight: 372 count = 0 373 for i in range(self.default_style.TotalCells-len(self.EllipsisStyles[self.default_style.EllipsisStyle][1]), self.default_style.TotalCells): 374 andmask[i] = chr(0) 375 #mw: produzierte Exception 376 # TODO: reproduzieren, Ursache finden, beheben 377 #ormask[i] = self.EllipsisStyles[self.default_style.EllipsisStyle][1][count] 378 outarray[i] = u' ' # corrects brltty/xwindows bug 379 count += 1 380 381 # add cursor properties to mask and set cursor position 382 if self.caretpos <= 0 or self.caretpos > self.default_style.TotalCells: 383 self.writestruct.cursor = 0 384 elif self.default_style.CaretStyle is CARET_NONE: 385 self.writestruct.cursor = 0 386 elif self.default_style.CaretStyle is CARET_TWO_BOTTOM: 387 ormask[self.caretpos-1] = chr(brlapi.DOT7 + brlapi.DOT8) 388 self.writestruct.cursor = self.caretpos 389 # TODO: check pins for CaretBottomRight 390 elif self.default_style.CaretStyle is CARET_BOTTOM_RIGHT: 391 ormask[self.caretpos-1] = chr(brlapi.DOT6 + brlapi.DOT8) 392 self.writestruct.cursor = self.caretpos 393 else: # style.CaretStyle is style.CaretAll: 394 ormask[self.caretpos-1] = chr(brlapi.DOT1 + brlapi.DOT2 +\ 395 brlapi.DOT3 + brlapi.DOT4 +\ 396 brlapi.DOT5 + brlapi.DOT6 +\ 397 brlapi.DOT7 + brlapi.DOT8) 398 self.writestruct.cursor = self.caretpos 399 400 # set write structure fields 401 self.writestruct.attrAnd = andmask.tostring() 402 self.writestruct.attrOr = ormask.tostring() 403 self.writestruct.text = outarray.tounicode() 404 self.writestruct.regionBegin = 1 405 self.writestruct.regionSize = self.default_style.TotalCells 406 407 # write to device 408 try: 409 self.brlconn.write(self.writestruct) 410 except brlapi.OperationError: 411 log.warn('braille output failed') 412 413 self.commands = []
414 415
416 - def sendTruncate(self, left, right, style):
417 ''' 418 Sends indicators of whether text was truncated on either side of the 419 current line or not. The style object is used by the device in deciding how 420 the truncation should be presented. 421 422 @param left: Was text truncated to the left? 423 @type left: boolean 424 @param right: Was text truncated to the right? 425 @type right: boolean 426 @param style: Style with which the truncation should be indicated 427 @type style: L{AccessEngine.AEOutput.AEOutput.Style} 428 @raise NotImplementedError: When not overridden in a subclass 429 ''' 430 self.commands.append((CMD_TRUNCATE, (left,right), style))
431
432 - def sendCaret(self, pos, style):
433 ''' 434 Sends the current caret position relative to the first cell (zero-offset) 435 on the device. The style object is used by the device in deciding how the 436 caret should be presented. 437 @note: braille displays are one-based. We will offset here. 438 @param pos: Zero-offset cell position of the caret, up to the device to 439 change to one-offset if need be 440 @type pos: string 441 @param style: Style with which the caret should be indicated 442 @type style: L{AccessEngine.AEOutput.AEOutput.Style} 443 @raise NotImplementedError: When not overridden in a subclass 444 ''' 445 self.caretpos = pos + 1
446
447 - def sendGetEllipsisSizes(self, style):
448 ''' 449 @param style: Style with which the ellipsis are defined 450 @type style: L{AccessEngine.AEOutput.AEOutput.Style} 451 @return: tuple containing length of left and right ellipsis 452 @rtype: tuple 453 @raise NotImplementedError: When not overridden in a subclass 454 ''' 455 left = 0 456 right = 0 457 458 if self.default_style.EllipsisLeft: 459 left = len(self.EllipsisStyles[self.default_style.EllipsisStyle][0]) 460 if self.default_style.EllipsisRight: 461 right = len(self.EllipsisStyles[self.default_style.EllipsisStyle][1]) 462 463 return (left, right)
464
465 - def sendGetMissingCellCount(self):
466 ''' 467 @return: integer containing missing cell count 468 @rtype: integer 469 @raise NotImplementedError: When not overridden in a subclass 470 ''' 471 return self.default_style.missingcellcnt
472
473 - def isActive(self):
474 ''' 475 Indicates whether the device has text waiting to be output. 476 477 @return: True if the braille device has text to be output, False otherwise 478 @rtype: boolean 479 ''' 480 if len(self.commands) > 0: 481 return True 482 else: 483 return False
484
485 - def sendStop(self, style=None):
486 ''' 487 Stops braille output immediately. 488 489 @param style: Ignored 490 @type style: L{AccessEngine.AEOutput.AEOutput.Style} 491 ''' 492 self.commands = [] 493 try: 494 self.brlconn.writeText("", cursor=0) 495 except brlapi.OperationError: 496 log.warn('braille output failed') 497 pass
498