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
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
56 '''
57 Overrides the base L{Braille.BrailleStyle} class. All properties
58 currently reside in base class.
59 '''
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
70 self._newBrailleGroup(root)
71 return root
72
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
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
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
121
122
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
138 self._loadKeyCmdConstants()
139
140
141
142
143 self.brlconn.ignoreAllKeys()
144
145
146 self.watcher = gobject.io_add_watch(self.brlconn.fileDescriptor,
147 gobject.IO_IN,
148 self._readInput)
149
150
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
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
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
193
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
217 if k['command'] == brlapi.KEY_CMD_ROUTE:
218 if len(self.default_style.CellMask) == self.default_style.TotalCells:
219
220 if self.default_style.CellMask[k['argument']] == '0':
221 return True
222
223 for i in range(k['argument']):
224 if self.default_style.CellMask[i] == '0':
225 k["argument"] -= 1
226
227
228 self._notifyInputListeners(gesture, time.time(), \
229 argument=k["argument"], flags=k["flags"])
230 return True
231
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
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
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
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
289 '''
290 Closes the braille device.
291 '''
292 gobject.source_remove(self.watcher)
293
294
295 self.brlconn.leaveTtyMode()
296
297 del self.brlconn
298
299 self.commands = []
300 self.brlconn = None
301
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
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
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
338 outarray = array.array('u', ''.join(stream))
339
340 if len(self.default_style.CellMask) == self.default_style.TotalCells:
341
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
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
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
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' '
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
376
377
378 outarray[i] = u' '
379 count += 1
380
381
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
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:
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
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
408 try:
409 self.brlconn.write(self.writestruct)
410 except brlapi.OperationError:
411 log.warn('braille output failed')
412
413 self.commands = []
414
415
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
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
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
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
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
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