brailleNote.py
8.97 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
#brailleDisplayDrivers/brailleNote.py
#A part of NonVisual Desktop Access (NVDA)
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.
# Copyright (C) 2011, 2012 Rui Batista <ruiandrebatista@gmail.com>
""" Braille Display driver for the BrailleNote notetakers in terminal mode.
USB, serial and bluetooth communications are supported.
QWERTY keyboard input and scroll weels are not yet supported.
"""
from collections import OrderedDict
import itertools
import serial
import wx
import braille
import brailleInput
import hwPortUtils
import inputCore
from logHandler import log
BLUETOOTH_NAMES = ("Braillenote",)
BLUETOOTH_ADDRS = (
# (first, last),
(0x0025EC000000, 0x0025EC01869F), # Apex
)
USB_IDS = frozenset((
"VID_1C71&PID_C004", # Apex
))
BAUD_RATE = 38400
TIMEOUT = 0.1
READ_INTERVAL = 50
# Tags sent by the braillenote
# Combinations of dots 1...6
DOTS_TAG = 0x80
# combinations of dots 1...6 plus the space bar
DOTS_SPACE_TAG = 0x81
# Combinations of dots 1..6 plus space bar and backspace
DOTS_BACKSPACE_TAG = 0x82
# Combinations of dots 1..6 plus space bar and enter
DOTS_ENTER_TAG = 0x83
# Combinations of one or two Thunb keys
THUNB_KEYS_TAG = 0x84
# Cursor Routing keys
CURSOR_KEY_TAG = 0x85
# Status
STATUS_TAG = 0x86
DESCRIBE_TAG = "\x1B?"
DISPLAY_TAG = "\x1bB"
ESCAPE = '\x1b'
# Dots
DOT_1 = 0x1
DOT_2 = 0x2
DOT_3 = 0x4
DOT_4 = 0x8
DOT_5 = 0x10
DOT_6 = 0x20
DOT_7 = 0x40
DOT_8 = 0x80
# Thumb keys
THUMB_PREVIOUS = 0x01
THUMB_BACK = 0x02
THUMB_ADVANCE = 0x04
THUMB_NEXT = 0x08
_keyNames = {
THUMB_PREVIOUS : "tprevious",
THUMB_BACK : "tback",
THUMB_ADVANCE : "tadvance",
THUMB_NEXT : "tnext",
0 : "space"
}
# Dots:
# Backspace is dot7 and enter dot8
_dotNames = {}
for i in xrange(1,9):
key = globals()["DOT_%d" % i]
_dotNames[key] = "d%d" % i
class BrailleDisplayDriver(braille.BrailleDisplayDriver):
name = "brailleNote"
# Translators: Names of braille displays
description = _("HumanWare BrailleNote")
@classmethod
def check(cls):
return True
@classmethod
def _getUSBPorts(cls):
return (p["port"] for p in hwPortUtils.listComPorts()
if p["hardwareID"].startswith("USB\\") and any(p["hardwareID"][4:].startswith(id) for id in USB_IDS))
@classmethod
def _getBluetoothPorts(cls):
for p in hwPortUtils.listComPorts():
try:
addr = p["bluetoothAddress"]
name = p["bluetoothName"]
except KeyError:
continue
if (any(first <= addr <= last for first, last in BLUETOOTH_ADDRS)
or any(name.startswith(prefix) for prefix in BLUETOOTH_NAMES)):
yield p["port"]
@classmethod
def getPossiblePorts(cls):
ports = OrderedDict()
usb = bluetooth = False
# See if we have any USB ports available:
try:
cls._getUSBPorts().next()
usb = True
except StopIteration:
pass
# See if we have any bluetooth ports available:
try:
cls._getBluetoothPorts().next()
bluetooth = True
except StopIteration:
pass
if usb or bluetooth:
ports.update([cls.AUTOMATIC_PORT])
if usb:
ports["usb"] = "USB"
if bluetooth:
ports["bluetooth"] = "Bluetooth"
for p in hwPortUtils.listComPorts():
# Translators: Name of a serial communications port
ports[p["port"]] = _("Serial: {portName}").format(portName=p["friendlyName"])
return ports
def __init__(self, port="auto"):
super(BrailleDisplayDriver, self).__init__()
self._serial = None
self._buffer = ""
if port == "auto":
portsToTry = itertools.chain(self._getUSBPorts(), self._getBluetoothPorts())
elif port == "usb":
portsToTry = self._getUSBPorts()
elif port == "bluetooth":
portsToTry = self._getBluetoothPorts()
else:
portsToTry = (port,)
found = False
for port in portsToTry:
log.debug("Checking port %s for a BrailleNote", port)
try:
self._serial = serial.Serial(port, baudrate=BAUD_RATE, timeout=TIMEOUT, writeTimeout=TIMEOUT, parity=serial.PARITY_NONE)
except serial.SerialException:
continue
# Check for cell information
if self._describe():
log.debug("BrailleNote found on %s with %d cells", port, self.numCells)
found = True
break
else:
self._serial.close()
if not found:
raise RuntimeError("Can't find a braillenote device (port = %s)" % port)
# start reading keys
self._readTimer = wx.PyTimer(self._readKeys)
self._readTimer.Start(READ_INTERVAL)
def terminate(self):
try:
super(BrailleDisplayDriver, self).terminate()
self._readTimer.Stop()
self._readTimer = None
finally:
self._closeComPort()
def _closeComPort(self):
if self._readTimer is not None:
self._readTimer.Stop()
self._readTimer = None
if self._serial is not None:
log.debug("Closing port %s", self._serial.port)
self._serial.close()
self._serial = None
def _describe(self):
log.debug("Writing sdescribe tag")
self._serial.write(DESCRIBE_TAG)
# This seems always able to read the three bytes, but if someone complain it might be better to retry
packet = self._serial.read(3)
log.debug("Read %d bytes", len(packet))
if len(packet) != 3 or packet[0] != chr(STATUS_TAG):
log.debug("Not a braillenote")
return False
self._numCells = ord(packet[2])
return True
def _get_numCells(self):
return self._numCells
def _readKeys(self):
try:
while self._serial is not None and self._serial.inWaiting():
command, arg = self._readPacket()
if command:
self._dispatch(command, arg)
except serial.SerialException:
self._closeComPort()
# Reraise to be logged
raise
def _readPacket(self):
self._buffer += self._serial.read(2 - len(self._buffer))
if len(self._buffer) < 2:
return None, None
command, arg = ord(self._buffer[0]), ord(self._buffer[1])
self._buffer = ""
return command, arg
def _dispatch(self, command, arg):
space = False
if command == THUNB_KEYS_TAG :
gesture = InputGesture(keys=arg)
elif command == CURSOR_KEY_TAG:
gesture = InputGesture(routing=arg)
elif command in (DOTS_TAG, DOTS_SPACE_TAG, DOTS_ENTER_TAG, DOTS_BACKSPACE_TAG):
if command != DOTS_TAG:
space = True
if command == DOTS_ENTER_TAG:
# Stuppid bug in the implementation
# Force dot8 here, although it should be already there
arg |= DOT_8
gesture = InputGesture(dots=arg, space=space)
else:
log.debugWarning("Unknown command")
return
try:
inputCore.manager.executeGesture(gesture)
except inputCore.NoInputGestureAction:
pass
def display(self, cells):
# if the serial port is not open don't even try to write
if self._serial is None:
return
# ESCAPE must be quoted because it is a control character
cells = [chr(cell).replace(ESCAPE, ESCAPE * 2) for cell in cells]
try:
self._serial.write(DISPLAY_TAG + "".join(cells))
except serial.SerialException, e:
self._closeComPort()
raise
gestureMap = inputCore.GlobalGestureMap({
"globalCommands.GlobalCommands": {
"braille_scrollBack": ("br(braillenote):tback",),
"braille_scrollForward": ("br(braillenote):tadvance",),
"braille_previousLine": ("br(braillenote):tprevious",),
"braille_nextLine": ("br(braillenote):tnext",),
"braille_routeTo": ("br(braillenote):routing",),
"braille_toggleTether": ("br(braillenote):tprevious+tnext",),
"kb:upArrow": ("br(braillenote):space+d1",),
"kb:downArrow": ("br(braillenote):space+d4",),
"kb:leftArrow": ("br(braillenote):space+d3",),
"kb:rightArrow": ("br(braillenote):space+d6",),
"kb:pageup": ("br(braillenote):space+d1+d3",),
"kb:pagedown": ("br(braillenote):space+d4+d6",),
"kb:home": ("br(braillenote):space+d1+d2",),
"kb:end": ("br(braillenote):space+d4+d5",),
"kb:control+home": ("br(braillenote):space+d1+d2+d3",),
"kb:control+end": ("br(braillenote):space+d4+d5+d6",),
"kb:enter": ("br(braillenote):space+d8",),
"kb:shift+tab": ("br(braillenote):space+d1+d2+d5+d6",),
"kb:tab": ("br(braillenote):space+d2+d3+d4+d5",),
"kb:backspace": ("br(braillenote):space+d7",),
"showGui": ("br(braillenote):space+d1+d3+d4+d5",),
"kb:windows": ("br(braillenote):space+d2+d4+d5+d6",),
"kb:alt": ("br(braillenote):space+d1+d3+d4",),
"toggleInputHelp": ("br(braillenote):space+d2+d3+d6",),
},
})
class InputGesture(braille.BrailleDisplayGesture, brailleInput.BrailleInputGesture):
source = BrailleDisplayDriver.name
def __init__(self, keys=None, dots=None, space=False, routing=None):
super(braille.BrailleDisplayGesture, self).__init__()
# see what thumb keys are pressed:
names = set()
if keys is not None:
names.update(_keyNames[1 << i] for i in xrange(4)
if (1 << i) & keys)
elif dots is not None:
# now the dots
self.dots = dots
if space:
self.space = space
names.add(_keyNames[0])
names.update(_dotNames[1 << i] for i in xrange(8)
if (1 << i) & dots)
elif routing is not None:
self.routingIndex = routing
names.add('routing')
self.id = "+".join(names)