def update_brl_display_gesture_map(cls, display=braille.handler.display): if not isinstance(display.gestureMap, inputCore.GlobalGestureMap): display.gestureMap = inputCore.GlobalGestureMap() source = "bk:" if cmpNVDAver(2018, 3) < 0 else "br({0}):".format( cls.source) for g, f in GlobalPlugin.default_bk_gestures.items(): display.gestureMap.add(source + g, *f)
class BrailleDisplayDriver(braille.BrailleDisplayDriver): """ HIMS SyncBraille braille display. """ name = "syncBraille" # Translators: The name of a braille display. description = _("HIMS SyncBraille") @classmethod def check(cls): return bool(himsSyncBrailleLib) def __init__(self): super(BrailleDisplayDriver, self).__init__() self._messageWindowClassAtom = windll.user32.RegisterClassExW( byref(nvdaHIMSBrlWndCls)) self._messageWindow = windll.user32.CreateWindowExW( 0, self._messageWindowClassAtom, u"nvdaHIMSBrlWndCls window", 0, 0, 0, 0, 0, None, None, appInstance, None) if himsSyncBrailleLib.OpenSyncBrl(self._messageWindow, nvdaHIMSBrlWm) == 1: return raise RuntimeError("No display found") def terminate(self): super(BrailleDisplayDriver, self).terminate() himsSyncBrailleLib.CloseSyncBrl() windll.user32.DestroyWindow(self._messageWindow) windll.user32.UnregisterClassW(self._messageWindowClassAtom, appInstance) def _get_numCells(self): return himsSyncBrailleLib.GetCellCount() def display(self, cells): cells = "".join([chr(x) for x in cells]) himsSyncBrailleLib.SendSyncBrl(cells) gestureMap = inputCore.GlobalGestureMap({ "globalCommands.GlobalCommands": { "braille_routeTo": ("br(syncBraille):routing", ), "brailleScrollBack": ("br(syncBraille):leftSideScrollDown", ), "brailleScrollForward": ("br(syncBraille):rightSideScrollDown", ), } })
class BrailleDisplayDriver(braille.BrailleDisplayDriver): name = "lilli" # Translators: Name of a braille display. description = _("MDV Lilli") @classmethod def check(cls): return bool(lilliDll) def __init__(self): global lilliCellsMap super(BrailleDisplayDriver, self).__init__() lilliCellsMap = [convertLilliCells(x) for x in range(256)] if (lilliDll.Init408USB()): self._keyCheckTimer = wx.PyTimer(self._handleKeyPresses) self._keyCheckTimer.Start(KEY_CHECK_INTERVAL) else: raise RuntimeError("No display found") def terminate(self): super(BrailleDisplayDriver, self).terminate() try: self._keyCheckTimer.Stop() self._keyCheckTimer = None except: pass lilliDll.Close408USB() def _get_numCells(self) -> int: return 40 def _handleKeyPresses(self): while True: key: Optional[int] = None try: # Python 3: review required # The code seems to assume this returns an int. # I haven't confirmed this. key = lilliDll.ReadBuf() except: log.debug("", exc_info=True) pass if not key: break if (key <= 0x40) or (0x101 <= key <= 0x128): self._onKeyPress(key) def _onKeyPress(self, key: int): try: if 0x101 <= key <= 0x128: inputCore.manager.executeGesture( InputGesture(ROUTE_COMMAND, key - 0x101)) elif key <= 0x40: inputCore.manager.executeGesture( InputGesture(LILLI_KEYS[key], 0)) except inputCore.NoInputGestureAction: pass def display(self, cells: List[int]): lilliDll.WriteBuf(bytes(lilliCellsMap[x] for x in cells)) gestureMap = inputCore.GlobalGestureMap({ "globalCommands.GlobalCommands": { "braille_routeTo": ("br(lilli):route", ), "braille_scrollBack": ("br(lilli):LF", ), "braille_previousLine": ("br(lilli):UP", ), "braille_nextLine": ("br(lilli):DN", ), "braille_scrollForward": ("br(lilli):RG", ), "kb:shift+tab": ("br(lilli):SLF", ), "kb:tab": ("br(lilli):SRG", ), "kb:alt+tab": ("br(lilli):SDN", ), "kb:alt+shift+tab": ("br(lilli):SUP", ), } })
class BrailleDisplayDriver(braille.BrailleDisplayDriver): _dev: hwIo.IoBase name = SEIKA_NAME # Translators: Name of a braille display. description = _("Seika Notetaker") path = "" isThreadSafe = True for d in hwPortUtils.listHidDevices(): if d["hardwareID"].startswith(hidvidpid): path = d["devicePath"] @classmethod def getManualPorts(cls) -> typing.Iterator[typing.Tuple[str, str]]: """@return: An iterator containing the name and description for each port. """ return braille.getSerialPorts() def __init__(self, port="hid"): super().__init__() self.numCells = 0 self.numBtns = 0 self.numRoutingKeys = 0 self.handle = None self._hidBuffer = b"" self._command: typing.Optional[bytes] = None self._argsLen: typing.Optional[int] = None log.info(f"Seika Notetaker braille driver path: {self.path}") if self.path == "": raise RuntimeError("No MINI-SEIKA display found, no path found") self._dev = dev = hwIo.Hid(path=self.path, onReceive=self._onReceive) if dev._file == INVALID_HANDLE_VALUE: raise RuntimeError("No MINI-SEIKA display found, open error") dev.setFeature(SEIKA_CONFIG) # baud rate, stop bit usw dev.setFeature(SEIKA_CMD_ON) # device on dev.write(SEIKA_REQUEST_INFO) # Request the Info from the device # wait and try to get info from the Braille display for i in range(MAX_READ_ATTEMPTS): # the info-block is about dev.waitForRead(READ_TIMEOUT_SECS) if self.numCells: log.info(f"Seika notetaker on USB-HID," f" Cells {self.numCells}" f" Buttons {self.numBtns}") break if self.numCells == 0: dev.close() raise RuntimeError("No MINI-SEIKA display found, no response") def terminate(self): try: super().terminate() finally: self._dev.close() def display(self, cells: List[int]): # cells will already be padded up to numCells. cellBytes = SEIKA_SEND_TEXT + self.numCells.to_bytes( 1, 'little') + bytes(cells) self._dev.write(cellBytes) def _onReceive(self, data: bytes): """ Note: Further insight into this function would be greatly appreciated. This function is a very simple state machine, each stage represents the collection of a field, when all fields are collected the command they represent can be processed. On each call to _onReceive three bytes are read from the device. The first and third bytes are discarded, the second byte is appended to a buffer. The buffer is accumulated until the buffer has the required number of bytes for the field being collected. There are 3 fields to be collected before a command can be processed: 1: first 3 bytes: command 2: 1 byte: specify length of subsequent arguments in bytes 3: variable length: arguments for command type After accumulating enough bytes for each phase, the buffer is cleared and the next stage is entered. """ COMMAND_LEN = 3 stream = BytesIO(data) cmd = stream.read(3) # Note, first and third bytes are discarded newByte: bytes = cmd[1:2] # use range to return bytes self._hidBuffer += newByte hasCommandBeenCollected = self._command is not None hasArgLenBeenCollected = self._argsLen is not None if ( # still collecting command bytes not hasCommandBeenCollected and len(self._hidBuffer) == COMMAND_LEN): self._command = self._hidBuffer # command found reset and wait for args length self._hidBuffer = b"" elif ( # next byte gives the command + args length hasCommandBeenCollected and not hasArgLenBeenCollected # argsLen has not ): # the data is sent with the following structure # - command name (3 bytes) # - number of subsequent bytes to read (1 byte) # - Args (variable bytes) self._argsLen = ord(newByte) self._hidBuffer = b"" elif ( # now collect the args, hasCommandBeenCollected and hasArgLenBeenCollected and len(self._hidBuffer) == self._argsLen): arg = self._hidBuffer command = self._command # reset state variables self._command = None self._argsLen = None self._hidBuffer = b"" self._processCommand(command, arg) def _processCommand(self, command: bytes, arg: bytes) -> None: if command == SEIKA_INFO: self._handleInfo(arg) elif command == SEIKA_ROUTING: self._handleRouting(arg) elif command == SEIKA_KEYS: self._handleKeys(arg) elif command == SEIKA_KEYS_ROU: self._handleKeysRouting(arg) else: log.warning( f"Seika device has received an unknown command {command}") def _handleInfo(self, arg: bytes): """After sending a request for information from the braille device this data is returned to complete the handshake. """ self.numBtns = arg[0] self.numCells = arg[1] self.numRoutingKeys = arg[2] try: self._description = arg[3:].decode("ascii") except UnicodeDecodeError: log.debugWarning( f"Unable to decode Seika Notetaker description {arg[3:]}") def _handleRouting(self, arg: bytes): routingIndexes = _getRoutingIndexes(arg) for routingIndex in routingIndexes: gesture = InputGestureRouting(routingIndex) try: inputCore.manager.executeGesture(gesture) except inputCore.NoInputGestureAction: log.debug("No action for Seika Notetaker routing command") def _handleKeys(self, arg: bytes): brailleDots = arg[0] key = arg[1] | (arg[2] << 8) gestures = [] if key: gestures.append(InputGesture(keys=key)) if brailleDots: gestures.append(InputGesture(dots=brailleDots)) for gesture in gestures: try: inputCore.manager.executeGesture(gesture) except inputCore.NoInputGestureAction: log.debug("No action for Seika Notetaker keys.") def _handleKeysRouting(self, arg: bytes): self._handleRouting(arg[3:]) self._handleKeys(arg[:3]) gestureMap = inputCore.GlobalGestureMap({ "globalCommands.GlobalCommands": { "braille_routeTo": ("br(seikantk):routing", ), "braille_scrollBack": ("br(seikantk):LB", ), "braille_scrollForward": ("br(seikantk):RB", ), "braille_previousLine": ("br(seikantk):LJ_UP", ), "braille_nextLine": ("br(seikantk):LJ_DOWN", ), "braille_toggleTether": ("br(seikantk):LJ_CENTER", ), "sayAll": ("br(seikantk):SPACE+BACKSPACE", ), "showGui": ("br(seikantk):RB+LB", ), "kb:tab": ("br(seikantk):LJ_RIGHT", ), "kb:shift+tab": ("br(seikantk):LJ_LEFT", ), "kb:upArrow": ("br(seikantk):RJ_UP", ), "kb:downArrow": ("br(seikantk):RJ_DOWN", ), "kb:leftArrow": ("br(seikantk):RJ_LEFT", ), "kb:rightArrow": ("br(seikantk):RJ_RIGHT", ), "kb:shift+upArrow": ("br(seikantk):SPACE+RJ_UP", "br(seikantk):BACKSPACE+RJ_UP"), "kb:shift+downArrow": ("br(seikantk):SPACE+RJ_DOWN", "br(seikantk):BACKSPACE+RJ_DOWN"), "kb:shift+leftArrow": ("br(seikantk):SPACE+RJ_LEFT", "br(seikantk):BACKSPACE+RJ_LEFT"), "kb:shift+rightArrow": ("br(seikantk):SPACE+RJ_RIGHT", "br(seikantk):BACKSPACE+RJ_RIGHT"), "kb:escape": ("br(seikantk):SPACE+RJ_CENTER", ), "kb:windows": ("br(seikantk):BACKSPACE+RJ_CENTER", ), "kb:space": ( "br(seikantk):BACKSPACE", "br(seikantk):SPACE", ), "kb:backspace": ("br(seikantk):d7", ), "kb:pageup": ("br(seikantk):SPACE+LJ_RIGHT", ), "kb:pagedown": ("br(seikantk):SPACE+LJ_LEFT", ), "kb:home": ("br(seikantk):SPACE+LJ_UP", ), "kb:end": ("br(seikantk):SPACE+LJ_DOWN", ), "kb:control+home": ("br(seikantk):BACKSPACE+LJ_UP", ), "kb:control+end": ("br(seikantk):BACKSPACE+LJ_DOWN", ), "kb:enter": ("br(seikantk):RJ_CENTER", "br(seikantk):d8"), }, })
class BrailleDisplayDriver(braille.BrailleDisplayDriver): name = "brailliantB" # Translators: The name of a series of braille displays. description = _("HumanWare Brailliant BI/B series") @classmethod def check(cls): try: next(_getPorts()) except StopIteration: # No possible ports found. return False return True def __init__(self): super(BrailleDisplayDriver, self).__init__() self.numCells = 0 for portType, port in _getPorts(): # Try talking to the display. try: self._ser = serial.Serial(port, baudrate=BAUD_RATE, parity=PARITY, timeout=TIMEOUT, writeTimeout=TIMEOUT) except serial.SerialException: continue # This will cause the number of cells to be returned. self._sendMessage(MSG_INIT) # #5406: With the new USB driver, the first command is ignored after a reconnection. # Worse, if we don't receive a reply, # _handleResponses freezes for some reason despite the timeout. # Send the init message again just in case. self._sendMessage(MSG_INIT) self._handleResponses(wait=True) if not self.numCells: # HACK: When connected via bluetooth, the display sometimes reports communication not allowed on the first attempt. self._sendMessage(MSG_INIT) self._handleResponses(wait=True) if self.numCells: # A display responded. log.info( "Found display with {cells} cells connected via {type} ({port})" .format(cells=self.numCells, type=portType, port=port)) break else: raise RuntimeError("No display found") self._readTimer = wx.PyTimer(self._handleResponses) self._readTimer.Start(READ_INTERVAL) self._keysDown = set() self._ignoreKeyReleases = False def terminate(self): try: super(BrailleDisplayDriver, self).terminate() self._readTimer.Stop() self._readTimer = None finally: # We absolutely must close the Serial object, as it does not have a destructor. # If we don't, we won't be able to re-open it later. self._ser.close() def _sendMessage(self, msgId, payload=""): if isinstance(payload, (int, bool)): payload = chr(payload) self._ser.write( "{header}{id}{length}{payload}".format(header=HEADER, id=msgId, length=chr(len(payload)), payload=payload)) def _handleResponses(self, wait=False): while wait or self._ser.inWaiting(): msgId, payload = self._readPacket() if msgId: self._handleResponse(msgId, payload) wait = False def _readPacket(self): # Wait for the header. while True: char = self._ser.read(1) if char == HEADER: break msgId = self._ser.read(1) length = ord(self._ser.read(1)) payload = self._ser.read(length) return msgId, payload def _handleResponse(self, msgId, payload): if msgId == MSG_INIT_RESP: if ord(payload[0]) != 0: # Communication not allowed. log.debugWarning( "Display at %r reports communication not allowed" % self._ser.port) return self.numCells = ord(payload[2]) elif msgId == MSG_KEY_DOWN: payload = ord(payload) self._keysDown.add(payload) # This begins a new key combination. self._ignoreKeyReleases = False elif msgId == MSG_KEY_UP: payload = ord(payload) if not self._ignoreKeyReleases and self._keysDown: try: inputCore.manager.executeGesture( InputGesture(self._keysDown)) except inputCore.NoInputGestureAction: pass # Any further releases are just the rest of the keys in the combination being released, # so they should be ignored. self._ignoreKeyReleases = True self._keysDown.discard(payload) else: log.debugWarning( "Unknown message: id {id!r}, payload {payload!r}".format( id=msgId, payload=payload)) def display(self, cells): # cells will already be padded up to numCells. self._sendMessage(MSG_DISPLAY, "".join(chr(cell) for cell in cells)) gestureMap = inputCore.GlobalGestureMap({ "globalCommands.GlobalCommands": { "braille_scrollBack": ("br(brailliantB):left", ), "braille_scrollForward": ("br(brailliantB):right", ), "braille_previousLine": ("br(brailliantB):up", ), "braille_nextLine": ("br(brailliantB):down", ), "braille_routeTo": ("br(brailliantB):routing", ), "braille_toggleTether": ("br(brailliantB):up+down", ), "kb:upArrow": ("br(brailliantB):space+dot1", ), "kb:downArrow": ("br(brailliantB):space+dot4", ), "kb:leftArrow": ("br(brailliantB):space+dot3", ), "kb:rightArrow": ("br(brailliantB):space+dot6", ), "showGui": ("br(brailliantB):c1+c3+c4+c5", ), "kb:shift+tab": ("br(brailliantB):space+dot1+dot3", ), "kb:tab": ("br(brailliantB):space+dot4+dot6", ), "kb:alt": ("br(brailliantB):space+dot1+dot3+dot4", ), "kb:escape": ("br(brailliantB):space+dot1+dot5", ), "kb:enter": ("br(brailliantB):dot8", ), "kb:windows+d": ("br(brailliantB):c1+c4+c5", ), "kb:windows": ("br(brailliantB):space+dot3+dot4", ), "kb:alt+tab": ("br(brailliantB):space+dot2+dot3+dot4+dot5", ), "sayAll": ("br(brailliantB):c1+c2+c3+c4+c5+c6", ), }, })
class BrailleDisplayDriver(braille.BrailleDisplayDriver): """ EcoBraille display driver. """ name = "ecoBraille" # Translators: The name of a braille display. description = _("EcoBraille displays") @classmethod def check(cls): return True @classmethod def getPossiblePorts(cls): ports = OrderedDict() 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): super(BrailleDisplayDriver, self).__init__() self._port = (port) # Try to open port self._dev = serial.Serial(self._port, baudrate=19200, bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE) # Use a longer timeout when waiting for initialisation. self._dev.timeout = self._dev.writeTimeout = 2.7 self._ecoType = eco_in_init(self._dev) # Use a shorter timeout hereafter. self._dev.timeout = self._dev.writeTimeout = TIMEOUT # Always send the protocol answer. self._dev.write("\x61\x10\x02\xf1\x57\x57\x57\x10\x03") self._dev.write("\x10\x02\xbc\x00\x00\x00\x00\x00\x10\x03") # Start keyCheckTimer. self._readTimer = wx.PyTimer(self._handleResponses) self._readTimer.Start(READ_INTERVAL) def terminate(self): super(BrailleDisplayDriver, self).terminate() try: self._dev.write("\x61\x10\x02\xf1\x57\x57\x57\x10\x03") self._readTimer.Stop() self._readTimer = None finally: self._dev.close() self._dev = None def _get_numCells(self): return self._ecoType def display(self, cells): try: self._dev.write(eco_out(cells)) except: pass def _handleResponses(self): if self._dev.inWaiting(): command = eco_in(self._dev) if command: try: self._handleResponse(command) except KeyError: pass def _handleResponse(self, command): if command in (ECO_KEY_STATUS1, ECO_KEY_STATUS2, ECO_KEY_STATUS3, ECO_KEY_STATUS4): # Nothing to do with the status cells return 0 if (command < ECO_END_ROUTING) and (command >= ECO_START_ROUTING): # Routing try: inputCore.manager.executeGesture( InputGestureRouting(((command - ECO_START_ROUTING) >> 24) + 1)) except: log.debug( "EcoBraille: No function associated with this routing key {key}" .format(key=command)) elif command > 0: # Button try: inputCore.manager.executeGesture(InputGestureKeys(command)) except inputCore.NoInputGestureAction: log.debug( "EcoBraille: No function associated with this Braille key {key}" .format(key=command)) return 0 gestureMap = inputCore.GlobalGestureMap({ "globalCommands.GlobalCommands": { "braille_routeTo": "br(ecoBraille):routing", "braille_previousLine": "br(ecoBraille):T1", "braille_nextLine": "br(ecoBraille):T5", "braille_scrollBack": "br(ecoBraille):T2", "braille_scrollForward": "br(ecoBraille):T4", "review_activate": "br(ecoBraille):T3", "reviewMode_next": "br(ecoBraille):F1", "navigatorObject_parent": "br(ecoBraille):F2", "reviewMode_previous": "br(ecoBraille):F3", "navigatorObject_previous": "br(ecoBraille):F4", "navigatorObject_current": "br(ecoBraille):F5", "navigatorObject_next": "br(ecoBraille):F6", "navigatorObject_toFocus": "br(ecoBraille):F7", "navigatorObject_firstChild": "br(ecoBraille):F8", "navigatorObject_moveFocus": "br(ecoBraille):F9", "navigatorObject_currentDimensions": "br(ecoBraille):F0", "braille_toggleTether": "br(ecoBraille):A", } })
class BrailleDisplayDriver(braille.BrailleDisplayDriver): name = "baum" # Translators: Names of braille displays. description = _("Baum/HumanWare/APH/Orbit braille displays") isThreadSafe = True @classmethod def check(cls): return True @classmethod def getPossiblePorts(cls): ports = OrderedDict() comPorts = list(hwPortUtils.listComPorts(onlyAvailable=True)) try: next(cls._getAutoPorts(comPorts)) ports.update((cls.AUTOMATIC_PORT, )) except StopIteration: pass for portInfo in comPorts: # Translators: Name of a serial communications port. ports[portInfo["port"]] = _("Serial: {portName}").format( portName=portInfo["friendlyName"]) return ports @classmethod def _getAutoPorts(cls, comPorts): for portInfo in hwPortUtils.listHidDevices(): if portInfo.get("usbID") in USB_IDS_HID: yield portInfo["devicePath"], "USB HID" # Try bluetooth ports last. for portInfo in sorted(comPorts, key=lambda item: "bluetoothName" in item): port = portInfo["port"] hwID = portInfo["hardwareID"] if hwID.startswith(r"FTDIBUS\COMPORT"): # USB. portType = "USB serial" try: usbID = hwID.split("&", 1)[1] except IndexError: continue if usbID not in USB_IDS_SER: continue elif hwID == r"USB\VID_0483&PID_5740&REV_0200": # Generic STMicroelectronics Virtual COM Port used by Orbit Reader 20. portType = "USB serial" elif "bluetoothName" in portInfo: # Bluetooth. portType = "bluetooth" btName = portInfo["bluetoothName"] if not any( btName.startswith(prefix) for prefix in BLUETOOTH_NAMES): continue else: continue yield port, portType def __init__(self, port="Auto"): super(BrailleDisplayDriver, self).__init__() self.numCells = 0 self._deviceID = None if port == "auto": tryPorts = self._getAutoPorts( hwPortUtils.listComPorts(onlyAvailable=True)) else: tryPorts = ((port, "serial"), ) for port, portType in tryPorts: # At this point, a port bound to this display has been found. # Try talking to the display. self.isHid = portType == "USB HID" try: if self.isHid: self._dev = hwIo.Hid(port, onReceive=self._onReceive) else: self._dev = hwIo.Serial(port, baudrate=BAUD_RATE, timeout=TIMEOUT, writeTimeout=TIMEOUT, onReceive=self._onReceive) except EnvironmentError: continue if self.isHid: try: # It's essential to send protocol on for the Orbit Reader 20. self._sendRequest(BAUM_PROTOCOL_ONOFF, True) except EnvironmentError: # Pronto! and VarioUltra don't support BAUM_PROTOCOL_ONOFF. pass # Explicitly request device info. # Even where it's supported, BAUM_PROTOCOL_ONOFF doesn't always return device info. self._sendRequest(BAUM_REQUEST_INFO, 0) else: # Serial # If the protocol is already on, sending protocol on won't return anything. # First ensure it's off. self._sendRequest(BAUM_PROTOCOL_ONOFF, False) # This will cause the device id, serial number and number of cells to be returned. self._sendRequest(BAUM_PROTOCOL_ONOFF, True) # Send again in case the display misses the first one. self._sendRequest(BAUM_PROTOCOL_ONOFF, True) for i in xrange(3): # An expected response hasn't arrived yet, so wait for it. self._dev.waitForRead(TIMEOUT) if self.numCells and self._deviceID: break if self.numCells: # A display responded. log.info("Found {device} connected via {type} ({port})".format( device=self._deviceID, type=portType, port=port)) break self._dev.close() else: raise RuntimeError("No Baum display found") self._keysDown = {} self._ignoreKeyReleases = False def terminate(self): try: super(BrailleDisplayDriver, self).terminate() try: self._sendRequest(BAUM_PROTOCOL_ONOFF, False) except EnvironmentError: # Some displays don't support BAUM_PROTOCOL_ONOFF. pass finally: # Make sure the device gets closed. # If it doesn't, we may not be able to re-open it later. self._dev.close() def _sendRequest(self, command, arg=""): if isinstance(arg, (int, bool)): arg = chr(arg) if self.isHid: self._dev.write(command + arg) else: self._dev.write("\x1b{command}{arg}".format(command=command, arg=arg.replace( ESCAPE, ESCAPE * 2))) def _onReceive(self, data): if self.isHid: # data contains the entire packet. stream = StringIO(data) else: if data != ESCAPE: log.debugWarning("Ignoring byte before escape: %r" % data) return # data only contained the escape. Read the rest from the device. stream = self._dev command = stream.read(1) length = BAUM_RSP_LENGTHS.get(command, 0) if command == BAUM_ROUTING_KEYS: length = 10 if self.numCells > 40 else 5 arg = stream.read(length) if command == BAUM_DEVICE_ID and arg == "Refreshabraille ": # For most Baum devices, the argument is 16 bytes, # but it is 18 bytes for the Refreshabraille. arg += stream.read(2) self._handleResponse(command, arg) def _handleResponse(self, command, arg): if command == BAUM_CELL_COUNT: self.numCells = ord(arg) elif command == BAUM_DEVICE_ID: # Short ids can be padded with either nulls or spaces. self._deviceID = arg.rstrip("\0 ") elif command in KEY_NAMES: arg = sum( ord(byte) << offset * 8 for offset, byte in enumerate(arg)) if arg < self._keysDown.get(command, 0): # Release. if not self._ignoreKeyReleases: # The first key released executes the key combination. try: inputCore.manager.executeGesture( InputGesture(self._keysDown)) except inputCore.NoInputGestureAction: pass # Any further releases are just the rest of the keys in the combination being released, # so they should be ignored. self._ignoreKeyReleases = True else: # Press. # This begins a new key combination. self._ignoreKeyReleases = False if arg > 0: self._keysDown[command] = arg elif command in self._keysDown: # All keys in this group have been released. # #3541: Remove this group so it doesn't count as a group with keys down. del self._keysDown[command] elif command == BAUM_POWERDOWN: log.debug("Power down") elif command in (BAUM_COMMUNICATION_CHANNEL, BAUM_SERIAL_NUMBER): pass else: log.debugWarning("Unknown command {command!r}, arg {arg!r}".format( command=command, arg=arg)) def display(self, cells): # cells will already be padded up to numCells. self._sendRequest(BAUM_DISPLAY_DATA, "".join(chr(cell) for cell in cells)) gestureMap = inputCore.GlobalGestureMap({ "globalCommands.GlobalCommands": { "braille_scrollBack": ("br(baum):d2", ), "braille_scrollForward": ("br(baum):d5", ), "braille_previousLine": ("br(baum):d1", ), "braille_nextLine": ("br(baum):d3", ), "braille_routeTo": ("br(baum):routing", ), "kb:upArrow": ("br(baum):up", ), "kb:downArrow": ("br(baum):down", ), "kb:leftArrow": ("br(baum):left", ), "kb:rightArrow": ("br(baum):right", ), "kb:enter": ("br(baum):select", ), }, })
class BrailleDisplayDriver(braille.BrailleDisplayDriver, ScriptableObject): name = "handyTech" # Translators: The name of a series of braille displays. description = _("Handy Tech braille displays") isThreadSafe = True receivesAckPackets = True timeout = 0.2 @classmethod def getManualPorts(cls): return braille.getSerialPorts() _dev: Optional[Union[hwIo.Hid, hwIo.Serial]] def __init__(self, port="auto"): super(BrailleDisplayDriver, self).__init__() self.numCells = 0 self._model = None self._ignoreKeyReleases = False self._keysDown = set() self.brailleInput = False self._dotFirmness = 1 self._hidSerialBuffer = b"" self._atc = False self._sleepcounter = 0 for portType, portId, port, portInfo in self._getTryPorts(port): # At this point, a port bound to this display has been found. # Try talking to the display. self.isHid = portType == bdDetect.KEY_HID self.isHidSerial = portId in USB_IDS_HID_CONVERTER self.port = port try: if self.isHidSerial: # This is either the standalone HID adapter cable for older displays, # or an older display with a HID - serial adapter built in self._dev = hwIo.Hid(port, onReceive=self._hidSerialOnReceive) # Send a flush to open the serial channel self._dev.write(HT_HID_RPT_InCommand + HT_HID_CMD_FlushBuffers) elif self.isHid: self._dev = hwIo.Hid(port, onReceive=self._hidOnReceive) else: self._dev = hwIo.Serial(port, baudrate=BAUD_RATE, parity=PARITY, timeout=self.timeout, writeTimeout=self.timeout, onReceive=self._serialOnReceive) except EnvironmentError: log.debugWarning("", exc_info=True) continue self.sendPacket(HT_PKT_RESET) for _i in range(3): # An expected response hasn't arrived yet, so wait for it. self._dev.waitForRead(self.timeout) if self.numCells and self._model: break if self.numCells: # A display responded. self._model.postInit() log.info("Found {device} connected via {type} ({port})".format( device=self._model.name, type=portType, port=port)) # Create the message window on the ui thread. wx.CallAfter(self.create_message_window) break self._dev.close() else: raise RuntimeError("No Handy Tech display found") def create_message_window(self): try: self._sleepcounter = 0 self._messageWindow = InvisibleDriverWindow(self) except WindowsError: log.debugWarning("", exc_info=True) def destroy_message_window(self): try: self._messageWindow.destroy() except WindowsError: log.debugWarning("", exc_info=True) def go_to_sleep(self): self._sleepcounter += 1 if self._dev is not None: # Must sleep before and after closing to ensure the device can be reconnected. time.sleep(self.timeout) self._dev.close() self._dev = None time.sleep(self.timeout) def wake_up(self): if self._sleepcounter > 0: self._sleepcounter -= 1 if self._sleepcounter > 0: # Still not zero after decrementing return # Might throw if device no longer exists. # We leave it to autodetection to grab it when it reappears. if self.isHidSerial: # This is either the standalone HID adapter cable for older displays, # or an older display with a HID - serial adapter built in self._dev = hwIo.Hid(self.port, onReceive=self._hidSerialOnReceive) # Send a flush to open the serial channel self._dev.write(HT_HID_RPT_InCommand + HT_HID_CMD_FlushBuffers) elif self.isHid: self._dev = hwIo.Hid(self.port, onReceive=self._hidOnReceive) else: self._dev = hwIo.Serial(self.port, baudrate=BAUD_RATE, parity=PARITY, timeout=self.timeout, writeTimeout=self.timeout, onReceive=self._serialOnReceive) def terminate(self): try: # Make sure this is called on the ui thread. wx.CallAfter(self.destroy_message_window) super(BrailleDisplayDriver, self).terminate() finally: # We must sleep before closing the connection as not doing this can leave the display in a bad state where it can not be re-initialized. # This has been observed for Easy Braille displays. time.sleep(self.timeout) # Make sure the device gets closed. self._dev.close() # We also must sleep after closing, as it sometimes takes some time for the device to disconnect. # This has been observed for Active Braille displays. time.sleep(self.timeout) def _get_supportedSettings(self): settings = [ braille.BrailleDisplayDriver.BrailleInputSetting(), ] if self._model: # Add the per model supported settings to the list. for cls in self._model.__class__.__mro__: if hasattr(cls, "supportedSettings"): settings.extend(cls.supportedSettings) return settings def _get_atc(self): return self._atc def _set_atc(self, state): if self._atc is state: return if isinstance(self._model, AtcMixin): self.sendExtendedPacket(HT_EXTPKT_SET_ATC_MODE, boolToByte(state)) else: log.debugWarning("Changing ATC setting for unsupported device %s" % self._model.name) # Regardless whether this setting is supported or not, we want to safe its state. self._atc = state def _get_dotFirmness(self): return self._dotFirmness def _set_dotFirmness(self, value): if self._dotFirmness is value: return if isinstance(self._model, TimeSyncFirmnessMixin): self.sendExtendedPacket(HT_EXTPKT_SET_FIRMNESS, intToByte(value)) else: log.debugWarning( "Changing dot firmness setting for unsupported device %s" % self._model.name) # Regardless whether this setting is supported or not, we want to safe its state. self._dotFirmness = value def sendPacket(self, packetType: bytes, data: bytes = b""): if self._sleepcounter > 0: return if self.isHid: self._sendHidPacket(packetType + data) else: self._dev.write(packetType + data) def sendExtendedPacket(self, packetType: bytes, data: bytes = b""): if self._sleepcounter > 0: log.debug("Packet discarded as driver was requested to sleep") return packetBytes: bytes = b"".join([ intToByte(len(data) + len(packetType)), packetType, data, b"\x16" ]) if self._model: packetBytes = self._model.deviceId + packetBytes self.sendPacket(HT_PKT_EXTENDED, packetBytes) def _sendHidPacket(self, packet: bytes): assert self.isHid maxBlockSize = self._dev._writeSize - 3 # When the packet length exceeds C{writeSize}, the packet is split up into several packets. # They contain C{HT_HID_RPT_InData}, the length of the data block, # the data block itself and a terminating null character. for offset in range(0, len(packet), maxBlockSize): block = packet[offset:offset + maxBlockSize] hidPacket = HT_HID_RPT_InData + intToByte( len(block)) + block + b"\x00" self._dev.write(hidPacket) def _handleKeyRelease(self): if self._ignoreKeyReleases or not self._keysDown: return # The first key released executes the key combination. try: inputCore.manager.executeGesture( InputGesture(self._model, self._keysDown, self.brailleInput)) except inputCore.NoInputGestureAction: pass # Any further releases are just the rest of the keys in the combination # being released, so they should be ignored. self._ignoreKeyReleases = True # pylint: disable=R0912 # Pylint complains about many branches, might be worth refactoring def _hidOnReceive(self, data: bytes): # data contains the entire packet. stream = BytesIO(data) htPacketType = data[2:3] # Skip the header, so reading the stream will only give the rest of the data stream.seek(3) self._handleInputStream(htPacketType, stream) def _hidSerialOnReceive(self, data: bytes): # The HID serial converter wraps one or two bytes into a single HID packet hidLength = data[1] self._hidSerialBuffer += data[2:(2 + hidLength)] self._processHidSerialBuffer() def _processHidSerialBuffer(self): while self._hidSerialBuffer: currentBufferLength = len(self._hidSerialBuffer) htPacketType: bytes = self._hidSerialBuffer[0:1] if htPacketType != HT_PKT_EXTENDED: packetLength = 2 if htPacketType == HT_PKT_OK else 1 if currentBufferLength >= packetLength: stream = BytesIO(self._hidSerialBuffer[:packetLength]) self._hidSerialBuffer: bytes = self._hidSerialBuffer[ packetLength:] else: # The packet is not yet complete return elif htPacketType == HT_PKT_EXTENDED and currentBufferLength >= 5: # Check whether our packet is complete # Extended packets are at least 5 bytes in size. # The second byte is the model, the third byte is the data length, excluding the terminator packet_length = self._hidSerialBuffer[2] + 4 if len(self._hidSerialBuffer) < packet_length: # The packet is not yet complete return # We have a complete packet. # We also isolate it from another packet that could have landed in the buffer, stream = BytesIO(self._hidSerialBuffer[:packet_length]) self._hidSerialBuffer: bytes = self._hidSerialBuffer[ packet_length:] if len(self._hidSerialBuffer) == packet_length: assert self._hidSerialBuffer.endswith( b"\x16"), "Extended packet terminator expected" else: # The packet is not yet complete return stream.seek(1) self._handleInputStream(htPacketType, stream) def _serialOnReceive(self, data: bytes): self._handleInputStream(data, self._dev) def _handleInputStream(self, htPacketType: bytes, stream): if htPacketType in (HT_PKT_OK, HT_PKT_EXTENDED): modelId: bytes = stream.read(1) if not self._model: if modelId not in MODELS: log.debugWarning("Unknown model: %r" % modelId) raise RuntimeError( "The model with ID %r is not supported by this driver" % modelId) self._model = MODELS.get(modelId)(self) self.numCells = self._model.numCells elif self._model.deviceId != modelId: # Somehow the model ID of this display changed, probably another display # plugged in the same (already open) serial port. self.terminate() if htPacketType == HT_PKT_OK: pass elif htPacketType == HT_PKT_ACK: # This is unexpected, but we need to make sure that we handle old style ack self._handleAck() elif htPacketType == HT_PKT_NAK: log.debugWarning("NAK received!") elif htPacketType == HT_PKT_EXTENDED: packet_length = ord(stream.read(1)) packet: bytes = stream.read(packet_length) terminator: bytes = stream.read(1) assert terminator == b"\x16" # Extended packets are terminated with \x16 extPacketType = packet[0:1] if extPacketType == HT_EXTPKT_CONFIRMATION: # Confirmation of a command. if packet[1:2] == HT_PKT_ACK: self._handleAck() elif packet[1:2] == HT_PKT_NAK: log.debugWarning("NAK received!") elif extPacketType == HT_EXTPKT_KEY: self._handleInput(packet[1]) elif extPacketType == HT_EXTPKT_ATC_INFO: # Ignore ATC packets for now pass elif extPacketType == HT_EXTPKT_GET_PROTOCOL_PROPERTIES: pass elif isinstance(self._model, TimeSyncFirmnessMixin): if extPacketType == HT_EXTPKT_GET_RTC: self._model.handleTime(packet[1:]) elif extPacketType == HT_EXTPKT_GET_FIRMNESS: self._dotFirmness = packet[1] else: # Unknown extended packet, log it log.debugWarning("Unhandled extended packet of type %r: %r" % (extPacketType, packet)) else: serPacketOrd = ord(htPacketType) if isinstance( self._model, OldProtocolMixin ) and serPacketOrd & ~KEY_RELEASE_MASK < ord(HT_PKT_EXTENDED): self._handleInput(serPacketOrd) else: # Unknown packet type, log it log.debugWarning("Unhandled packet of type %r" % htPacketType) def _handleInput(self, key: int): release = (key & KEY_RELEASE_MASK) != 0 if release: key ^= KEY_RELEASE_MASK self._handleKeyRelease() self._keysDown.discard(key) else: # Press. # This begins a new key combination. self._ignoreKeyReleases = False self._keysDown.add(key) def display(self, cells: List[int]): # cells will already be padded up to numCells. self._model.display(cells) scriptCategory = SCRCAT_BRAILLE def script_toggleBrailleInput(self, _gesture): self.brailleInput = not self.brailleInput if self.brailleInput: # Translators: message when braille input is enabled ui.message(_('Braille input enabled')) else: # Translators: message when braille input is disabled ui.message(_('Braille input disabled')) # Translators: description of the script to toggle braille input script_toggleBrailleInput.__doc__ = _("Toggle braille input") __gestures = { 'br(handytech):space+b1+b3+b4': 'toggleBrailleInput', 'br(handytech):leftSpace+b1+b3+b4': 'toggleBrailleInput', 'br(handytech):rightSpace+b1+b3+b4': 'toggleBrailleInput', 'br(handytech.easybraille):left+b1+b3+b4': 'toggleBrailleInput', 'br(handytech.easybraille):right+b1+b3+b4': 'toggleBrailleInput', 'br(handytech):space+dot1+dot2+dot7': 'toggleBrailleInput', } gestureMap = inputCore.GlobalGestureMap({ "globalCommands.GlobalCommands": { "braille_routeTo": ("br(handyTech):routing", ), "braille_scrollBack": ( "br(handytech):leftSpace", "br(handytech):leftTakTop", "br(handytech):rightTakTop", "br(handytech):b3", "br(handytech):left", ), "braille_previousLine": ("br(handytech):b4", ), "braille_nextLine": ("br(handytech):b5", ), "braille_scrollForward": ( "br(handytech):rightSpace", "br(handytech):leftTakBottom", "br(handytech):rightTakBottom", "br(handytech):b6", "br(handytech):right", ), "braille_toggleTether": ("br(handytech):b2", ), "braille_toggleFocusContextPresentation": ("br(handytech):b7", ), "braille_toggleShowCursor": ("br(handytech):b1", ), "kb:shift+tab": ( "br(handytech):leftTakTop+leftTakBottom", "br(handytech):escape", ), "kb:tab": ( "br(handytech):rightTakTop+rightTakBottom", "br(handytech):return", ), "kb:enter": ( "br(handytech):leftTakTop+leftTakBottom+rightTakTop+rightTakBottom", "br(handytech):b8", "br(handytech):escape+return", "br(handytech):joystickAction", ), "kb:alt": ("br(handytech):b2+b4+b5", ), "kb:escape": ("br(handytech):b4+b6", ), "kb:upArrow": ("br(handytech):joystickUp", ), "kb:downArrow": ("br(handytech):joystickDown", ), "kb:leftArrow": ("br(handytech):joystickLeft", ), "kb:rightArrow": ("br(handytech):joystickRight", ), "kb:1": ("br(handytech):n1", ), "kb:2": ("br(handytech):n2", ), "kb:3": ("br(handytech):n3", ), "kb:4": ("br(handytech):n4", ), "kb:5": ("br(handytech):n5", ), "kb:6": ("br(handytech):n6", ), "kb:7": ("br(handytech):n7", ), "kb:8": ("br(handytech):n8", ), "kb:9": ("br(handytech):n9", ), "kb:0": ("br(handytech):n0", ), "showGui": ("br(handytech):b2+b4+b5+b6", ), }, })
class BrailleDisplayDriver(braille.BrailleDisplayDriver): name = "hedoMobilLine" description = "hedo MobilLine USB" numCells = HEDO_MOBIL_CELL_COUNT @classmethod def check(cls): return True def __init__(self): super(BrailleDisplayDriver, self).__init__() for portInfo in hwPortUtils.listComPorts(onlyAvailable=True): port = portInfo["port"] hwID = portInfo["hardwareID"] # log.info("Found port {port} with hardwareID {hwID}".format(port=port, hwID=hwID)) if not hwID.startswith(r"FTDIBUS\COMPORT"): continue if HEDO_MOBIL_USBID not in hwID: continue # At this point, a port bound to this display has been found. # Try talking to the display. try: self._ser = serial.Serial(port, baudrate=HEDO_MOBIL_BAUDRATE, timeout=HEDO_MOBIL_TIMEOUT, writeTimeout=HEDO_MOBIL_TIMEOUT, parity=serial.PARITY_ODD, bytesize=serial.EIGHTBITS, stopbits=serial.STOPBITS_ONE) except serial.SerialException: continue # Prepare a blank line cells = chr(HEDO_MOBIL_INIT) + chr(0) * ( HEDO_MOBIL_CELL_COUNT + HEDO_MOBIL_STATUS_CELL_COUNT) # Send the blank line twice self._ser.write(cells) self._ser.flush() self._ser.write(cells) self._ser.flush() # Read out the input buffer ackS = self._ser.read(2) if chr(HEDO_MOBIL_ACK) in ackS: log.info("Found hedo MobilLine connected via {port}".format( port=port)) break else: raise RuntimeError("No display found") self._readTimer = wx.PyTimer(self.handleResponses) self._readTimer.Start(HEDO_MOBIL_READ_INTERVAL) self._keysDown = set() self._released_keys = set() def terminate(self): try: super(BrailleDisplayDriver, self).terminate() self._readTimer.Stop() self._readTimer = None finally: # We absolutely must close the Serial object, as it does not have a destructor. # If we don't, we won't be able to re-open it later. self._ser.close() def display(self, cells): # every transmitted line consists of the preamble HEDO_MOBIL_INIT, the statusCells and the Cells line = chr( HEDO_MOBIL_INIT) + chr(0) * HEDO_MOBIL_STATUS_CELL_COUNT + "".join( chr(cell) for cell in cells) # cells are already padded up numCells # thus the expected length of the line is 1 + HEDO_MOBIL_STATUS_CELL_COUNT + HEDO_MOBIL_CELL_COUNT # ... just how it should be self._ser.write(line) def handleResponses(self, wait=False): while wait or self._ser.inWaiting(): data = self._ser.read(1) if data: # do not handle acknowledge bytes if data != chr(HEDO_MOBIL_ACK): self.handleData(ord(data)) wait = False def handleData(self, data): if data >= HEDO_MOBIL_CR_BEGIN and data <= HEDO_MOBIL_CR_END: # Routing key is pressed try: inputCore.manager.executeGesture( InputGestureRouting(data - HEDO_MOBIL_CR_BEGIN)) except inputCore.NoInputGestureAction: log.debug("No Action for routing index " + index) pass return # On every keypress or keyrelease information about all keys is sent # There are three groups of keys thus three bytes will be sent on # each keypress or release # The 4 MSB of each byte mark the group # Bytes of the form 0x0? include information for B1 to B3 # Bytes of the form 0x1? include information for B4 to B6 # Bytes of the form 0x2? include information for K1 to K3 # The 4 LSB mark the pressed buttons in the group # Are all buttons of one group released, the 4 LSB are zero if data & 0xF0 == 0x00: # B1..B3 if data & 0x01: self._keysDown.add("B1") if data & 0x02: self._keysDown.add("B2") if data & 0x04: self._keysDown.add("B3") if data == 0x00: self._released_keys.add("B1") elif data & 0xF0 == 0x10: # B4..B6 if data & 0x01: self._keysDown.add("B4") if data & 0x02: self._keysDown.add("B5") if data & 0x04: self._keysDown.add("B6") if data == 0x10: self._released_keys.add("B4") elif data & 0xF0 == 0x20: # K1..K3 if data & 0x01: self._keysDown.add("K1") if data & 0x02: self._keysDown.add("K2") if data & 0x04: self._keysDown.add("K3") if data == 0x20: self._released_keys.add("K1") if "B1" in self._released_keys and "B4" in self._released_keys and "K1" in self._released_keys: # all keys are released keys = "+".join(self._keysDown) self._keysDown = set() self._released_keys = set() try: inputCore.manager.executeGesture(InputGestureKeys(keys)) except inputCore.NoInputGestureAction: log.debug("No Action for keys " + keys) pass gestureMap = inputCore.GlobalGestureMap({ "globalCommands.GlobalCommands": { "braille_scrollBack": ("br(hedoMobilLine):K1", ), "braille_toggleTether": ("br(hedoMobilLine):K2", ), "braille_scrollForward": ("br(hedoMobilLine):K3", ), "braille_previousLine": ("br(hedoMobilLine):B2", ), "braille_nextLine": ("br(hedoMobilLine):B5", ), "sayAll": ("br(hedoMobilLine):B6", ), "braille_routeTo": ("br(hedoMobilLine):routing", ), }, })
class BrailleDisplayDriver(braille.BrailleDisplayDriver, ScriptableObject): _dev: hwIo.IoBase # Used to for error checking. _awaitingFrameReceipts: Dict[int, Any] name = "eurobraille" # Translators: Names of braille displays. description = _("Eurobraille Esys/Esytime/Iris displays") isThreadSafe = True timeout = 0.2 supportedSettings = (braille.BrailleDisplayDriver.HIDInputSetting( useConfig=True), ) @classmethod def getManualPorts(cls): return braille.getSerialPorts() def __init__(self, port="Auto"): super(BrailleDisplayDriver, self).__init__() self.numCells = 0 self.deviceType = None self._deviceData = {} self._awaitingFrameReceipts = {} self._frameLength = None self._frame = 0x20 self._frameLock = threading.Lock() self._hidKeyboardInput = False self._hidInputBuffer = b"" for portType, portId, port, portInfo in self._getTryPorts(port): # At this point, a port bound to this display has been found. # Try talking to the display. self.isHid = portType == bdDetect.KEY_HID try: if self.isHid: self._dev = hwIo.Hid( port, onReceive=self._onReceive, # Eurobraille wants us not to block other application's access to this handle. exclusive=False) else: self._dev = hwIo.Serial(port, baudrate=BAUD_RATE, bytesize=serial.EIGHTBITS, parity=serial.PARITY_EVEN, stopbits=serial.STOPBITS_ONE, timeout=self.timeout, writeTimeout=self.timeout, onReceive=self._onReceive) except EnvironmentError: log.debugWarning("Error while connecting to port %r" % port, exc_info=True) continue for i in range(3): # Request device identification self._sendPacket(EB_SYSTEM, EB_SYSTEM_IDENTITY) # Make sure visualisation packets are disabled, as we ignore them anyway. self._sendPacket(EB_VISU, EB_VISU_DOT, EB_FALSE) # A device identification results in multiple packets. # Make sure we've received everything before we continue while self._dev.waitForRead(self.timeout * 2): continue if self.numCells and self.deviceType: break if self.numCells and self.deviceType: # A display responded. log.info("Found {device} connected via {type} ({port})".format( device=self.deviceType, type=portType, port=port)) break self._dev.close() else: raise RuntimeError("No supported Eurobraille display found") self.keysDown = defaultdict(int) self._ignoreCommandKeyReleases = False def terminate(self): try: super(BrailleDisplayDriver, self).terminate() finally: # We must sleep before closing the port as not doing this can leave the display in a bad state where it can not be re-initialized. time.sleep(self.timeout) self._dev.close() self._dev = None self._deviceData.clear() def _prepFirstByteStreamAndData( self, data: bytes) -> (bytes, Union[BytesIO, hwIo.IoBase], bytes): if self.isHid: # data contains the entire packet. # HID Packets start with 0x00. byte0 = data[0:1] assert byte0 == b"\x00", "byte 0 is %r" % byte0 # Check whether there is an incomplete packet in the buffer if self._hidInputBuffer: data = self._hidInputBuffer + data[1:] self._hidInputBuffer = b"" byte1 = data[1:2] stream = BytesIO(data) stream.seek(2) return byte1, stream, data else: # is serial return data, self._dev, data def _onReceive(self, data: bytes): byte1, stream, data = self._prepFirstByteStreamAndData(data) if byte1 == ACK: frame = ord(stream.read(1)) self._handleAck(frame) elif byte1 == STX: length = bytesToInt( stream.read(2)) - 2 # length includes the length itself packet: bytes = stream.read(length) if self.isHid and not stream.read(1) == ETX: # Incomplete packet self._hidInputbuffer = data return packetType: bytes = packet[0:1] packetSubType: bytes = packet[1:2] packetData: bytes = packet[2:] if length > 2 else b"" if packetType == EB_SYSTEM: self._handleSystemPacket(packetSubType, packetData) elif packetType == EB_MODE: if packetSubType == EB_MODE_DRIVER: log.debug( "Braille display switched to driver mode, updating display..." ) braille.handler.update() elif packetSubType == EB_MODE_INTERNAL: log.debug("Braille display switched to internal mode") elif packetType == EB_KEY: self._handleKeyPacket(packetSubType, packetData) elif packetType == EB_IRIS_TEST and packetSubType == EB_IRIS_TEST_sub: # Ping command sent by Iris every two seconds, send it back on the main thread. # This means that, if the main thread is frozen, Iris will be notified of this. log.debug("Received ping from Iris braille display") wx.CallAfter(self._sendPacket, packetType, packetSubType, packetData) elif packetType == EB_VISU: log.debug("Ignoring visualisation packet") elif packetType == EB_ENCRYPTION_KEY: log.debug("Ignoring encryption key packet") else: log.debug("Ignoring packet: type %r, subtype %r, data %r" % (packetType, packetSubType, packetData)) def _handleAck(self, frame: int): try: super(BrailleDisplayDriver, self)._handleAck() except NotImplementedError: log.debugWarning( "Received ACK for frame %d while ACK handling is disabled" % frame) else: try: del self._awaitingFrameReceipts[frame] except KeyError: log.debugWarning("Received ACK for unregistered frame %d" % frame) def _handleSystemPacket(self, packetType: bytes, data: bytes): if packetType == EB_SYSTEM_TYPE: deviceType = ord(data) self.deviceType = DEVICE_TYPES[deviceType] if 0x01 <= deviceType <= 0x06: # Iris self.keys = KEYS_IRIS elif 0x07 <= deviceType <= 0x0d: # Esys self.keys = KEYS_ESYS elif 0x0e <= deviceType <= 0x11: # Esitime self.keys = KEYS_ESITIME else: log.debugWarning("Unknown device identifier %r" % data) elif packetType == EB_SYSTEM_DISPLAY_LENGTH: self.numCells = ord(data) elif packetType == EB_SYSTEM_FRAME_LENGTH: self._frameLength = bytesToInt(data) elif packetType == EB_SYSTEM_PROTOCOL and self.isHid: protocol = data.rstrip(b"\x00 ") try: version = float(protocol[:4]) except ValueError: pass else: self.receivesAckPackets = version >= 3.0 elif packetType == EB_SYSTEM_IDENTITY: return # End of system information self._deviceData[packetType] = data.rstrip(b"\x00 ") def _handleKeyPacket(self, group: bytes, data: bytes): if group == EB_KEY_USB_HID_MODE: assert data in [EB_TRUE, EB_FALSE] self._hidKeyboardInput = EB_TRUE == data return if group == EB_KEY_QWERTY: log.debug("Ignoring Iris AZERTY/QWERTY input") return if group == EB_KEY_INTERACTIVE and data[ 0:1] == EB_KEY_INTERACTIVE_REPETITION: log.debug("Ignoring routing key %d repetition" % (data[1] - 1)) return arg = bytesToInt(data) if arg == self.keysDown[group]: log.debug("Ignoring key repetition") return self.keysDown[group] |= arg isIris = self.deviceType.startswith("Iris") if not isIris and group == EB_KEY_COMMAND and arg >= self.keysDown[ group]: # Started a gesture including command keys self._ignoreCommandKeyReleases = False else: if isIris or group != EB_KEY_COMMAND or not self._ignoreCommandKeyReleases: try: inputCore.manager.executeGesture(InputGesture(self)) except inputCore.NoInputGestureAction: pass self._ignoreCommandKeyReleases = not isIris and ( group == EB_KEY_COMMAND or self.keysDown[EB_KEY_COMMAND] > 0) if not isIris and group == EB_KEY_COMMAND: self.keysDown[group] = arg else: del self.keysDown[group] def _sendPacket(self, packetType: bytes, packetSubType: bytes, packetData: bytes = b""): packetSize = len(packetData) + 4 packetBytes = bytearray(b"".join([ STX, packetSize.to_bytes(2, "big", signed=False), packetType, packetSubType, packetData, ETX ])) if self.receivesAckPackets: with self._frameLock: frame = self._frame # Assumption: frame will only ever be 1 byte, otherwise consider byte order packetBytes.insert(-1, frame) self._awaitingFrameReceipts[frame] = packetBytes self._frame = frame + 1 if frame < 0x7F else 0x20 packetData = bytes(packetBytes) if self.isHid: self._sendHidPacket(packetData) else: self._dev.write(packetData) def _sendHidPacket(self, packet: bytes): assert self.isHid blockSize = self._dev._writeSize - 1 # When the packet length exceeds C{blockSize}, the packet is split up into several block packets. # These blocks are of size C{blockSize}. for offset in range(0, len(packet), blockSize): bytesToWrite = packet[offset:(offset + blockSize)] hidPacket = b"".join([ b"\x00", bytesToWrite, b"\x55" * (blockSize - len(bytesToWrite)) # padding ]) self._dev.write(hidPacket) def display(self, cells: List[int]): # cells will already be padded up to numCells. self._sendPacket(packetType=EB_BRAILLE_DISPLAY, packetSubType=EB_BRAILLE_DISPLAY_STATIC, packetData=bytes(cells)) def _get_hidKeyboardInput(self): return self._hidKeyboardInput def _set_hidKeyboardInput(self, state: bool): self._sendPacket(packetType=EB_KEY, packetSubType=EB_KEY_USB_HID_MODE, packetData=EB_TRUE if state else EB_FALSE) for i in range(3): self._dev.waitForRead(self.timeout) if state is self._hidKeyboardInput: break scriptCategory = SCRCAT_BRAILLE def script_toggleHidKeyboardInput(self, gesture): def announceUnavailableMessage(): # Translators: Message when HID keyboard simulation is unavailable. ui.message(_("HID keyboard input simulation is unavailable.")) if not self.isHid: announceUnavailableMessage() return state = not self.hidKeyboardInput self.hidKeyboardInput = state if state is not self._hidKeyboardInput: announceUnavailableMessage() elif state: # Translators: Message when HID keyboard simulation is enabled. ui.message(_('HID keyboard simulation enabled')) else: # Translators: Message when HID keyboard simulation is disabled. ui.message(_('HID keyboard simulation disabled')) # Translators: Description of the script that toggles HID keyboard simulation. script_toggleHidKeyboardInput.__doc__ = _("Toggle HID keyboard simulation") __gestures = { "br(eurobraille.esytime):l1+joystick1Down": "toggleHidKeyboardInput", "br(eurobraille):switch1Left+joystick1Down": "toggleHidKeyboardInput", "br(eurobraille.esytime):l8+joystick1Down": "toggleHidKeyboardInput", "br(eurobraille):switch1Right+joystick1Down": "toggleHidKeyboardInput", } gestureMap = inputCore.GlobalGestureMap({ "globalCommands.GlobalCommands": { "braille_routeTo": ("br(eurobraille):routing", ), "braille_reportFormatting": ("br(eurobraille):doubleRouting", ), "braille_scrollBack": ( "br(eurobraille):switch1Left", "br(eurobraille):l1", ), "braille_scrollForward": ( "br(eurobraille):switch1Right", "br(eurobraille):l8", ), "braille_toFocus": ( "br(eurobraille):switch1Left+switch1Right", "br(eurobraille):switch2Left+switch2Right", "br(eurobraille):switch3Left+switch3Right", "br(eurobraille):switch4Left+switch4Right", "br(eurobraille):switch5Left+switch5Right", "br(eurobraille):switch6Left+switch6Right", "br(eurobraille):l1+l8", ), "review_previousLine": ("br(eurobraille):joystick1Up", ), "review_nextLine": ("br(eurobraille):joystick1Down", ), "review_previousCharacter": ("br(eurobraille):joystick1Left", ), "review_nextCharacter": ("br(eurobraille):joystick1Right", ), "reviewMode_previous": ("br(eurobraille):joystick1Left+joystick1Up", ), "reviewMode_next": ("br(eurobraille):joystick1Right+joystick1Down", ), # Esys and esytime have a dedicated key for backspace and combines backspace and space to perform a return. "braille_eraseLastCell": ("br(eurobraille):backSpace", ), "braille_enter": ("br(eurobraille):backSpace+space", ), "kb:insert": ( "br(eurobraille):dot3+dot5+space", "br(eurobraille):l7", ), "kb:delete": ("br(eurobraille):dot3+dot6+space", ), "kb:home": ( "br(eurobraille):dot1+dot2+dot3+space", "br(eurobraille):joystick2Left+joystick2Up", ), "kb:end": ( "br(eurobraille):dot4+dot5+dot6+space", "br(eurobraille):joystick2Right+joystick2Down", ), "kb:leftArrow": ( "br(eurobraille):dot2+space", "br(eurobraille):joystick2Left", "br(eurobraille):leftArrow", ), "kb:rightArrow": ( "br(eurobraille):dot5+space", "br(eurobraille):joystick2Right", "br(eurobraille):rightArrow", ), "kb:upArrow": ( "br(eurobraille):dot1+space", "br(eurobraille):joystick2Up", "br(eurobraille):upArrow", ), "kb:downArrow": ( "br(eurobraille):dot6+space", "br(eurobraille):joystick2Down", "br(eurobraille):downArrow", ), "kb:enter": ("br(eurobraille):joystick2Center", ), "kb:pageUp": ("br(eurobraille):dot1+dot3+space", ), "kb:pageDown": ("br(eurobraille):dot4+dot6+space", ), "kb:numpad1": ("br(eurobraille):dot1+dot6+backspace", ), "kb:numpad2": ("br(eurobraille):dot1+dot2+dot6+backspace", ), "kb:numpad3": ("br(eurobraille):dot1+dot4+dot6+backspace", ), "kb:numpad4": ("br(eurobraille):dot1+dot4+dot5+dot6+backspace", ), "kb:numpad5": ("br(eurobraille):dot1+dot5+dot6+backspace", ), "kb:numpad6": ("br(eurobraille):dot1+dot2+dot4+dot6+backspace", ), "kb:numpad7": ("br(eurobraille):dot1+dot2+dot4+dot5+dot6+backspace", ), "kb:numpad8": ("br(eurobraille):dot1+dot2+dot5+dot6+backspace", ), "kb:numpad9": ("br(eurobraille):dot2+dot4+dot6+backspace", ), "kb:numpadInsert": ("br(eurobraille):dot3+dot4+dot5+dot6+backspace", ), "kb:numpadDecimal": ("br(eurobraille):dot2+backspace", ), "kb:numpadDivide": ("br(eurobraille):dot3+dot4+backspace", ), "kb:numpadMultiply": ("br(eurobraille):dot3+dot5+backspace", ), "kb:numpadMinus": ("br(eurobraille):dot3+dot6+backspace", ), "kb:numpadPlus": ("br(eurobraille):dot2+dot3+dot5+backspace", ), "kb:numpadEnter": ("br(eurobraille):dot3+dot4+dot5+backspace", ), "kb:escape": ( "br(eurobraille):dot1+dot2+dot4+dot5+space", "br(eurobraille):l2", ), "kb:tab": ( "br(eurobraille):dot2+dot5+dot6+space", "br(eurobraille):l3", ), "kb:shift+tab": ("br(eurobraille):dot2+dot3+dot5+space", ), "kb:printScreen": ("br(eurobraille):dot1+dot3+dot4+dot6+space", ), "kb:pause": ("br(eurobraille):dot1+dot4+space", ), "kb:applications": ("br(eurobraille):dot5+dot6+backspace", ), "kb:f1": ("br(eurobraille):dot1+backspace", ), "kb:f2": ("br(eurobraille):dot1+dot2+backspace", ), "kb:f3": ("br(eurobraille):dot1+dot4+backspace", ), "kb:f4": ("br(eurobraille):dot1+dot4+dot5+backspace", ), "kb:f5": ("br(eurobraille):dot1+dot5+backspace", ), "kb:f6": ("br(eurobraille):dot1+dot2+dot4+backspace", ), "kb:f7": ("br(eurobraille):dot1+dot2+dot4+dot5+backspace", ), "kb:f8": ("br(eurobraille):dot1+dot2+dot5+backspace", ), "kb:f9": ("br(eurobraille):dot2+dot4+backspace", ), "kb:f10": ("br(eurobraille):dot2+dot4+dot5+backspace", ), "kb:f11": ("br(eurobraille):dot1+dot3+backspace", ), "kb:f12": ("br(eurobraille):dot1+dot2+dot3+backspace", ), "kb:windows": ("br(eurobraille):dot1+dot2+dot3+dot4+backspace", ), "kb:capsLock": ( "br(eurobraille):dot7+backspace", "br(eurobraille):dot8+backspace", ), "kb:numLock": ( "br(eurobraille):dot3+backspace", "br(eurobraille):dot6+backspace", ), "kb:shift": ( "br(eurobraille):dot7+space", "br(eurobraille):l4", ), "braille_toggleShift": ( "br(eurobraille):dot1+dot7+space", "br(eurobraille):dot4+dot7+space", ), "kb:control": ( "br(eurobraille):dot7+dot8+space", "br(eurobraille):l5", ), "braille_toggleControl": ( "br(eurobraille):dot1+dot7+dot8+space", "br(eurobraille):dot4+dot7+dot8+space", ), "kb:alt": ( "br(eurobraille):dot8+space", "br(eurobraille):l6", ), "braille_toggleAlt": ( "br(eurobraille):dot1+dot8+space", "br(eurobraille):dot4+dot8+space", ), }, })
class BrailleDisplayDriver(braille.BrailleDisplayDriver): name = "kgs" description = _(u"KGS BrailleMemo series") _portName = None _directBM = None def __init__(self, port="auto"): super(BrailleDisplayDriver, self).__init__() global fConnection, numCells if not lock(): return if port != self._portName and self._portName: execEndConnection = True log.info("changing connection %s to %s" % (self._portName, port)) elif fConnection: log.info("already connection %s" % port) execEndConnection = False self.numCells = numCells unlock() return else: log.info("first connection %s" % port) execEndConnection = False self.numCells = 0 kgs_dll = os.path.join(kgs_dir, 'DirectBM.dll') self._directBM = windll.LoadLibrary(kgs_dll.encode('mbcs')) if not self._directBM: unlock() raise RuntimeError("No KGS instance found") self._keyCallbackInst = KGS_PKEYCALLBACK(nvdaKgsHandleKeyInfoProc) self._statusCallbackInst = KGS_PSTATUSCALLBACK( nvdaKgsStatusChangedProc) ret, self._portName = bmConnect(self._directBM, port, self._keyCallbackInst, self._statusCallbackInst, execEndConnection) if ret: self.numCells = numCells log.info("connected %s" % port) else: self.numCells = 0 log.info("failed %s" % port) unlock() raise RuntimeError("No KGS display found") unlock() def terminate(self): if not lock(): return log.info("KGS driver terminating") super(BrailleDisplayDriver, self).terminate() if self._directBM and self._directBM._handle: bmDisConnect(self._directBM, self._portName) waitAfterDisconnect() ret = windll.kernel32.FreeLibrary(self._directBM._handle) # ret is not zero if success log.info("KGS driver terminated %d" % ret) self._directBM = None self._portName = None self._keyCallbackInst = None self._statusCallbackInst = None unlock() @classmethod def check(cls): return True @classmethod def getPossiblePorts(cls): ar = [cls.AUTOMATIC_PORT] ports = {} for p in kgsListComPorts(): log.info(p) ports[p["port"]] = p["friendlyName"] log.info(ports) for i in xrange(64): p = "COM%d" % (i + 1) if p in ports: fname = ports[p] ar.append((p, fname)) return OrderedDict(ar) def display(self, data): if not data: return s = '' for c in data: d = 0 if c & 0x01: d += 0x80 if c & 0x02: d += 0x40 if c & 0x04: d += 0x20 if c & 0x08: d += 0x08 if c & 0x10: d += 0x04 if c & 0x20: d += 0x02 if c & 0x40: d += 0x10 if c & 0x80: d += 0x01 s += chr(d) dataBuf = create_string_buffer(s, 256) cursorBuf = create_string_buffer('', 256) try: ret = self._directBM.bmDisplayData(dataBuf, cursorBuf, self.numCells) log.debug("bmDisplayData %d" % ret) except: log.debug("error bmDisplayData") gestureMap = inputCore.GlobalGestureMap({ "globalCommands.GlobalCommands": { "showGui": ("br(kgs):ins", ), "kb:escape": ("br(kgs):esc", ), "kb:windows": ("br(kgs):read", ), "kb:shift": ("br(kgs):select", ), "kb:control": ("br(kgs):ctrl", ), "kb:alt": ("br(kgs):alt", ), "kb:alt+tab": ("br(kgs):alt+inf", ), "kb:enter": ( "br(kgs):enter", "br(kgs):ok", "br(kgs):set", ), "kb:space": ("br(kgs):space", ), "kb:delete": ("br(kgs):del", ), "kb:backspace": ("br(kgs):bs", ), "kb:tab": ("br(kgs):inf", ), "kb:shift+tab": ("br(kgs):select+inf", ), "kb:upArrow": ("br(kgs):upArrow", ), "kb:downArrow": ("br(kgs):downArrow", ), "kb:leftArrow": ("br(kgs):leftArrow", ), "kb:rightArrow": ("br(kgs):rightArrow", ), "kb:shift+upArrow": ("br(kgs):select+upArrow", ), "kb:shift+downArrow": ("br(kgs):select+downArrow", ), "kb:shift+leftArrow": ("br(kgs):select+leftArrow", ), "kb:shift+rightArrow": ("br(kgs):select+rightArrow", ), "review_previousLine": ("br(kgs):bw", ), "review_nextLine": ("br(kgs):fw", ), "review_previousWord": ("br(kgs):ls", ), "review_nextWord": ("br(kgs):rs", ), "braille_routeTo": ("br(kgs):route", ), "braille_scrollBack": ( "br(kgs):func1", "br(kgs):func3+leftArrow", ), "braille_scrollForward": ( "br(kgs):func4", "br(kgs):func3+rightArrow", ), "braille_previousLine": ("br(kgs):func3+upArrow", ), "braille_nextLine": ("br(kgs):func3+downArrow", ), "kb:a": ("br(kgs):dot1", ), "kb:b": ("br(kgs):dot1+dot2", ), "kb:c": ("br(kgs):dot1+dot4", ), "kb:d": ("br(kgs):dot1+dot4+dot5", ), "kb:e": ("br(kgs):dot1+dot5", ), "kb:f": ("br(kgs):dot1+dot2+dot4", ), "kb:g": ("br(kgs):dot1+dot2+dot4+dot5", ), "kb:h": ("br(kgs):dot1+dot2+dot5", ), "kb:i": ("br(kgs):dot2+dot4", ), "kb:j": ("br(kgs):dot2+dot4+dot5", ), "kb:k": ("br(kgs):dot1+dot3", ), "kb:l": ("br(kgs):dot1+dot2+dot3", ), "kb:m": ("br(kgs):dot1+dot3+dot4", ), "kb:n": ("br(kgs):dot1+dot3+dot4+dot5", ), "kb:o": ("br(kgs):dot1+dot3+dot5", ), "kb:p": ("br(kgs):dot1+dot2+dot3+dot4", ), "kb:q": ("br(kgs):dot1+dot2+dot3+dot4+dot5", ), "kb:r": ("br(kgs):dot1+dot2+dot3+dot5", ), "kb:s": ("br(kgs):dot2+dot3+dot4", ), "kb:t": ("br(kgs):dot2+dot3+dot4+dot5", ), "kb:u": ("br(kgs):dot1+dot3+dot6", ), "kb:v": ("br(kgs):dot1+dot2+dot3+dot6", ), "kb:w": ("br(kgs):dot2+dot4+dot5+dot6", ), "kb:x": ("br(kgs):dot1+dot3+dot4+dot6", ), "kb:y": ("br(kgs):dot1+dot3+dot4+dot5+dot6", ), "kb:z": ("br(kgs):dot1+dot3+dot5+dot6", ), "kb:control+a": ("br(kgs):ctrl+dot1", ), "kb:control+b": ("br(kgs):ctrl+dot1+dot2", ), "kb:control+c": ("br(kgs):ctrl+dot1+dot4", ), "kb:control+d": ("br(kgs):ctrl+dot1+dot4+dot5", ), "kb:control+e": ("br(kgs):ctrl+dot1+dot5", ), "kb:control+f": ("br(kgs):ctrl+dot1+dot2+dot4", ), "kb:control+g": ("br(kgs):ctrl+dot1+dot2+dot4+dot5", ), "kb:control+h": ("br(kgs):ctrl+dot1+dot2+dot5", ), "kb:control+i": ("br(kgs):ctrl+dot2+dot4", ), "kb:control+j": ("br(kgs):ctrl+dot2+dot4+dot5", ), "kb:control+k": ("br(kgs):ctrl+dot1+dot3", ), "kb:control+l": ("br(kgs):ctrl+dot1+dot2+dot3", ), "kb:control+m": ("br(kgs):ctrl+dot1+dot3+dot4", ), "kb:control+n": ("br(kgs):ctrl+dot1+dot3+dot4+dot5", ), "kb:control+o": ("br(kgs):ctrl+dot1+dot3+dot5", ), "kb:control+p": ("br(kgs):ctrl+dot1+dot2+dot3+dot4", ), "kb:control+q": ("br(kgs):ctrl+dot1+dot2+dot3+dot4+dot5", ), "kb:control+r": ("br(kgs):ctrl+dot1+dot2+dot3+dot5", ), "kb:control+s": ("br(kgs):ctrl+dot2+dot3+dot4", ), "kb:control+t": ("br(kgs):ctrl+dot2+dot3+dot4+dot5", ), "kb:control+u": ("br(kgs):ctrl+dot1+dot3+dot6", ), "kb:control+v": ("br(kgs):ctrl+dot1+dot2+dot3+dot6", ), "kb:control+w": ("br(kgs):ctrl+dot2+dot4+dot5+dot6", ), "kb:control+x": ("br(kgs):ctrl+dot1+dot3+dot4+dot6", ), "kb:control+y": ("br(kgs):ctrl+dot1+dot3+dot4+dot5+dot6", ), "kb:control+z": ("br(kgs):ctrl+dot1+dot3+dot5+dot6", ), "kb:alt+a": ("br(kgs):alt+dot1", ), "kb:alt+b": ("br(kgs):alt+dot1+dot2", ), "kb:alt+c": ("br(kgs):alt+dot1+dot4", ), "kb:alt+d": ("br(kgs):alt+dot1+dot4+dot5", ), "kb:alt+e": ("br(kgs):alt+dot1+dot5", ), "kb:alt+f": ("br(kgs):alt+dot1+dot2+dot4", ), "kb:alt+g": ("br(kgs):alt+dot1+dot2+dot4+dot5", ), "kb:alt+h": ("br(kgs):alt+dot1+dot2+dot5", ), "kb:alt+i": ("br(kgs):alt+dot2+dot4", ), "kb:alt+j": ("br(kgs):alt+dot2+dot4+dot5", ), "kb:alt+k": ("br(kgs):alt+dot1+dot3", ), "kb:alt+l": ("br(kgs):alt+dot1+dot2+dot3", ), "kb:alt+m": ("br(kgs):alt+dot1+dot3+dot4", ), "kb:alt+n": ("br(kgs):alt+dot1+dot3+dot4+dot5", ), "kb:alt+o": ("br(kgs):alt+dot1+dot3+dot5", ), "kb:alt+p": ("br(kgs):alt+dot1+dot2+dot3+dot4", ), "kb:alt+q": ("br(kgs):alt+dot1+dot2+dot3+dot4+dot5", ), "kb:alt+r": ("br(kgs):alt+dot1+dot2+dot3+dot5", ), "kb:alt+s": ("br(kgs):alt+dot2+dot3+dot4", ), "kb:alt+t": ("br(kgs):alt+dot2+dot3+dot4+dot5", ), "kb:alt+u": ("br(kgs):alt+dot1+dot3+dot6", ), "kb:alt+v": ("br(kgs):alt+dot1+dot2+dot3+dot6", ), "kb:alt+w": ("br(kgs):alt+dot2+dot4+dot5+dot6", ), "kb:alt+x": ("br(kgs):alt+dot1+dot3+dot4+dot6", ), "kb:alt+y": ("br(kgs):alt+dot1+dot3+dot4+dot5+dot6", ), "kb:alt+z": ("br(kgs):alt+dot1+dot3+dot5+dot6", ), "kb:.": ("br(kgs):dot2+dot5+dot6", ), "kb::": ("br(kgs):dot2+dot5", ), "kb:;": ("br(kgs):dot2+dot3", ), "kb:,": ("br(kgs):dot2", ), "kb:-": ("br(kgs):dot3+dot6", ), "kb:?": ("br(kgs):dot2+dot3+dot6", ), "kb:!": ("br(kgs):dot2+dot3+dot5", ), "kb:'": ("br(kgs):dot3", ), } })
class BrailleDisplayDriver(braille.BrailleDisplayDriver): name = "alvaBC6" # Translators: The name of a braille display. description = _("ALVA BC640/680 series") @classmethod def check(cls): return bool(AlvaLib) def __init__(self): super(BrailleDisplayDriver,self).__init__() log.debug("ALVA BC6xx Braille init") _AlvaNumDevices=c_int(0) AlvaLib.AlvaScanDevices(byref(_AlvaNumDevices)) if _AlvaNumDevices.value==0: raise RuntimeError("No ALVA display found") log.debug("%d devices found" %_AlvaNumDevices.value) AlvaLib.AlvaOpen(0) self._alva_NumCells = 0 self._keysDown = set() self._ignoreKeyReleases = False self._keyCallbackInst = ALVA_PKEYCALLBACK(self._keyCallback) AlvaLib.AlvaSetKeyCallback(0, self._keyCallbackInst, None) def terminate(self): super(BrailleDisplayDriver, self).terminate() AlvaLib.AlvaClose(1) # Drop the ctypes function instance for the key callback, # as it is holding a reference to an instance method, which causes a reference cycle. self._keyCallbackInst = None def _get_numCells(self): if self._alva_NumCells==0: NumCells = c_int(0) AlvaLib.AlvaGetCells(0, byref(NumCells)) if NumCells.value==0: raise RuntimeError("Cannot obtain number of cells") self._alva_NumCells = NumCells.value log.info("ALVA BC6xx has %d cells" %self._alva_NumCells) return self._alva_NumCells def display(self, cells): cells="".join([chr(x) for x in cells]) AlvaLib.AlvaSendBraille(0, cells, 0, len(cells)) def _keyCallback(self, dev, key, userData): group = (key >> 8) & 0x7F number = key & 0xFF if key & ALVA_RELEASE_MASK: # Release. if not self._ignoreKeyReleases and self._keysDown: try: inputCore.manager.executeGesture(InputGesture(self._keysDown)) except inputCore.NoInputGestureAction: pass # Any further releases are just the rest of the keys in the combination being released, # so they should be ignored. self._ignoreKeyReleases = True self._keysDown.discard((group, number)) else: # Press. if group == ALVA_CR_GROUP: # Execute routing keys when pressed instead of released. try: inputCore.manager.executeGesture(InputGesture(((group, number),))) except inputCore.NoInputGestureAction: pass else: self._keysDown.add((group, number)) # This begins a new key combination. self._ignoreKeyReleases = False return False gestureMap = inputCore.GlobalGestureMap({ "globalCommands.GlobalCommands": { "braille_scrollBack": ("br(alvaBC6):t1",), "braille_previousLine": ("br(alvaBC6):t2",), "braille_nextLine": ("br(alvaBC6):t4",), "braille_scrollForward": ("br(alvaBC6):t5",), "braille_routeTo": ("br(alvaBC6):routing",), "kb:shift+tab": ("br(alvaBC6):sp1",), "kb:alt": ("br(alvaBC6):sp2",), "kb:escape": ("br(alvaBC6):sp3",), "kb:tab": ("br(alvaBC6):sp4",), "kb:upArrow": ("br(alvaBC6):spUp",), "kb:downArrow": ("br(alvaBC6):spDown",), "kb:leftArrow": ("br(alvaBC6):spLeft",), "kb:rightArrow": ("br(alvaBC6):spRight",), "kb:enter": ("br(alvaBC6):spEnter",), "showGui": ("br(alvaBC6):sp1+sp3",), "kb:windows+d": ("br(alvaBC6):sp1+sp4",), "kb:windows": ("br(alvaBC6):sp2+sp3",), "kb:alt+tab": ("br(alvaBC6):sp2+sp4",), } })
class BrailleDisplayDriver(braille.BrailleDisplayDriver, ScriptableObject): """Handy Tech braille display driver. """ name = "handyTech" # Translators: Names of braille displays. description = _("Handy Tech braille displays") @classmethod def check(cls): try: GUID.from_progid(COM_CLASS) return True except WindowsError: return False def __init__(self): global constants, HT_KEYS super(BrailleDisplayDriver, self).__init__() self._server = comtypes.client.CreateObject(COM_CLASS) import comtypes.gen.HTBRAILLEDRIVERSERVERLib as constants HT_KEYS = {} for key, constant in constants.__dict__.items(): if key.startswith('KEY_'): HT_KEYS[constant] = key[4:].lower().replace('_', '') # Keep the connection object so it won't become garbage self._advise = comtypes.client.GetEvents(self._server, Sink(self._server), constants.IHtBrailleDriverSink) self._server.initialize() def terminate(self): super(BrailleDisplayDriver, self).terminate() self._server.terminate() def _get_numCells(self): return self._server.getCurrentTextLength()[0] def display(self, cells): self._server.displayText(cells) scriptCategory = SCRCAT_BRAILLE def script_showConfig(self, gesture): self._server.startConfigDialog(False) script_showConfig.__doc__ = _("Show the Handy Tech driver configuration window.") gestureMap = inputCore.GlobalGestureMap({ "globalCommands.GlobalCommands": { "braille_scrollBack": ("br(handytech):left", "br(handytech):up"), "braille_previousLine": ("br(handytech):b4",), "braille_nextLine": ("br(handytech):b5",), "braille_scrollForward": ("br(handytech):right", "br(handytech):down"), "braille_routeTo": ("br(handytech):routing",), "kb:shift+tab": ("br(handytech):esc",), "kb:alt": ("br(handytech):b2+b4+b5",), "kb:escape": ("br(handytech):b4+b6",), "kb:tab": ("br(handytech):enter",), "kb:enter": ("br(handytech):esc+enter",), "kb:upArrow": ("br(handytech):leftSpace",), "kb:downArrow": ("br(handytech):rightSpace",), "showGui": ("br(handytech):b2+b4+b5+b6",), } }) __gestures = { 'br(handytech):b4+b8': 'showConfig', }
class BrailleDisplayDriver(braille.BrailleDisplayDriver): name = "superBrl" # Translators: Names of braille displays. description = _("SuperBraille") isThreadSafe = True @classmethod def getManualPorts(cls): return braille.getSerialPorts() def __init__(self, port="Auto"): super(BrailleDisplayDriver, self).__init__() for portType, portId, port, portInfo in self._getTryPorts(port): try: self._dev = hwIo.Serial(port, baudrate=BAUD_RATE, stopbits=serial.STOPBITS_ONE, parity=serial.PARITY_NONE, timeout=TIMEOUT, writeTimeout=TIMEOUT, onReceive=self._onReceive) except EnvironmentError: log.debugWarning("", exc_info=True) continue # try to initialize the device and request number of cells self._dev.write(DESCRIBE_TAG) self._dev.waitForRead(TIMEOUT) # Check for cell information if self.numCells: # ok, it is a SuperBraille log.info("Found superBraille device, version %s" % self.version) break else: self._dev.close() else: raise RuntimeError("No SuperBraille found") def terminate(self): try: super(BrailleDisplayDriver, self).terminate() finally: # We must sleep before closing the COM port as not doing this can leave the display in a bad state where it can not be re-initialized time.sleep(TIMEOUT) self._dev.close() self._dev = None def _onReceive(self, data): # The only info this display ever sends is number of cells and the display version. # It sends 0x00, 0x05, number of cells, then version string of 8 bytes. if data != '\x00': return data = self._dev.read(1) if data != '\x05': return self.numCells = ord(self._dev.read(1)) self._dev.read(1) self.version = self._dev.read(8) def display(self, cells): out = [] for cell in cells: out.append("\x00") out.append(chr(cell)) self._dev.write(DISPLAY_TAG + "".join(out)) gestureMap = inputCore.GlobalGestureMap({ "globalCommands.GlobalCommands": { "braille_scrollBack": ("kb:numpadMinus", ), "braille_scrollForward": ("kb:numpadPlus", ), }, })
class BrailleDisplayDriver(braille.BrailleDisplayDriver): name = "hedoProfiLine" description = "hedo ProfiLine USB" numCells = HEDO_CELL_COUNT @classmethod def check(cls): return True def __init__(self): super(BrailleDisplayDriver, self).__init__() for portInfo in hwPortUtils.listComPorts(onlyAvailable=True): port = portInfo["port"] hwID = portInfo["hardwareID"] #log.info("Found port {port} with hardwareID {hwID}".format(port=port, hwID=hwID)) if not hwID.startswith(r"FTDIBUS\COMPORT"): continue try: usbID = hwID.split("&", 1)[1] except IndexError: continue if usbID not in HEDO_USB_IDS: continue # At this point, a port bound to this display has been found. # Try talking to the display. try: self._ser = serial.Serial(port, baudrate=HEDO_BAUDRATE, timeout=HEDO_TIMEOUT, writeTimeout=HEDO_TIMEOUT, parity=serial.PARITY_ODD, bytesize=serial.EIGHTBITS, stopbits=serial.STOPBITS_ONE) except serial.SerialException: continue # Prepare a blank line totalCells: int = HEDO_CELL_COUNT + HEDO_CELL_COUNT cells: bytes = HEDO_INIT + bytes(totalCells) # Send the blank line twice self._ser.write(cells) self._ser.flush() self._ser.write(cells) self._ser.flush() # Read out the input buffer ackS: bytes = self._ser.read(2) if HEDO_ACK in ackS: log.info("Found hedo ProfiLine connected via {port}".format( port=port)) break else: raise RuntimeError("No hedo display found") self._readTimer = wx.PyTimer(self.handleResponses) self._readTimer.Start(HEDO_READ_INTERVAL) self._keysDown = set() self._ignoreKeyReleases = False def terminate(self): try: super(BrailleDisplayDriver, self).terminate() self._readTimer.Stop() self._readTimer = None finally: # We absolutely must close the Serial object, as it does not have a destructor. # If we don't, we won't be able to re-open it later. self._ser.close() def display(self, cells: List[int]): # every transmitted line consists of the preamble HEDO_INIT, the statusCells and the Cells # add padding so total length is 1 + numberOfStatusCells + numberOfRegularCells cellPadding: bytes = bytes(HEDO_CELL_COUNT - len(cells)) statusPadding: bytes = bytes(HEDO_STATUS_CELL_COUNT) cellBytes: bytes = HEDO_INIT + statusPadding + bytes( cells) + cellPadding self._ser.write(cellBytes) def handleResponses(self, wait=False): while wait or self._ser.in_waiting: data: bytes = self._ser.read(1) if data: # do not handle acknowledge bytes if data != HEDO_ACK: self.handleData(ord(data)) wait = False def handleData(self, data: int): if HEDO_CR_BEGIN <= data <= HEDO_CR_END: # Routing key is pressed try: inputCore.manager.executeGesture( InputGestureRouting(data - HEDO_CR_BEGIN)) except inputCore.NoInputGestureAction: log.debug("No Action for routing command: %d", data) pass elif (HEDO_CR_BEGIN + HEDO_RELEASE_OFFSET) <= data <= ( HEDO_CR_END + HEDO_RELEASE_OFFSET): # Routing key is released return elif data in HEDO_KEYMAP: # A key is pressed # log.debug("Key " + HEDO_KEYMAP[data] + " pressed") self._keysDown.add(HEDO_KEYMAP[data]) self._ignoreKeyReleases = False elif data > HEDO_RELEASE_OFFSET and ( data - HEDO_RELEASE_OFFSET) in HEDO_KEYMAP: # A key is released # log.debug("Key " + str(self._keysDown) + " released") if not self._ignoreKeyReleases: keys = "+".join(self._keysDown) self._ignoreKeyReleases = True self._keysDown = set() try: inputCore.manager.executeGesture(InputGestureKeys(keys)) except inputCore.NoInputGestureAction: log.debug("No Action for keys {keys}".format(keys=keys)) pass # else: # log.debug("Key " + hex(data) + " not identified") gestureMap = inputCore.GlobalGestureMap({ "globalCommands.GlobalCommands": { "braille_scrollBack": ("br(hedoProfiLine):K1", ), "braille_toggleTether": ("br(hedoProfiLine):K2", ), "braille_scrollForward": ("br(hedoProfiLine):K3", ), "braille_previousLine": ("br(hedoProfiLine):B2", ), "braille_nextLine": ("br(hedoProfiLine):B5", ), "sayAll": ("br(hedoProfiLine):B6", ), "braille_routeTo": ("br(hedoProfiLine):routing", ), }, })
class BrailleDisplayDriver(braille.BrailleDisplayDriver): name = "brailliantB" # Translators: The name of a series of braille displays. description = _("HumanWare Brailliant BI/B series / BrailleNote Touch") isThreadSafe = True @classmethod def getManualPorts(cls): return braille.getSerialPorts() def __init__(self, port="auto"): super(BrailleDisplayDriver, self).__init__() self.numCells = 0 for portType, portId, port, portInfo in self._getTryPorts(port): self.isHid = portType == bdDetect.KEY_HID # Try talking to the display. try: if self.isHid: self._dev = hwIo.Hid(port, onReceive=self._hidOnReceive) else: self._dev = hwIo.Serial(port, baudrate=BAUD_RATE, parity=PARITY, timeout=TIMEOUT, writeTimeout=TIMEOUT, onReceive=self._serOnReceive) except EnvironmentError: log.debugWarning("", exc_info=True) continue # Couldn't connect. # The Brailliant can fail to init if you try immediately after connecting. time.sleep(DELAY_AFTER_CONNECT) # Sometimes, a few attempts are needed to init successfully. for attempt in xrange(INIT_ATTEMPTS): if attempt > 0: # Not the first attempt time.sleep(INIT_RETRY_DELAY) # Delay before next attempt. self._initAttempt() if self.numCells: break # Success! if self.numCells: # A display responded. log.info( "Found display with {cells} cells connected via {type} ({port})" .format(cells=self.numCells, type=portType, port=port)) break # This device can't be initialized. Move on to the next (if any). self._dev.close() else: raise RuntimeError("No display found") self._keysDown = set() self._ignoreKeyReleases = False def _initAttempt(self): if self.isHid: try: data = self._dev.getFeature(HR_CAPS) except WindowsError: return # Fail! self.numCells = ord(data[24]) else: # This will cause the display to return the number of cells. # The _serOnReceive callback will see this and set self.numCells. self._serSendMessage(MSG_INIT) self._dev.waitForRead(TIMEOUT) def terminate(self): try: super(BrailleDisplayDriver, self).terminate() finally: # Make sure the device gets closed. # If it doesn't, we may not be able to re-open it later. self._dev.close() def _serSendMessage(self, msgId, payload=""): if isinstance(payload, (int, bool)): payload = chr(payload) self._dev.write( "{header}{id}{length}{payload}".format(header=HEADER, id=msgId, length=chr(len(payload)), payload=payload)) def _serOnReceive(self, data): if data != HEADER: log.debugWarning("Ignoring byte before header: %r" % data) return msgId = self._dev.read(1) length = ord(self._dev.read(1)) payload = self._dev.read(length) self._serHandleResponse(msgId, payload) def _serHandleResponse(self, msgId, payload): if msgId == MSG_INIT_RESP: if ord(payload[0]) != 0: # Communication not allowed. log.debugWarning( "Display at %r reports communication not allowed" % self._dev.port) return self.numCells = ord(payload[2]) elif msgId == MSG_KEY_DOWN: payload = ord(payload) self._keysDown.add(payload) # This begins a new key combination. self._ignoreKeyReleases = False elif msgId == MSG_KEY_UP: payload = ord(payload) self._handleKeyRelease() self._keysDown.discard(payload) else: log.debugWarning( "Unknown message: id {id!r}, payload {payload!r}".format( id=msgId, payload=payload)) def _hidOnReceive(self, data): rId = data[0] if rId == HR_KEYS: keys = data[1:].split("\0", 1)[0] keys = {ord(key) for key in keys} if len(keys) > len(self._keysDown): # Press. This begins a new key combination. self._ignoreKeyReleases = False elif len(keys) < len(self._keysDown): self._handleKeyRelease() self._keysDown = keys elif rId == HR_POWEROFF: log.debug("Powering off") else: log.debugWarning("Unknown report: %r" % data) def _handleKeyRelease(self): if self._ignoreKeyReleases or not self._keysDown: return try: inputCore.manager.executeGesture(InputGesture(self._keysDown)) except inputCore.NoInputGestureAction: pass # Any further releases are just the rest of the keys in the combination being released, # so they should be ignored. self._ignoreKeyReleases = True def display(self, cells): # cells will already be padded up to numCells. cells = "".join(chr(cell) for cell in cells) if self.isHid: outputReport = ( "{id}" "\x01\x00" # Module 1, offset 0 "{length}{cells}".format(id=HR_BRAILLE, length=chr(self.numCells), cells=cells)) #: Humanware HID devices require the use of HidD_SetOutputReport when sending data to the device via HID, as WriteFile seems to block forever or fail to reach the device at all. self._dev.setOutputReport(outputReport) else: self._serSendMessage(MSG_DISPLAY, cells) gestureMap = inputCore.GlobalGestureMap({ "globalCommands.GlobalCommands": { "braille_scrollBack": ("br(brailliantB):left", ), "braille_scrollForward": ("br(brailliantB):right", ), "braille_previousLine": ("br(brailliantB):up", ), "braille_nextLine": ("br(brailliantB):down", ), "braille_routeTo": ("br(brailliantB):routing", ), "braille_toggleTether": ("br(brailliantB):up+down", ), "kb:upArrow": ("br(brailliantB):space+dot1", "br(brailliantB):stickUp"), "kb:downArrow": ("br(brailliantB):space+dot4", "br(brailliantB):stickDown"), "kb:leftArrow": ("br(brailliantB):space+dot3", "br(brailliantB):stickLeft"), "kb:rightArrow": ("br(brailliantB):space+dot6", "br(brailliantB):stickRight"), "showGui": ( "br(brailliantB):c1+c3+c4+c5", "br(brailliantB):space+dot1+dot3+dot4+dot5", ), "kb:shift+tab": ("br(brailliantB):space+dot1+dot3", ), "kb:tab": ("br(brailliantB):space+dot4+dot6", ), "kb:alt": ("br(brailliantB):space+dot1+dot3+dot4", ), "kb:escape": ("br(brailliantB):space+dot1+dot5", ), "kb:enter": ("br(brailliantB):stickAction"), "kb:windows+d": ( "br(brailliantB):c1+c4+c5", "br(brailliantB):Space+dot1+dot4+dot5", ), "kb:windows": ("br(brailliantB):space+dot3+dot4", ), "kb:alt+tab": ("br(brailliantB):space+dot2+dot3+dot4+dot5", ), "sayAll": ( "br(brailliantB):c1+c2+c3+c4+c5+c6", "br(brailliantB):Space+dot1+dot2+dot3+dot4+dot5+dot6", ), }, })
class BrailleDisplayDriver(braille.BrailleDisplayDriver, ScriptableObject): """papenmeier braille display driver. """ _dev: serial.Serial name = "papenmeier" # Translators: Names of braille displays. description = _("Papenmeier BRAILLEX newer models") @classmethod def check(cls): """should return false if there is a missing dependency""" return True def connectBluetooth(self): """try to connect to bluetooth device first, bluetooth is only supported on Braillex Trio""" if (self._baud == 0 and self._dev is None): for portInfo in sorted( hwPortUtils.listComPorts(onlyAvailable=True), key=lambda item: "bluetoothName" in item): port = portInfo["port"] hwID = portInfo["hardwareID"] if "bluetoothName" in portInfo: if portInfo["bluetoothName"][ 0:14] == "braillex trio " or portInfo[ "bluetoothName"][0:13] == "braillex live": try: self._dev = serial.Serial( port, baudrate=57600, timeout=BLUETOOTH_TIMEOUT, writeTimeout=BLUETOOTH_TIMEOUT) log.info("connectBluetooth success") except: log.debugWarning("connectBluetooth failed") def connectUSB(self, devlist: List[bytes]): """Try to connect to usb device, this is triggered when bluetooth connection could not be established""" try: self._dev = ftdi2.open_ex(devlist[0]) self._dev.set_baud_rate(self._baud) self._dev.inWaiting = self._dev.get_queue_status log.info("connectUSB success") except: log.debugWarning("connectUSB failed") def __init__(self): """initialize driver""" super(BrailleDisplayDriver, self).__init__() self.numCells = 0 self._nlk = 0 self._nrk = 0 self.decodedkeys = [] self._baud = 0 self._dev = None self._proto = None devlist: List[bytes] = [] #try to connect to usb device, #if no usb device is found there may be a bluetooth device if ftdi2: devlist = ftdi2.list_devices() if (len(devlist) == 0): self.connectBluetooth() elif ftdi2: self._baud = 57600 self.connectUSB(devlist) if (self._dev is not None): try: #request type of braille display self._dev.write(brl_auto_id()) time.sleep( 0.05 ) # wait 50 ms in order to get response for further actions autoid: bytes = brl_poll(self._dev) if autoid == b'': #no response, assume a Trio is connected self._baud = 115200 self._dev.set_baud_rate(self._baud) self._dev.purge() self._dev.read(self._dev.inWaiting()) #request type of braille display twice because of baudrate change self._dev.write(brl_auto_id()) self._dev.write(brl_auto_id()) time.sleep( 0.05 ) # wait 50 ms in order to get response for further actions autoid = brl_poll(self._dev) if len(autoid) != 8: return else: if (autoid[3] == 0x35 and autoid[4] == 0x38): #EL80s self.numCells = 80 self._nlk = 1 self._nrk = 1 self._proto = 'A' self._voffset = 0 log.info("Found EL80s") elif (autoid[3] == 0x35 and autoid[4] == 0x3A): #EL70s self.numCells = 70 self._nlk = 1 self._nrk = 1 self._proto = 'A' self._voffset = 0 log.info("Found EL70s") elif (autoid[3] == 0x35 and autoid[4] == 0x35): #EL40s self.numCells = 40 self._nlk = 1 self._nrk = 1 self._proto = 'A' self._voffset = 0 log.info("Found EL40s") elif (autoid[3] == 0x35 and autoid[4] == 0x37): #EL66s self.numCells = 66 self._nlk = 1 self._nrk = 1 self._proto = 'A' self._voffset = 0 log.info("Found EL66s") elif (autoid[3] == 0x35 and autoid[4] == 0x3E): #EL20c self.numCells = 20 self._nlk = 1 self._nrk = 1 self._proto = 'A' self._voffset = 0 log.info("Found EL20c") elif (autoid[3] == 0x35 and autoid[4] == 0x3F): #EL40c self.numCells = 40 self._nlk = 1 self._nrk = 1 self._proto = 'A' self._voffset = 0 log.info("Found EL40c") elif (autoid[3] == 0x36 and autoid[4] == 0x30): #EL60c self.numCells = 60 self._nlk = 1 self._nrk = 1 self._proto = 'A' self._voffset = 0 log.info("Found EL60c") elif (autoid[3] == 0x36 and autoid[4] == 0x31): #EL80c self.numCells = 80 self._nlk = 1 self._nrk = 1 self._proto = 'A' self._voffset = 0 log.info("Found EL80c") elif (autoid[3] == 0x35 and autoid[4] == 0x3b): #EL2D80s self.numCells = 80 self._nlk = 1 self._nrk = 1 self._proto = 'A' self._voffset = 20 log.info("Found EL2D80s") elif (autoid[3] == 0x35 and autoid[4] == 0x39): #trio self.numCells = 40 self._proto = 'B' self._voffset = 0 log.info("Found trio") elif (autoid[3] == 0x36 and autoid[4] == 0x34): #live20 self.numCells = 20 self._proto = 'B' self._voffset = 0 log.info("Found live 20") elif (autoid[3] == 0x36 and autoid[4] == 0x33): #live+ self.numCells = 40 self._proto = 'B' self._voffset = 0 log.info("Found live+") elif (autoid[3] == 0x36 and autoid[4] == 0x32): #live self.numCells = 40 self._proto = 'B' self._voffset = 0 log.info("Found live") else: log.debugWarning('UNKNOWN BRAILLE') except: log.debugWarning('BROKEN PIPE - THIS SHOULD NEVER HAPPEN') if (self.numCells == 0): raise Exception('no device found') #start keycheck timer self.startTimer() self.initmapping() def startTimer(self): """start timers used by this driver""" self._keyCheckTimer = wx.PyTimer(self._handleKeyPresses) self._keyCheckTimer.Start(KEY_CHECK_INTERVAL) #the keycheck timer polls the braille display for keypresses self._bluetoothTimer = wx.PyTimer(self.connectBluetooth) self._bluetoothTimer.Start(BLUETOOTH_INTERVAL) #the bluetooth timer tries to reconnect if the bluetooth connection is lost def stopTimer(self): """stop all timers""" try: self._keyCheckTimer.Stop() self._bluetoothTimer.Stop() except: pass self._keyCheckTimer = None self._bluetoothTimer = None def initmapping(self): if (self._proto == 'A'): self._keynamesrepeat = { 20: 'up2', 21: 'up', 22: 'dn', 23: 'dn2', 24: 'right', 25: 'left', 26: 'right2', 27: 'left2' } x = self.numCells * 2 + 4 self._keynames = { x + 28: 'r1', x + 29: 'r2', 20: 'up2', 21: 'up', 22: 'dn', 23: 'dn2', 24: 'right', 25: 'left', 26: 'right2', 27: 'left2', 28: 'l1', 29: 'l2' } else: self._keynamesrepeat = { 16: 'left2', 17: 'right2', 18: 'left', 19: 'right', 20: 'dn2', 21: 'dn', 22: 'up', 23: 'up2' } x = self.numCells * 2 self._keynames = { 16: 'left2', 17: 'right2', 18: 'left', 19: 'right', 20: 'dn2', 21: 'dn', 22: 'up', 23: 'up2', x + 38: 'r2', x + 39: 'r1', 30: 'l2', 31: 'l1' } self._dotNames = { 32: 'd6', 1: 'd1', 2: 'd2', 4: 'd3', 8: 'd4', 64: 'd7', 128: 'd8', 16: 'd5' } self._thumbs = {1: "rt", 2: "space", 4: "lt"} def terminate(self): """free resources used by this driver""" try: super(BrailleDisplayDriver, self).terminate() self.stopTimer() if (self._dev is not None): self._dev.close() self._dev = None except: self._dev = None def display(self, cells: List[int]): """write to braille display""" if (self._dev is None): return try: self._dev.write(brl_out(cells, self._nlk, self._nrk, self._voffset)) except: self._dev.close() self._dev = None def executeGesture(self, gesture): """executes a gesture""" if gesture.id or (gesture.dots or gesture.space): inputCore.manager.executeGesture(gesture) def _handleKeyPresses(self): """handles key presses and performs a gesture""" try: if (self._dev is None and self._baud > 0): try: devlist: List[bytes] = ftdi2.list_devices() if (len(devlist) > 0): self.connectUSB(devlist) except: return s: bytes = brl_poll(self._dev) if s: self._repeatcount = 0 ig = InputGesture(s, self) self.executeGesture(ig) else: if (len(self.decodedkeys)): ig = InputGesture(None, self) self.executeGesture(ig) except: if (self._dev != None): self._dev.close() self._dev = None #global gestures gestureMap = inputCore.GlobalGestureMap({ "globalCommands.GlobalCommands": { "braille_scrollBack": ("br(papenmeier):left", ), "braille_scrollForward": ("br(papenmeier):right", ), "braille_previousLine": ("br(papenmeier):up", ), "braille_nextLine": ("br(papenmeier):dn", ), "braille_routeTo": ("br(papenmeier):route", ), "braille_reportFormatting": ("br(papenmeier):upperRouting", ), "braille_toggleTether": ("br(papenmeier):r2", ), "review_currentCharacter": ("br(papenmeier):l1", ), "review_activate": ("br(papenmeier):l2", ), "navigatorObject_previous": ("br(papenmeier):left2", ), "navigatorObject_next": ("br(papenmeier):right2", ), "navigatorObject_parent": ("br(papenmeier):up2", ), "navigatorObject_firstChild": ("br(papenmeier):dn2", ), "title": ("br(papenmeier):l1,up", ), "reportStatusLine": ("br(papenmeier):l2,dn", ), "kb:alt": ("br(papenmeier):lt+d3", ), "kb:control": ("br(papenmeier):lt+d2", ), "kb:escape": ("br(papenmeier):space+d7", ), "kb:tab": ("br(papenmeier):space+d3+d7", ), "kb:upArrow": ("br(papenmeier):space+d2", ), "kb:downArrow": ("br(papenmeier):space+d5", ), "kb:leftArrow": ("br(papenmeier):space+d1", ), "kb:rightArrow": ("br(papenmeier):space+d4", ), "kb:control+escape": ("br(papenmeier):space+d1+d2+d3+d4+d5+d6", ), "kb:control+alt+delete": ("br(papenmeier):space+d1+d2+d3+d4+d5+d6+d7+d8", ), "kb:enter": ( "br(papenmeier):space+d8", "br(papenmeier):d8", ), "kb:pageup": ("br(papenmeier):space+d3", ), "kb:pagedown": ("br(papenmeier):space+d6", ), "kb:backspace": ( "br(papenmeier):space+d6+d8", "br(papenmeier):d7", ), "kb:home": ("br(papenmeier):space+d1+d2", ), "kb:end": ("br(papenmeier):space+d4+d5", ), "kb:delete": ("br(papenmeier):space+d5+d6", ), "kb:f1": ("br(papenmeier):rt+d1", ), "kb:f2": ("br(papenmeier):rt+d1+d2", ), "kb:f3": ("br(papenmeier):rt+d1+d4", ), "kb:f4": ("br(papenmeier):rt+d1+d4+d5", ), "kb:f5": ("br(papenmeier):rt+d1+d5", ), "kb:f6": ("br(papenmeier):rt+d1+d2+d4", ), "kb:f7": ("br(papenmeier):rt+d1+d2+d4+d5", ), "kb:f8": ("br(papenmeier):rt+d1+d2+d5", ), "kb:f9": ("br(papenmeier):rt+d2+d4", ), "kb:f10": ("br(papenmeier):rt+d2+d4+d5", ), "kb:f11": ("br(papenmeier):rt+d1+d3", ), "kb:f12": ("br(papenmeier):rt+d1+d2+d3", ), "kb:control+a": ("br(papenmeier):d1+d7+d8", ), "kb:control+p": ("br(papenmeier):d1+d2+d3+d4+d7+d8", ), "kb:control+s": ("br(papenmeier):d2+d3+d4+d7+d8", ), "kb:control+b": ("br(papenmeier):d1+d2+d7+d8", ), "kb:control+c": ("br(papenmeier):d1+d4+d7+d8", ), "kb:control+d": ("br(papenmeier):d1+d4+d5+d7+d8", ), "kb:control+e": ("br(papenmeier):d1+d5+d7+d8", ), "kb:control+f": ("br(papenmeier):d1+d2+d4+d7+d8", ), "kb:control+g": ("br(papenmeier):d1+d2+d4+d5+d7+d8", ), "kb:control+h": ("br(papenmeier):d1+d2+d5+d7+d8", ), "kb:control+i": ("br(papenmeier):d2+d4+d7+d8", ), "kb:control+j": ("br(papenmeier):d2+d4+d5+d7+d8", ), "kb:control+k": ("br(papenmeier):d1+d3+d7+d8", ), "kb:control+l": ("br(papenmeier):d1+d2+d3+d7+d8", ), "kb:control+m": ("br(papenmeier):d1+d3+d4+d7+d8", ), "kb:control+n": ("br(papenmeier):d1+d3+d4+d5+d7+d8", ), "kb:control+o": ("br(papenmeier):d1+d3+d5+d7+d8", ), "kb:control+q": ("br(papenmeier):d1+d2+d3+d4+d5+d7+d8", ), "kb:control+r": ("br(papenmeier):d1+d2+d3+d5+d7+d8", ), "kb:control+t": ("br(papenmeier):d2+d3+d4+d5+d7+d8", ), "kb:control+u": ("br(papenmeier):d1+d3+d6+d7+d8", ), "kb:control+v": ("br(papenmeier):d1+d2+d3+d6+d7+d8", ), "kb:control+w": ("br(papenmeier):d2+d4+d5+d6+d7+d8", ), "kb:control+x": ("br(papenmeier):d1+d3+d4+d6+d7+d8", ), "kb:control+y": ("br(papenmeier):d1+d3+d4+d5+d6+d7+d8", ), "kb:control+z": ("br(papenmeier):d1+d3+d5+d6+d7+d8", ), } })
class BrailleDisplayDriver(braille.BrailleDisplayDriver): _dev: hwIo.IoBase name = SEIKA_NAME # Translators: Name of a braille display. description = _("Seika Notetaker") isThreadSafe = True @classmethod def getManualPorts(cls) -> typing.Iterator[typing.Tuple[str, str]]: """@return: An iterator containing the name and description for each port. """ return braille.getSerialPorts(isSeikaBluetoothDeviceInfo) def __init__(self, port: typing.Union[None, str, DeviceMatch]): super().__init__() self.numCells = 0 self.numBtns = 0 self.numRoutingKeys = 0 self.handle = None self._hidBuffer = b"" self._command: typing.Optional[bytes] = None self._argsLen: typing.Optional[int] = None log.debug(f"Seika Notetaker braille driver: ({port!r})") dev: typing.Optional[typing.Union[hwIo.Hid, hwIo.Serial]] = None for match in self._getTryPorts(port): self.isHid = match.type == bdDetect.KEY_HID self.isSerial = match.type == bdDetect.KEY_SERIAL try: if self.isHid: log.info(f"Trying Seika notetaker on USB-HID") self._dev = dev = hwIo.Hid( path=match. port, # for a Hid match type 'port' is actually 'path'. onReceive=self._onReceiveHID) dev.setFeature( SEIKA_HID_FEATURES) # baud rate, stop bit usw dev.setFeature(SEIKA_CMD_ON) # device on elif self.isSerial: log.info( f"Trying Seika notetaker on Bluetooth (serial) port:{match.port}" ) self._dev = dev = hwIo.Serial( port=match.port, onReceive=self._onReceiveSerial, baudrate=BAUD, parity=serial.PARITY_NONE, bytesize=serial.EIGHTBITS, stopbits=serial.STOPBITS_ONE, ) # Note: SEIKA_CMD_ON not sent as per USB-HID, testing from users hasn't indicated any problems. # The exact purpose of SEIKA_CMD_ON isn't known/documented here. else: log.debug(f"Port type not handled: {match.type}") continue except EnvironmentError: log.debugWarning("", exc_info=True) continue if self._getDeviceInfo(dev): break elif dev: dev.close() dev = None if not dev: RuntimeError("No MINI-SEIKA display found") elif self.numCells == 0: dev.close() dev = None raise RuntimeError("No MINI-SEIKA display found, no response") else: log.info(f"Seika notetaker," f" Cells {self.numCells}" f" Buttons {self.numBtns}") def _getDeviceInfo(self, dev: hwIo.IoBase) -> bool: if not dev or dev._file == INVALID_HANDLE_VALUE: log.debug("No MINI-SEIKA display found, open error") return False dev.write(SEIKA_REQUEST_INFO) # Request the Info from the device # wait and try to get info from the Braille display for i in range(MAX_READ_ATTEMPTS): # the info-block is about dev.waitForRead(READ_TIMEOUT_SECS) if self.numCells: return True return False def terminate(self): try: super().terminate() finally: self._dev.close() def display(self, cells: List[int]): # cells will already be padded up to numCells. cellBytes = SEIKA_SEND_TEXT + bytes([self.numCells]) + bytes(cells) self._dev.write(cellBytes) def _onReceiveHID(self, data: bytes): """Three bytes at a time expected, only the middle byte is used to construct the command, the first and third byte are discarded. """ stream = BytesIO(data) cmd = stream.read(3) # Note, first and third bytes are discarded newByte: bytes = cmd[ 1:2] # use range to return bytes type, containing only index 1 self._onReceive(newByte) def _onReceiveSerial(self, data: bytes): """One byte at a time is expected""" self._onReceive(data) def _onReceive(self, newByte: bytes): """ Note: Further insight into this function would be greatly appreciated. This function is a very simple state machine, each stage represents the collection of a field, when all fields are collected the command they represent can be processed. On each call to _onReceive the new byte is appended to a buffer. The buffer is accumulated until the buffer has the required number of bytes for the field being collected. There are 3 fields to be collected before a command can be processed: 1: first 3 bytes: command 2: 1 byte: specify length of subsequent arguments in bytes 3: variable length: arguments for command type After accumulating enough bytes for each phase, the buffer is cleared and the next stage is entered. """ COMMAND_LEN = 3 self._hidBuffer += newByte hasCommandBeenCollected = self._command is not None hasArgLenBeenCollected = self._argsLen is not None if ( # still collecting command bytes not hasCommandBeenCollected and len(self._hidBuffer) == COMMAND_LEN): self._command = self._hidBuffer # command found reset and wait for args length self._hidBuffer = b"" elif ( # next byte gives the command + args length hasCommandBeenCollected and not hasArgLenBeenCollected # argsLen has not ): # the data is sent with the following structure # - command name (3 bytes) # - number of subsequent bytes to read (1 byte) # - Args (variable bytes) self._argsLen = ord(newByte) self._hidBuffer = b"" elif ( # now collect the args, hasCommandBeenCollected and hasArgLenBeenCollected and len(self._hidBuffer) == self._argsLen): arg = self._hidBuffer command = self._command # reset state variables self._command = None self._argsLen = None self._hidBuffer = b"" self._processCommand(command, arg) def _processCommand(self, command: bytes, arg: bytes) -> None: if command == SEIKA_INFO: self._handleInfo(arg) elif command == SEIKA_ROUTING: self._handleRouting(arg) elif command == SEIKA_KEYS: self._handleKeys(arg) elif command == SEIKA_KEYS_ROU: self._handleKeysRouting(arg) else: log.warning( f"Seika device has received an unknown command {command}") def _handleInfo(self, arg: bytes): """After sending a request for information from the braille device this data is returned to complete the handshake. """ self.numBtns = arg[0] self.numCells = arg[1] self.numRoutingKeys = arg[2] try: self._description = arg[3:].decode("ascii") except UnicodeDecodeError: log.debugWarning( f"Unable to decode Seika Notetaker description {arg[3:]}") def _handleRouting(self, arg: bytes): routingIndexes = _getRoutingIndexes(arg) for routingIndex in routingIndexes: gesture = InputGestureRouting(routingIndex) try: inputCore.manager.executeGesture(gesture) except inputCore.NoInputGestureAction: log.debug("No action for Seika Notetaker routing command") def _handleKeys(self, arg: bytes): brailleDots = arg[0] key = arg[1] | (arg[2] << 8) gestures = [] if key: gestures.append(InputGesture(keys=key)) if brailleDots: gestures.append(InputGesture(dots=brailleDots)) for gesture in gestures: try: inputCore.manager.executeGesture(gesture) except inputCore.NoInputGestureAction: log.debug("No action for Seika Notetaker keys.") def _handleKeysRouting(self, arg: bytes): self._handleRouting(arg[3:]) self._handleKeys(arg[:3]) gestureMap = inputCore.GlobalGestureMap({ "globalCommands.GlobalCommands": { "braille_routeTo": ("br(seikantk):routing", ), "braille_scrollBack": ("br(seikantk):LB", ), "braille_scrollForward": ("br(seikantk):RB", ), "braille_previousLine": ("br(seikantk):LJ_UP", ), "braille_nextLine": ("br(seikantk):LJ_DOWN", ), "braille_toggleTether": ("br(seikantk):LJ_CENTER", ), "sayAll": ("br(seikantk):SPACE+BACKSPACE", ), "showGui": ("br(seikantk):RB+LB", ), "kb:tab": ("br(seikantk):LJ_RIGHT", ), "kb:shift+tab": ("br(seikantk):LJ_LEFT", ), "kb:upArrow": ("br(seikantk):RJ_UP", ), "kb:downArrow": ("br(seikantk):RJ_DOWN", ), "kb:leftArrow": ("br(seikantk):RJ_LEFT", ), "kb:rightArrow": ("br(seikantk):RJ_RIGHT", ), "kb:shift+upArrow": ("br(seikantk):SPACE+RJ_UP", "br(seikantk):BACKSPACE+RJ_UP"), "kb:shift+downArrow": ("br(seikantk):SPACE+RJ_DOWN", "br(seikantk):BACKSPACE+RJ_DOWN"), "kb:shift+leftArrow": ("br(seikantk):SPACE+RJ_LEFT", "br(seikantk):BACKSPACE+RJ_LEFT"), "kb:shift+rightArrow": ("br(seikantk):SPACE+RJ_RIGHT", "br(seikantk):BACKSPACE+RJ_RIGHT"), "kb:escape": ("br(seikantk):SPACE+RJ_CENTER", ), "kb:windows": ("br(seikantk):BACKSPACE+RJ_CENTER", ), "kb:space": ( "br(seikantk):BACKSPACE", "br(seikantk):SPACE", ), "kb:backspace": ("br(seikantk):d7", ), "kb:pageup": ("br(seikantk):SPACE+LJ_RIGHT", ), "kb:pagedown": ("br(seikantk):SPACE+LJ_LEFT", ), "kb:home": ("br(seikantk):SPACE+LJ_UP", ), "kb:end": ("br(seikantk):SPACE+LJ_DOWN", ), "kb:control+home": ("br(seikantk):BACKSPACE+LJ_UP", ), "kb:control+end": ("br(seikantk):BACKSPACE+LJ_DOWN", ), "kb:enter": ("br(seikantk):RJ_CENTER", "br(seikantk):d8"), }, })
class BrailleDisplayDriver(braille.BrailleDisplayDriver, ScriptableObject): _dev: Union[hwIo.Serial, hwIo.Hid] name = "alva" # Translators: The name of a braille display. description = _("Optelec ALVA 6 series/protocol converter") isThreadSafe = True timeout = 0.2 supportedSettings = (braille.BrailleDisplayDriver.HIDInputSetting( useConfig=False), ) @classmethod def getManualPorts(cls): return braille.getSerialPorts(filterFunc=lambda info: info.get( "bluetoothName", "").startswith("ALVA ")) def _get_model(self): if not self._deviceId: return "" self.model = ALVA_MODEL_IDS[self._deviceId] return self.model def _updateSettings(self): oldNumCells = self.numCells if self.isHid: displaySettings = self._dev.getFeature( ALVA_DISPLAY_SETTINGS_REPORT) if displaySettings[ALVA_DISPLAY_SETTINGS_STATUS_CELL_SIDE_POS] > 1: # #8106: The ALVA BC680 is known to return a malformed feature report for the first issued request. # Therefore, request another display settings report displaySettings = self._dev.getFeature( ALVA_DISPLAY_SETTINGS_REPORT) self.numCells = displaySettings[ ALVA_DISPLAY_SETTINGS_CELL_COUNT_POS] timeBytes: bytes = self._dev.getFeature( ALVA_RTC_REPORT)[1:ALVA_RTC_STR_LENGTH + 1] try: self._handleTime(timeBytes) except: log.debugWarning("Getting time from ALVA display failed", exc_info=True) keySettings = self._dev.getFeature( ALVA_KEY_SETTINGS_REPORT)[ALVA_KEY_SETTINGS_POS] self._rawKeyboardInput = bool(keySettings & ALVA_KEY_RAW_INPUT_MASK) else: # Get cell count self._ser6SendMessage(b"E", b"?") for i in range(3): self._dev.waitForRead(self.timeout) if self.numCells: # Display responded break else: # No response from display, do not send the other requests. return # Get device date and time self._ser6SendMessage(b"H", b"?") # Get HID keyboard input state self._ser6SendMessage(b"r", b"?") if oldNumCells not in (0, self.numCells): # In case of splitpoint changes, we need to update the braille handler as well braille.handler.displaySize = self.numCells def __init__(self, port="auto"): super(BrailleDisplayDriver, self).__init__() self.numCells = 0 self._rawKeyboardInput = False self._deviceId = None for portType, portId, port, portInfo in self._getTryPorts(port): self.isHid = portType == bdDetect.KEY_HID # Try talking to the display. try: if self.isHid: self._dev = hwIo.Hid(port, onReceive=self._hidOnReceive) self._deviceId = int(portId[-2:], 16) else: self._dev = hwIo.Serial(port, timeout=self.timeout, writeTimeout=self.timeout, onReceive=self._ser6OnReceive) # Get the device ID self._ser6SendMessage(b"?", b"?") for i in range(3): self._dev.waitForRead(self.timeout) if self._deviceId: # Display responded break else: # No response from display continue except EnvironmentError: log.debugWarning("", exc_info=True) continue self._updateSettings() if self.numCells: # A display responded. log.info( "Found display with {cells} cells connected via {type} ({port})" .format(cells=self.numCells, type=portType, port=port)) break self._dev.close() else: raise RuntimeError("No ALVA display found") self._keysDown = set() self._ignoreKeyReleases = False def terminate(self): try: super(BrailleDisplayDriver, self).terminate() finally: # Make sure the device gets closed. # If it doesn't, we may not be able to re-open it later. self._dev.close() if not self.isHid: # We must sleep after closing the COM port, as it takes some time for the device to disconnect. time.sleep(self.timeout) def _ser6SendMessage(self, cmd: bytes, value: bytes = b"") -> None: if not isinstance(value, bytes): raise TypeError("Expected param 'value' to have type 'bytes'") self._dev.write(b"".join([ESCAPE, cmd, value])) def _ser6OnReceive(self, data: bytes): """ Callback for L{self._dev} when it is L{hwIo.Serial} """ if data != ESCAPE: return cmd: bytes = self._dev.read(1) commandLen = ALVA_SER_CMD_LENGTHS[cmd] value: bytes = self._dev.read(commandLen) if cmd == b"K": # Input self._handleInput(group=value[0], number=value[1]) elif cmd == b"E": # Braille cell count self.numCells = ord(value) elif cmd == b"?": # Device ID self._deviceId = ord(value) # this command only gets one byte elif cmd == b"r": # Raw keyboard messages enable/disable self._rawKeyboardInput = bool(ord(value)) elif cmd == b"H": # Time # Handling time for serial displays does not block initialization if it fails. self._handleTime(value) def _hidOnReceive(self, data: bytes): """Callback for L{self._dev} when it is L{hwIo.Hid} """ reportID: bytes = data[0:1] if reportID == ALVA_KEY_REPORT: self._handleInput(data[ALVA_KEY_REPORT_KEY_GROUP_POS], data[ALVA_KEY_REPORT_KEY_POS]) def _handleInput(self, group: int, number: int) -> None: if group == ALVA_SPECIAL_KEYS_GROUP: # ALVA displays communicate setting changes as input messages. if number == ALVA_SPECIAL_SETTINGS_CHANGED: # Some internal settings have changed. # For example, split point could have been set, in which case the number of cells changed. # We must handle these properly. self._updateSettings() return isRelease = bool(group & ALVA_RELEASE_MASK) group = group & ~ALVA_RELEASE_MASK if isRelease: if not self._ignoreKeyReleases and self._keysDown: try: inputCore.manager.executeGesture( InputGesture(self.model, self._keysDown, brailleInput=self._rawKeyboardInput)) except inputCore.NoInputGestureAction: pass # Any further releases are just the rest of the keys in the combination being released, # so they should be ignored. self._ignoreKeyReleases = True self._keysDown.discard((group, number)) else: # Press self._keysDown.add((group, number)) # This begins a new key combination. self._ignoreKeyReleases = False def _hidDisplay(self, cellBytes: bytes) -> None: for offset in range(0, len(cellBytes), ALVA_BRAILLE_OUTPUT_MAX_SIZE): cellsToWrite = cellBytes[offset:offset + ALVA_BRAILLE_OUTPUT_MAX_SIZE] data = b"".join([ ALVA_BRAILLE_OUTPUT_REPORT, intToByte(offset), intToByte(len(cellsToWrite)), cellsToWrite ]) self._dev.write(data) def _ser6Display(self, cellBytes: bytes) -> None: if not isinstance(cellBytes, bytes): raise TypeError("Expected param 'cells' to be of type 'bytes'") value = b"".join([b"\x00", intToByte(len(cellBytes)), cellBytes]) self._ser6SendMessage(b"B", value) def display(self, cells: List[int]): # cells will already be padded up to numCells. cellBytes = bytes(cells) if self.isHid: self._hidDisplay(cellBytes) else: self._ser6Display(cellBytes) def _handleTime(self, time: bytes): """ @type time: bytes """ year = time[0] | time[1] << 8 if not ALVA_RTC_MIN_YEAR <= year <= ALVA_RTC_MAX_YEAR: log.debug("This ALVA display doesn't reveal clock information") return try: displayDateTime = datetime.datetime(year=year, month=time[2], day=time[3], hour=time[4], minute=time[5], second=time[6]) except ValueError: log.debugWarning("Invalid time/date of ALVA display: %r" % time) return localDateTime = datetime.datetime.today() if abs((displayDateTime - localDateTime).total_seconds()) >= ALVA_RTC_MAX_DRIFT: log.debugWarning("Display time out of sync: %s" % displayDateTime.isoformat()) self._syncTime(localDateTime) else: log.debug("Time not synchronized. Display time %s" % displayDateTime.isoformat()) def _syncTime(self, dt: datetime.datetime): log.debug("Synchronizing braille display date and time...") timeList: List[int] = [ dt.year & 0xFF, dt.year >> 8, dt.month, dt.day, dt.hour, dt.minute, dt.second ] if self.isHid: self._dev.setFeature(ALVA_RTC_REPORT + bytes(timeList)) else: self._ser6SendMessage(b"H", bytes(timeList)) def _get_hidKeyboardInput(self): return not self._rawKeyboardInput def _set_hidKeyboardInput(self, state): rawState = not state if self.isHid: # Make sure the device settings are up to date. keySettings: int = self._dev.getFeature( ALVA_KEY_SETTINGS_REPORT)[ALVA_KEY_SETTINGS_POS] # Try to update the state if rawState: newKeySettings = intToByte(keySettings | ALVA_KEY_RAW_INPUT_MASK) elif keySettings & ALVA_KEY_RAW_INPUT_MASK: newKeySettings = intToByte(keySettings ^ ALVA_KEY_RAW_INPUT_MASK) else: newKeySettings = intToByte(keySettings) self._dev.setFeature(ALVA_KEY_SETTINGS_REPORT + newKeySettings) # Check whether the state has been changed successfully. # If not, this device does not support this feature. keySettings: int = self._dev.getFeature( ALVA_KEY_SETTINGS_REPORT)[ALVA_KEY_SETTINGS_POS] # Save the new state self._rawKeyboardInput = bool(keySettings & ALVA_KEY_RAW_INPUT_MASK) else: self._ser6SendMessage(cmd=b"r", value=boolToByte(rawState)) self._ser6SendMessage(b"r", b"?") for i in range(3): self._dev.waitForRead(self.timeout) if rawState is self._rawKeyboardInput: break scriptCategory = SCRCAT_BRAILLE def script_toggleHidKeyboardInput(self, gesture): oldHidKeyboardInput = self.hidKeyboardInput self.hidKeyboardInput = not self.hidKeyboardInput if self.hidKeyboardInput is oldHidKeyboardInput: # Translators: Message when setting HID keyboard simulation failed. ui.message(_("Setting HID keyboard simulation not supported")) elif self.hidKeyboardInput: # Translators: Message when HID keyboard simulation is enabled. ui.message(_("HID keyboard simulation enabled")) else: # Translators: Message when HID keyboard simulation is disabled. ui.message(_("HID keyboard simulation disabled")) # Translators: Description of the script that toggles HID keyboard simulation. script_toggleHidKeyboardInput.__doc__ = _( "Toggles HID keyboard simulation") __gestures = { "br(alva):t1+spEnter": "toggleHidKeyboardInput", } gestureMap = inputCore.GlobalGestureMap({ "globalCommands.GlobalCommands": { "braille_scrollBack": ("br(alva):t1", "br(alva):etouch1"), "braille_previousLine": ("br(alva):t2", ), "braille_toFocus": ("br(alva):t3", ), "braille_nextLine": ("br(alva):t4", ), "braille_scrollForward": ("br(alva):t5", "br(alva):etouch3"), "braille_routeTo": ("br(alva):routing", ), "braille_reportFormatting": ("br(alva):secondRouting", ), "review_top": ("br(alva):t1+t2", ), "review_bottom": ("br(alva):t4+t5", ), "braille_toggleTether": ("br(alva):t1+t3", ), "braille_cycleCursorShape": ("br(alva):t1+t4", ), "braille_toggleShowCursor": ("br(alva):t2+t5", ), "title": ("br(alva):etouch2", ), "reportStatusLine": ("br(alva):etouch4", ), "kb:shift+tab": ("br(alva):sp1", ), "kb:alt": ( "br(alva):sp2", "br(alva):alt", ), "kb:escape": ("br(alva):sp3", ), "kb:tab": ("br(alva):sp4", ), "kb:upArrow": ("br(alva):spUp", ), "kb:downArrow": ("br(alva):spDown", ), "kb:leftArrow": ("br(alva):spLeft", ), "kb:rightArrow": ("br(alva):spRight", ), "kb:enter": ( "br(alva):spEnter", "br(alva):enter", ), "dateTime": ("br(alva):sp2+sp3", ), "showGui": ("br(alva):sp1+sp3", ), "kb:windows+d": ("br(alva):sp1+sp4", ), "kb:windows+b": ("br(alva):sp3+sp4", ), "kb:windows": ( "br(alva):sp1+sp2", "br(alva):windows", ), "kb:alt+tab": ("br(alva):sp2+sp4", ), "kb:control+home": ("br(alva):t3+spUp", ), "kb:control+end": ("br(alva):t3+spDown", ), "kb:home": ("br(alva):t3+spLeft", ), "kb:end": ("br(alva):t3+spRight", ), "kb:control": ("br(alva):control", ), } })
class BrailleDisplayDriver(braille.BrailleDisplayDriver,ScriptableObject): name="freedomScientific" # Translators: Names of braille displays. description=_("Freedom Scientific Focus/PAC Mate series") @classmethod def check(cls): return bool(fsbLib) @classmethod def getPossiblePorts(cls): ports = OrderedDict([cls.AUTOMATIC_PORT, ("USB", "USB",)]) try: cls._getBluetoothPorts().next() ports["bluetooth"] = "Bluetooth" except StopIteration: pass return ports @classmethod def _getBluetoothPorts(cls): for p in hwPortUtils.listComPorts(): try: btName = p["bluetoothName"] except KeyError: continue if not any(btName == prefix or btName.startswith(prefix + " ") for prefix in bluetoothNames): continue yield p["port"].encode("mbcs") wizWheelActions=[ # Translators: The name of a key on a braille display, that scrolls the display to show previous/next part of a long line. (_("display scroll"),("globalCommands","GlobalCommands","braille_scrollBack"),("globalCommands","GlobalCommands","braille_scrollForward")), # Translators: The name of a key on a braille display, that scrolls the display to show the next/previous line. (_("line scroll"),("globalCommands","GlobalCommands","braille_previousLine"),("globalCommands","GlobalCommands","braille_nextLine")), ] def __init__(self, port="auto"): self.leftWizWheelActionCycle=itertools.cycle(self.wizWheelActions) action=self.leftWizWheelActionCycle.next() self.gestureMap.add("br(freedomScientific):leftWizWheelUp",*action[1]) self.gestureMap.add("br(freedomScientific):leftWizWheelDown",*action[2]) self.rightWizWheelActionCycle=itertools.cycle(self.wizWheelActions) action=self.rightWizWheelActionCycle.next() self.gestureMap.add("br(freedomScientific):rightWizWheelUp",*action[1]) self.gestureMap.add("br(freedomScientific):rightWizWheelDown",*action[2]) super(BrailleDisplayDriver,self).__init__() self._messageWindowClassAtom=windll.user32.RegisterClassExW(byref(nvdaFsBrlWndCls)) self._messageWindow=windll.user32.CreateWindowExW(0,self._messageWindowClassAtom,u"nvdaFsBrlWndCls window",0,0,0,0,0,None,None,appInstance,None) if port == "auto": portsToTry = itertools.chain(["USB"], self._getBluetoothPorts()) elif port == "bluetooth": portsToTry = self._getBluetoothPorts() else: # USB portsToTry = ["USB"] fbHandle=-1 for port in portsToTry: fbHandle=fbOpen(port,self._messageWindow,nvdaFsBrlWm) if fbHandle!=-1: break if fbHandle==-1: windll.user32.DestroyWindow(self._messageWindow) windll.user32.UnregisterClassW(self._messageWindowClassAtom,appInstance) raise RuntimeError("No display found") self.fbHandle=fbHandle self._configureDisplay() numCells=self.numCells self.gestureMap.add("br(freedomScientific):topRouting1","globalCommands","GlobalCommands","braille_scrollBack") self.gestureMap.add("br(freedomScientific):topRouting%d"%numCells,"globalCommands","GlobalCommands","braille_scrollForward") def terminate(self): super(BrailleDisplayDriver,self).terminate() fbClose(self.fbHandle) windll.user32.DestroyWindow(self._messageWindow) windll.user32.UnregisterClassW(self._messageWindowClassAtom,appInstance) def _get_numCells(self): return fbGetCellCount(self.fbHandle) def display(self,cells): cells="".join([chr(x) for x in cells]) fbWrite(self.fbHandle,0,len(cells),cells) def _configureDisplay(self): # See what display we are connected to displayName= firmwareVersion="" buf = create_string_buffer(16) if fbGetDisplayName(self.fbHandle, buf, 16): displayName=buf.value if fbGetFirmwareVersion(self.fbHandle, buf, 16): firmwareVersion=buf.value if displayName and firmwareVersion and displayName=="Focus" and ord(firmwareVersion[0])>=ord('3'): # Focus 2 or later. Make sure extended keys support is enabled. log.debug("Activating extended keys on freedom Scientific display. Display name: %s, firmware version: %s.", displayName, firmwareVersion) fbConfigure(self.fbHandle, 0x02) def script_toggleLeftWizWheelAction(self,gesture): action=self.leftWizWheelActionCycle.next() self.gestureMap.add("br(freedomScientific):leftWizWheelUp",*action[1],replace=True) self.gestureMap.add("br(freedomScientific):leftWizWheelDown",*action[2],replace=True) braille.handler.message(action[0]) def script_toggleRightWizWheelAction(self,gesture): action=self.rightWizWheelActionCycle.next() self.gestureMap.add("br(freedomScientific):rightWizWheelUp",*action[1],replace=True) self.gestureMap.add("br(freedomScientific):rightWizWheelDown",*action[2],replace=True) braille.handler.message(action[0]) __gestures={ "br(freedomScientific):leftWizWheelPress":"toggleLeftWizWheelAction", "br(freedomScientific):rightWizWheelPress":"toggleRightWizWheelAction", } gestureMap=inputCore.GlobalGestureMap({ "globalCommands.GlobalCommands" : { "braille_routeTo":("br(freedomScientific):routing",), "braille_scrollBack" : ("br(freedomScientific):leftAdvanceBar", "br(freedomScientific):leftBumperBarUp","br(freedomScientific):rightBumperBarUp",), "braille_scrollForward" : ("br(freedomScientific):rightAdvanceBar","br(freedomScientific):leftBumperBarDown","br(freedomScientific):rightBumperBarDown",), "braille_previousLine" : ("br(freedomScientific):leftRockerBarUp", "br(freedomScientific):rightRockerBarUp",), "braille_nextLine" : ("br(freedomScientific):leftRockerBarDown", "br(freedomScientific):rightRockerBarDown",), "kb:shift+tab": ("br(freedomScientific):dot1+dot2+brailleSpaceBar",), "kb:tab" : ("br(freedomScientific):dot4+dot5+brailleSpaceBar",), "kb:upArrow" : ("br(freedomScientific):dot1+brailleSpaceBar",), "kb:downArrow" : ("br(freedomScientific):dot4+brailleSpaceBar",), "kb:leftArrow" : ("br(freedomScientific):dot3+brailleSpaceBar",), "kb:rightArrow" : ("br(freedomScientific):dot6+brailleSpaceBar",), "kb:control+leftArrow" : ("br(freedomScientific):dot2+brailleSpaceBar",), "kb:control+rightArrow" : ("br(freedomScientific):dot5+brailleSpaceBar",), "kb:home" : ("br(freedomScientific):dot1+dot3+brailleSpaceBar",), "kb:control+home" : ("br(freedomScientific):dot1+dot2+dot3+brailleSpaceBar",), "kb:end" : ("br(freedomScientific):dot4+dot6+brailleSpaceBar",), "kb:control+end" : ("br(freedomScientific):dot4+dot5+dot6+brailleSpaceBar",), "kb:alt" : ("br(freedomScientific):dot1+dot3+dot4+brailleSpaceBar",), "kb:alt+tab" : ("br(freedomScientific):dot2+dot3+dot4+dot5+brailleSpaceBar",), "kb:escape" : ("br(freedomScientific):dot1+dot5+brailleSpaceBar",), "kb:windows" : ("br(freedomScientific):dot2+dot4+dot5+dot6+brailleSpaceBar",), "kb:windows+d" : ("br(freedomScientific):dot1+dot2+dot3+dot4+dot5+dot6+brailleSpaceBar",), "reportCurrentLine" : ("br(freedomScientific):dot1+dot4+brailleSpaceBar",), "showGui" :("br(freedomScientific):dot1+dot3+dot4+dot5+brailleSpaceBar",), "braille_toggleTether" : ("br(freedomScientific):leftGDFButton+rightGDFButton",), } })
class HidBrailleDriver(braille.BrailleDisplayDriver): _dev: hwIo.hid.Hid name = "hidBrailleStandard" # Translators: The name of a series of braille displays. description = _("Standard HID Braille Display") isThreadSafe = True @classmethod def check(cls): return (isSupportEnabled() and super().check()) def __init__(self, port="auto"): super().__init__() self.numCells = 0 for portType, portId, port, portInfo in self._getTryPorts(port): if portType != bdDetect.KEY_HID: continue # Try talking to the display. try: self._dev = hwIo.hid.Hid(port, onReceive=self._hidOnReceive) except EnvironmentError: log.debugWarning("", exc_info=True) continue # Couldn't connect. if self._dev.usagePage != HID_USAGE_PAGE_BRAILLE: log.debug("Not braille") continue cellValueCaps = self._findCellValueCaps() if cellValueCaps: self._cellValueCaps = cellValueCaps self.numCells = cellValueCaps.ReportCount # A display responded. log.info( "Found display with {cells} cells connected via {type} ({port})" .format(cells=self.numCells, type=portType, port=port)) break # This device can't be initialized. Move on to the next (if any). self._dev.close() else: raise RuntimeError("No display found") self._inputButtonCapsByDataIndex = self._collectInputButtonCapsByDataIndex( ) self._keysDown = set() self._ignoreKeyReleases = False def _findCellValueCaps(self) -> Optional[hidpi.HIDP_VALUE_CAPS]: for valueCaps in self._dev.outputValueCaps: if (valueCaps.LinkUsagePage == HID_USAGE_PAGE_BRAILLE and valueCaps.LinkUsage == BraillePageUsageID.BRAILLE_ROW and valueCaps.u1.NotRange.Usage in (BraillePageUsageID.EIGHT_DOT_BRAILLE_CELL, BraillePageUsageID.SIX_DOT_BRAILLE_CELL) and valueCaps.ReportCount > 0): return valueCaps return None def _collectInputButtonCapsByDataIndex(self): capsByDataIndex = {} relativeIndexInCollection = 0 lastLinkCollection = None # Walk through all the available input buttons # storing them in a dictionary, keyed by their data index. # Also store the index of each button, relative to the collection it is a part of, # As this relative index is used as the routing index for routing keys. # We must however walk through the input buttons in reverse order # as windows loads the input caps arrays in reverse, # See https://docs.microsoft.com/en-us/windows-hardware/drivers/hid/button-capability-arrays for buttonCaps in reversed(self._dev.inputButtonCaps): if buttonCaps.LinkCollection != lastLinkCollection: lastLinkCollection = buttonCaps.LinkCollection relativeIndexInCollection = 0 else: relativeIndexInCollection += 1 if buttonCaps.IsRange: r = buttonCaps.u1.Range for index in range(r.DataIndexMin, r.DataIndexMax + 1): capsByDataIndex[index] = ButtonCapsInfo( buttonCaps, relativeIndexInCollection=relativeIndexInCollection) else: nr = buttonCaps.u1.NotRange index = nr.DataIndex capsByDataIndex[index] = ButtonCapsInfo( buttonCaps, relativeIndexInCollection=relativeIndexInCollection) return capsByDataIndex def terminate(self): try: super().terminate() finally: # Make sure the device gets closed. # If it doesn't, we may not be able to re-open it later. self._dev.close() def _hidOnReceive(self, data: bytes): report = hwIo.hid.HidInputReport(self._dev, data) keys = [] for dataItem in report.getDataItems(): if dataItem.DataIndex in self._inputButtonCapsByDataIndex and dataItem.u1.On: keys.append(dataItem.DataIndex) if len(keys) > len(self._keysDown): # Press. This begins a new key combination. self._ignoreKeyReleases = False elif len(keys) < len(self._keysDown): self._handleKeyRelease() self._keysDown = keys def _handleKeyRelease(self): if self._ignoreKeyReleases or not self._keysDown: return try: inputCore.manager.executeGesture(InputGesture( self, self._keysDown)) except inputCore.NoInputGestureAction: pass # Any further releases are just the rest of the keys in the combination being released, # so they should be ignored. self._ignoreKeyReleases = True def display(self, cells: List[int]): # cells will already be padded up to numCells. cellBytes = b"".join(intToByte(cell) for cell in cells) report = hwIo.hid.HidOutputReport( self._dev, reportID=self._cellValueCaps.ReportID) report.setUsageValueArray(HID_USAGE_PAGE_BRAILLE, self._cellValueCaps.LinkCollection, self._cellValueCaps.u1.NotRange.Usage, cellBytes) self._dev.write(report.data) gestureMap = inputCore.GlobalGestureMap({ "globalCommands.GlobalCommands": { "braille_scrollBack": ( "br(hidBrailleStandard):panLeft", "br(hidBrailleStandard):rockerUp", ), "braille_scrollForward": ( "br(hidBrailleStandard):panRight", "br(hidBrailleStandard):rockerDown", ), "braille_previousLine": ("br(hidBrailleStandard):space+dot1", ), "braille_nextLine": ("br(hidBrailleStandard):space+dot4", ), "braille_routeTo": ("br(hidBrailleStandard):routerSet1_routerKey", ), "braille_toggleTether": ("br(hidBrailleStandard):up+down", ), "kb:upArrow": ("br(hidBrailleStandard):joystickUp", ), "kb:downArrow": ("br(hidBrailleStandard):joystickDown", ), "kb:leftArrow": ("br(hidBrailleStandard):space+dot3", "br(hidBrailleStandard):joystickLeft"), "kb:rightArrow": ("br(hidBrailleStandard):space+dot6", "br(hidBrailleStandard):joystickRight"), "showGui": ("br(hidBrailleStandard):space+dot1+dot3+dot4+dot5", ), "kb:shift+tab": ("br(hidBrailleStandard):space+dot1+dot3", ), "kb:tab": ("br(hidBrailleStandard):space+dot4+dot6", ), "kb:alt": ("br(hidBrailleStandard):space+dot1+dot3+dot4", ), "kb:escape": ("br(hidBrailleStandard):space+dot1+dot5", ), "kb:enter": ("br(hidBrailleStandard):joystickCenter"), "kb:windows+d": ("br(hidBrailleStandard):Space+dot1+dot4+dot5", ), "kb:windows": ("br(hidBrailleStandard):space+dot3+dot4", ), "kb:alt+tab": ("br(hidBrailleStandard):space+dot2+dot3+dot4+dot5", ), "sayAll": ("br(hidBrailleStandard):Space+dot1+dot2+dot3+dot4+dot5+dot6", ), }, })
class BrailleDisplayDriver(braille.BrailleDisplayDriver): name = "brailleNote" # Translators: Names of braille displays description = _("HumanWare BrailleNote") isThreadSafe = True @classmethod def getManualPorts(cls): return braille.getSerialPorts() def __init__(self, port="auto"): super(BrailleDisplayDriver, self).__init__() self._serial = None for portType, portId, port, portInfo in self._getTryPorts(port): log.debug("Checking port %s for a BrailleNote", port) try: self._serial = hwIo.Serial(port, baudrate=BAUD_RATE, timeout=TIMEOUT, writeTimeout=TIMEOUT, parity=serial.PARITY_NONE, onReceive=self._onReceive) except EnvironmentError: log.debugWarning("", exc_info=True) continue # Check for cell information if self._describe(): log.debug("BrailleNote found on %s with %d cells", port, self.numCells) break else: self._serial.close() else: raise RuntimeError("Can't find a braillenote device (port = %s)" % port) def terminate(self): try: super(BrailleDisplayDriver, self).terminate() finally: self._serial.close() self._serial = None def _describe(self): self.numCells = 0 log.debug("Writing describe tag") self._serial.write(DESCRIBE_TAG) self._serial.waitForRead(TIMEOUT) # If a valid response was received, _onReceive will have set numCells. if self.numCells: return True log.debug("Not a braillenote") return False def _onReceive(self, command): command = ord(command) if command == STATUS_TAG: arg = self._serial.read(2) self.numCells = ord(arg[1]) return arg = self._serial.read(1) if not arg: log.debugWarning("Timeout reading argument for command 0x%X" % command) return # #5993: Read the buffer once more if a BrailleNote QT says it's got characters in its pipeline. if command == QT_MOD_TAG: key = self._serial.read(2)[-1] arg2 = _qtKeys.get(ord(key), key) else: arg2 = None self._dispatch(command, ord(arg), arg2 if arg2 is not None else None) def _dispatch(self, command, arg, arg2=None): space = False if command == THUMB_KEYS_TAG: gesture = InputGesture(keys=arg) elif command == SCROLL_WHEEL_TAG: gesture = InputGesture(wheel=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: # Stupid bug in the implementation # Force dot8 here, although it should be already there arg |= DOT_8 gesture = InputGesture(dots=arg, space=space) elif command == QT_MOD_TAG: # BrailleNote QT gesture = InputGesture(qtMod=arg, qtData=arg2) else: log.debugWarning("Unknown command") return try: inputCore.manager.executeGesture(gesture) except inputCore.NoInputGestureAction: pass def display(self, cells): # ESCAPE must be quoted because it is a control character cells = [chr(cell).replace(ESCAPE, ESCAPE * 2) for cell in cells] self._serial.write(DISPLAY_TAG + "".join(cells)) 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", "br(braillenote):wUp", "br(braillenote):upArrow", ), "kb:downArrow": ( "br(braillenote):space+d4", "br(braillenote):wDown", "br(braillenote):downArrow", ), "kb:leftArrow": ( "br(braillenote):space+d3", "br(braillenote):wLeft", "br(braillenote):leftArrow", ), "kb:rightArrow": ( "br(braillenote):space+d6", "br(braillenote):wRight", "br(braillenote):rightArrow", ), "kb:pageup": ( "br(braillenote):space+d1+d3", "br(braillenote):function+upArrow", ), "kb:pagedown": ( "br(braillenote):space+d4+d6", "br(braillenote):function+downArrow", ), "kb:home": ( "br(braillenote):space+d1+d2", "br(braillenote):function+leftArrow", ), "kb:end": ( "br(braillenote):space+d4+d5", "br(braillenote):function+rightArrow", ), "kb:control+home": ( "br(braillenote):space+d1+d2+d3", "br(braillenote):read+T", ), "kb:control+end": ( "br(braillenote):space+d4+d5+d6", "br(braillenote):read+B", ), "braille_enter": ( "br(braillenote):space+d8", "br(braillenote):wCenter", "br(braillenote):enter", ), "kb:shift+tab": ( "br(braillenote):space+d1+d2+d5+d6", "br(braillenote):wCounterclockwise", "br(braillenote):shift+tab", ), "kb:tab": ( "br(braillenote):space+d2+d3+d4+d5", "br(braillenote):wClockwise", "br(braillenote):tab", ), "braille_eraseLastCell": ( "br(braillenote):space+d7", "br(braillenote):backspace", ), "showGui": ( "br(braillenote):space+d1+d3+d4+d5", "br(braillenote):read+N", ), "kb:windows": ( "br(braillenote):space+d2+d4+d5+d6", "br(braillenote):read+W", ), "kb:alt": ( "br(braillenote):space+d1+d3+d4", "br(braillenote):read+M", ), "toggleInputHelp": ( "br(braillenote):space+d2+d3+d6", "br(braillenote):read+1", ), }, })
class BrailleDisplayDriver(braille.BrailleDisplayDriver, ScriptableObject): """papenmeier_serial braille display driver. """ name = "papenmeier_serial" # Translators: Names of braille displays. description = _("Papenmeier BRAILLEX older models") @classmethod def check(cls): """should return false if there is a missing dependency""" return True @classmethod def getPossiblePorts(cls): ports = OrderedDict() for p in hwPortUtils.listComPorts(): # Translators: Name of a serial communications port ports[p["port"]] = _("Serial: {portName}").format( portName=p["friendlyName"]) return ports def initTable(self): """do not use braille builtin table""" table = [1] * self.numCells self._dev.write(brl_out(512 + self._offsetHorizontal, table)) def __init__(self, port): """Initializes braille display driver""" super(BrailleDisplayDriver, self).__init__() self.numCells = 0 self._lastkey = '' self._dev = None self._repeatcount = 0 self._port = (port) log.info("papenmeier_serial using port " + self._port) #try to connect to braille display for baud in (19200, 38400): if (self._dev is None): self._dev = serial.Serial(self._port, baudrate=baud, timeout=TIMEOUT, writeTimeout=TIMEOUT) self._dev.write(brl_auto_id()) if (baud == 19200): time.sleep(0.2) else: time.sleep(0.03) displaytype = brl_poll(self._dev) dic = -1 if len(displaytype) == 10 and displaytype[ 0] == STX and displaytype[1] == ord(b'I'): dic = displaytype[2] self._eab = (baud == 38400) if (dic == -1): self._dev.close() self._dev = None #set parameters for display if (dic == 2): self.numCells = 40 self._offsetHorizontal = 0 self._keymap = [ 'l1', 'l2', 'left', 'up', 'r2', 'dn', 'right', 'r1', 'reportf' ] elif (dic == 1): self._offsetHorizontal = 0 self.numCells = 40 elif (dic == 3): self.numCells = 80 self._offsetHorizontal = 22 self._keymap = [ 'l1', 'l2', 'r2', 'reportf', 'up', 'left', 'r1', 'right', 'dn', 'left2', 'up2', 'dn2', 'right2' ] elif (dic == 6): self.numCells = 80 self._offsetHorizontal = 0 elif (dic == 66): #//BRAILLEX EL 80 self._offsetHorizontal = 2 self.numCells = 80 elif (dic == 67): self._offsetHorizontal = 20 self.numCells = 80 elif (dic == 64): self._offsetHorizontal = 13 self.numCells = 40 elif (dic == 65): self._offsetHorizontal = 13 self.numCells = 66 elif (dic == 68): self._offsetHorizontal = 0 self.numCells = 40 elif (dic == 69): self._offsetHorizontal = 0 self.numCells = 32 elif (dic == 70): self._offsetHorizontal = 0 self.numCells = 20 else: raise Exception("No or unknown braille display found") #initialize display self.initTable() #start keyCheckTimer self._decodedkeys = [] self._keyCheckTimer = wx.PyTimer(self._handleKeyPresses) self._keyCheckTimer.Start(KEY_CHECK_INTERVAL) def terminate(self): """free resources""" super(BrailleDisplayDriver, self).terminate() try: if (self._dev != None): self._dev.close() self._dev = None self._keyCheckTimer.Stop() self._keyCheckTimer = None except: pass def display(self, cells: List[int]): """write data to braille display""" if (self._dev != None): try: self._dev.write(brl_out(self._offsetHorizontal, cells)) except: self._dev = None def executeGesture(self, gesture): """execute a gesture""" #here you can add other gesture types try: if gesture.id: inputCore.manager.executeGesture(gesture) except inputCore.NoInputGestureAction: pass def _handleKeyPresses(self): #called by the keycheck timer """if a button was pressed an input gesture is executed""" if (self._dev != None): data = brl_poll(self._dev) if len(data) == 10 and data[1] == ord(b'K'): pos = data[2] * 256 + data[3] pos = (pos - 768) / 3 pressed = data[6] keys = data[8] self._repeatcount = 0 self.executeGesture(InputGesture(pos, pressed, keys, self)) elif (len(data) == 0): if (self._repeatcount == 50): if (len(self._lastkey)): self.executeGesture( InputGesture(None, None, None, self)) self._repeatcount = 0 else: self._repeatcount += 1 #global gestures gestureMap = inputCore.GlobalGestureMap({ "globalCommands.GlobalCommands": { "braille_scrollBack": ("br(papenmeier_serial):left", ), "braille_scrollForward": ("br(papenmeier_serial):right", ), "braille_previousLine": ("br(papenmeier_serial):up", ), "braille_nextLine": ("br(papenmeier_serial):dn", ), "braille_routeTo": ("br(papenmeier_serial):route", ), "braille_reportFormatting": ("br(papenmeier_serial):upperRouting", ), "braille_toggleTether": ("br(papenmeier_serial):r2", ), "review_currentCharacter": ("br(papenmeier_serial):l1", ), "review_activate": ("br(papenmeier_serial):l2", ), "reportFormatting": ("br(papenmeier_serial):reportf", ), "navigatorObject_previous": ("br(papenmeier_serial):left2", "br(papenmeier_serial):r1,left"), "navigatorObject_next": ("br(papenmeier_serial):right2", "br(papenmeier_serial):r1,right"), "navigatorObject_parent": ("br(papenmeier_serial):up2", "br(papenmeier_serial):r1,up"), "navigatorObject_firstChild": ("br(papenmeier_serial):dn2", "br(papenmeier_serial):r1,dn"), "title": ("br(papenmeier_serial):l1,up", ), "reportStatusLine": ("br(papenmeier_serial):l2,dn", ), } })
class BrailleDisplayDriver(braille.BrailleDisplayDriver, ScriptableObject): """papenmeier braille display driver. """ name = "papenmeier" # Translators: Names of braille displays. description = _("Papenmeier BRAILLEX newer models") @classmethod def check(cls): """should return false if there is a missing dependency""" return True def connectBrxCom( self): #connect to brxcom server (provided by papenmeier) try: brxcomkey = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, "SOFTWARE\\FHP\\BrxCom") value, vtype = _winreg.QueryValueEx(brxcomkey, "InstallPath") _winreg.CloseKey(brxcomkey) self._brxnvda = c.cdll.LoadLibrary(str(value + "\\brxnvda.dll")) if (self._brxnvda.brxnvda_init( str(value + "\\BrxCom.dll").decode("mbcs")) == 0): self._baud = 1 #prevent bluetooth from connecting self.numCells = self._brxnvda.brxnvda_numCells() self._voffset = self._brxnvda.brxnvda_numVertCells() log.info("Found Braille Display connected via BRXCom") self.startTimer() return None except: log.debugWarning("BRXCom is not installed") self._brxnvda = None def connectBluetooth(self): """try to connect to bluetooth device first, bluetooth is only supported on Braillex Trio""" if (self._baud == 0 and self._dev is None): for portInfo in sorted( hwPortUtils.listComPorts(onlyAvailable=True), key=lambda item: "bluetoothName" in item): port = portInfo["port"] hwID = portInfo["hardwareID"] if "bluetoothName" in portInfo: if (portInfo["bluetoothName"] == "braillex trio "): try: self._dev = serial.Serial( port, baudrate=57600, timeout=BLUETOOTH_TIMEOUT, writeTimeout=BLUETOOTH_TIMEOUT) self.numCells = 40 self._proto = 'B' self._voffset = 0 log.info("connectBluetooth success") except: log.debugWarning("connectBluetooth failed") def connectUSB(self, devlist): """try to connect to usb device,is triggered when BRXCOM is not installed and bluetooth connection could not be established""" try: self._dev = ftdi2.open_ex(devlist[0]) self._dev.set_baud_rate(self._baud) self._dev.inWaiting = self._dev.get_queue_status log.info("connectUSB success") except: log.debugWarning("connectUSB failed") def __init__(self): """initialize driver""" super(BrailleDisplayDriver, self).__init__() self.numCells = 0 self._nlk = 0 self._nrk = 0 self._r1next = itertools.cycle(('r1a', 'r1b')) self.decodedkeys = [] self._baud = 0 self._dev = None self._proto = None self.connectBrxCom() if (self._baud == 1): return #brxcom is running, skip bluetooth and USB #try to connect to usb device, #if no usb device is found there may be a bluetooth device try: devlist = ftdi2.list_devices() except: devlist = [] if (len(devlist) == 0): self.connectBluetooth() else: self._baud = 57600 self.connectUSB(devlist) if (self._dev is None): return None try: #request type of braille display self._dev.write(brl_auto_id()) time.sleep( 0.05 ) # wait 50 ms in order to get response for further actions autoid = brl_poll(self._dev) if (autoid == ''): #no response, assume a Trio is connected self._baud = 115200 self._dev.set_baud_rate(self._baud) self._dev.purge() self._dev.read(self._dev.inWaiting()) self.numCells = 40 self._proto = 'B' self._voffset = 0 #we don't use autoid here, because the trio might be switched off and other braille displays using the same baud rate do not exist else: if (len(autoid) != 8): return None autoid = struct.unpack('BBBBBBBB', autoid) if (autoid[3] == 0x35 and autoid[4] == 0x38): #EL80s self.numCells = 80 self._nlk = 1 self._nrk = 1 self._proto = 'A' self._voffset = 0 log.info("Found EL80s") elif (autoid[3] == 0x35 and autoid[4] == 0x3A): #EL70s self.numCells = 70 self._nlk = 1 self._nrk = 1 self._proto = 'A' self._voffset = 0 log.info("Found EL70s") elif (autoid[3] == 0x35 and autoid[4] == 0x35): #EL40s self.numCells = 40 self._nlk = 1 self._nrk = 1 self._proto = 'A' self._voffset = 0 log.info("Found EL40s") elif (autoid[3] == 0x35 and autoid[4] == 0x37): #EL66s self.numCells = 66 self._nlk = 1 self._nrk = 1 self._proto = 'A' self._voffset = 0 log.info("Found EL66s") elif (autoid[3] == 0x35 and autoid[4] == 0x3E): #EL20c self.numCells = 20 self._nlk = 1 self._nrk = 1 self._proto = 'A' self._voffset = 0 log.info("Found EL20c") elif (autoid[3] == 0x35 and autoid[4] == 0x3F): #EL40c self.numCells = 40 self._nlk = 1 self._nrk = 1 self._proto = 'A' self._voffset = 0 log.info("Found EL40c") elif (autoid[3] == 0x36 and autoid[4] == 0x30): #EL60c self.numCells = 60 self._nlk = 1 self._nrk = 1 self._proto = 'A' self._voffset = 0 log.info("Found EL60c") elif (autoid[3] == 0x36 and autoid[4] == 0x31): #EL80c self.numCells = 80 self._nlk = 1 self._nrk = 1 self._proto = 'A' self._voffset = 0 log.info("Found EL80c") elif (autoid[3] == 0x35 and autoid[4] == 0x3b): #EL2D80s self.numCells = 80 self._nlk = 1 self._nrk = 1 self._proto = 'A' self._voffset = 20 log.info("Found EL2D80s") else: log.debugWarning('UNKNOWN BRAILLE') except: log.debugWarning('BROKEN PIPE - THIS SHOULD NEVER HAPPEN') if (self.numCells == 0): raise Exception('no device found') #start keycheck timer self.startTimer() self.initmapping() def startTimer(self): """start timers used by this driver""" self._keyCheckTimer = wx.PyTimer(self._handleKeyPresses) self._keyCheckTimer.Start(KEY_CHECK_INTERVAL) #the keycheck timer polls the braille display for keypresses self._bluetoothTimer = wx.PyTimer(self.connectBluetooth) self._bluetoothTimer.Start(BLUETOOTH_INTERVAL) #the bluetooth timer tries to reconnect if the bluetooth connection is lost def stopTimer(self): """stop all timers""" try: self._keyCheckTimer.Stop() self._bluetoothTimer.Stop() except: pass self._keyCheckTimer = None self._bluetoothTimer = None def initmapping(self): if (self._proto == 'A'): self._keynamesrepeat = { 20: 'up2', 21: 'up', 22: 'dn', 23: 'dn2', 24: 'right', 25: 'left', 26: 'right2', 27: 'left2' } x = self.numCells * 2 + 4 self._keynames = { x + 28: 'r1', x + 29: 'r2', 20: 'up2', 21: 'up', 22: 'dn', 23: 'dn2', 24: 'right', 25: 'left', 26: 'right2', 27: 'left2', 28: 'l1', 29: 'l2' } else: self._keynamesrepeat = { 16: 'left2', 17: 'right2', 18: 'left', 19: 'right', 20: 'dn2', 21: 'dn', 22: 'up', 23: 'up2' } x = self.numCells * 2 self._keynames = { 16: 'left2', 17: 'right2', 18: 'left', 19: 'right', 20: 'dn2', 21: 'dn', 22: 'up', 23: 'up2', x + 38: 'r2', x + 39: 'r1', 30: 'l2', 31: 'l1' } self._dotNames = { 32: 'd6', 1: 'd1', 2: 'd2', 4: 'd3', 8: 'd4', 64: 'd7', 128: 'd8', 16: 'd5' } self._thumbs = {1: "rt", 2: "space", 4: "lt"} def terminate(self): """free resources used by this driver""" try: super(BrailleDisplayDriver, self).terminate() self.stopTimer() if (self._dev != None): self._dev.close() self._dev = None if (self._brxnvda): self._brxnvda.brxnvda_close() except: self._dev = None def display(self, cells): """write to braille display""" if (self._brxnvda): newcells = "".join([chr(cell) for cell in cells]) self._brxnvda.brxnvda_sendToDisplay(newcells) return if (self._dev is None): return try: self._dev.write(brl_out(cells, self._nlk, self._nrk, self._voffset)) except: self._dev.close() self._dev = None def executeGesture(self, gesture): """executes a gesture""" if (gesture.id == 'r1'): gesture.id = next(self._r1next) if gesture.id or (gesture.dots or gesture.space): inputCore.manager.executeGesture(gesture) def _handleKeyPresses(self): """handles key presses and performs a gesture""" try: if (self._brxnvda): k = self._brxnvda.brxnvda_keyIndex() if (k != -1): self.executeGesture(InputGesture(k, self)) return if (self._dev is None and self._baud > 0): try: devlist = ftdi2.list_devices() if (len(devlist) > 0): self.connectUSB(devlist) except: return s = brl_poll(self._dev) if s: self._repeatcount = 0 ig = InputGesture(s, self) self.executeGesture(ig) else: if (len(self.decodedkeys)): ig = InputGesture(None, self) self.executeGesture(ig) except: if (self._dev != None): self._dev.close() self._dev = None def script_upperRouting(self, gesture): globalCommands.commands.script_braille_routeTo(gesture) wx.CallLater(50, scriptHandler.executeScript, globalCommands.commands.script_reportFormatting, gesture) script_upperRouting.__doc__ = _("Route to and report formatting") #global gestures gestureMap = inputCore.GlobalGestureMap({ "globalCommands.GlobalCommands": { "braille_scrollBack": ("br(papenmeier):left", ), "braille_scrollForward": ("br(papenmeier):right", ), "braille_previousLine": ("br(papenmeier):up", ), "braille_nextLine": ("br(papenmeier):dn", ), "braille_routeTo": ("br(papenmeier):route", ), "navigatorObject_moveToFlatReviewAtObjectPosition": ("br(papenmeier):r1a", ), "braille_toggleTether": ("br(papenmeier):r2", ), "review_currentCharacter": ("br(papenmeier):l1", ), "navigatorObject_toFocus": ("br(papenmeier):r1b", ), "review_activate": ("br(papenmeier):l2", ), "navigatorObject_previous": ("br(papenmeier):left2", ), "navigatorObject_next": ("br(papenmeier):right2", ), "navigatorObject_parent": ("br(papenmeier):up2", ), "navigatorObject_firstChild": ("br(papenmeier):dn2", ), "title": ("br(papenmeier):l1,up", ), "reportStatusLine": ("br(papenmeier):l2,dn", ), "kb:enter": ("br(papenmeier):d8", ), "kb:backspace": ("br(papenmeier):d7", ), "kb:alt": ("br(papenmeier):lt+d3", ), "kb:control": ("br(papenmeier):lt+d2", ), "kb:escape": ("br(papenmeier):space+d7", ), "kb:control+escape": ("br(papenmeier):lt+d1+d2+d3+d4+d5+d6", ), "kb:tab": ("br(papenmeier):space+d3+d7", ), "kb:upArrow": ("br(papenmeier):space+d2", ), "kb:downArrow": ("br(papenmeier):space+d5", ), "kb:leftArrow": ("br(papenmeier):space+d1", ), "kb:rightArrow": ("br(papenmeier):space+d4", ), } }) __gestures = { "br(papenmeier):upperRouting": "upperRouting", }
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 BrailleDisplayDriver(braille.BrailleDisplayDriver): name = "hims" # Translators: The name of a series of braille displays. description = _( "HIMS Braille Sense/Braille EDGE/Smart Beetle/Sync Braille series") isThreadSafe = True timeout = 0.2 @classmethod def getManualPorts(cls): return braille.getSerialPorts( filterFunc=lambda info: "bluetoothName" in info) def __init__(self, port="auto"): super(BrailleDisplayDriver, self).__init__() self.numCells = 0 self._model = None for match in self._getTryPorts(port): portType, portId, port, portInfo = match self.isBulk = portType == bdDetect.KEY_CUSTOM # Try talking to the display. try: if self.isBulk: # onReceiveSize based on max packet size according to USB endpoint information. self._dev = hwIo.Bulk(port, 0, 1, self._onReceive, onReceiveSize=64) else: self._dev = hwIo.Serial(port, baudrate=BAUD_RATE, parity=PARITY, timeout=self.timeout, writeTimeout=self.timeout, onReceive=self._onReceive) except EnvironmentError: log.debugWarning("", exc_info=True) continue for i in range(3): self._sendCellCountRequest() # Wait for an expected response. if self.isBulk: # Hims Bulk devices sometimes present themselves to the system while not yet ready. # For example, when switching the connection mode toggle on the Braille EDGE from Bluetooth to USB, # the USB device is connected but not yet ready. # Wait ten times the timeout, which is ugly, but effective. self._dev.waitForRead(self.timeout * 10) else: self._dev.waitForRead(self.timeout) if self.numCells: break if not self.numCells: log.debugWarning("No response from potential Hims display") self._dev.close() continue self._sendIdentificationRequests(match) if self._model: # A display responded. log.info("Found {device} connected via {type} ({port})".format( device=self._model.name, type=portType, port=port)) break self._dev.close() else: raise RuntimeError("No Hims display found") def display(self, cells: List[int]): # cells will already be padded up to numCells. cellBytes = bytes(cells) self._sendPacket(b"\xfc", b"\x01", cellBytes) def _sendCellCountRequest(self): log.debug("Sending cell count request...") self._sendPacket(b"\xfb", b"\x01", bytes(32)) # send 32 null bytes def _sendIdentificationRequests(self, match: bdDetect.DeviceMatch): log.debug("Considering sending identification requests for device %s" % str(match)) if match.type == bdDetect.KEY_CUSTOM: # USB Bulk matchedModelsMap = [ modelTuple for modelTuple in modelMap if (modelTuple[1].usbId == match.id) ] elif "bluetoothName" in match.deviceInfo: # Bluetooth matchedModelsMap = [ modelTuple for modelTuple in modelMap if (modelTuple[1].bluetoothPrefix and match.id.startswith(modelTuple[1].bluetoothPrefix)) ] else: # The only serial device we support which is not bluetooth, is a Sync Braille self._model = SyncBraille() log.debug( "Use %s as model without sending an additional identification request" % self._model.name) return if not matchedModelsMap: log.debugWarning( "The provided device match to send identification requests didn't yield any results" ) matchedModelsMap = modelMap if len(matchedModelsMap) == 1: modelCls = matchedModelsMap[0][1] numCells = self.numCells or modelCls.numCells if numCells: # There is only one model matching the criteria, and we have the proper number of cells. # There's no point in sending an identification request at all, just use this model log.debug( "Use %s as model without sending an additional identification request" % modelCls.name) self._model = modelCls() self.numCells = numCells return self._model = None for modelId, cls in matchedModelsMap: log.debug("Sending request for id %r" % modelId) self._dev.write(b"".join([b"\x1c", modelId, b"\x1f"])) self._dev.waitForRead(self.timeout) if self._model: log.debug("%s model has been set" % self._model.name) break def _handleIdentification(self, recvId: bytes): modelCls = None models = [ modelCls for modelId, modelCls in modelMap if (modelId == recvId) ] log.debug("Identification received, id %s" % recvId) if not models: raise ValueError("Device identification ID unknown in model map") if len(models) == 1: modelCls = models[0] self.numCells = self.numCells or modelCls.numCells log.debug("There is an exact match, %s found with %d cells" % (modelCls.name, self.numCells)) elif len(models) > 1: log.debug("Multiple models match: %s" % ", ".join(modelCls.name for modelCls in models)) try: modelCls = next(cls for cls in models if cls.numCells == self.numCells) log.debug( "There is an exact match out of multiple models, %s found with %d cells" % (modelCls.name, self.numCells)) except StopIteration: log.debugWarning( "No exact model match found for the reported %d cells display" % self.numCells) try: modelCls = next(cls for cls in models if not cls.numCells) except StopIteration: modelCls = Model if modelCls: self._model = modelCls() def _handlePacket(self, packet: bytes): mode = packet[1] if mode == 0x00: # Cursor routing routingIndex = packet[3] try: inputCore.manager.executeGesture( RoutingInputGesture(routingIndex)) except inputCore.NoInputGestureAction: pass elif mode == 0x01: # Braille input or function key if not self._model: return _keys = int.from_bytes(packet[4:8], "little", signed=False) keys = set() for keyHex in self._model.keys: if _keys & keyHex: # This key is pressed _keys -= keyHex keys.add(keyHex) if _keys == 0: break if _keys: log.error("Unknown key(s) 0x%x received from Hims display" % _keys) return try: inputCore.manager.executeGesture( KeyInputGesture(self._model, keys)) except inputCore.NoInputGestureAction: pass elif mode == 0x02: # Cell count self.numCells = packet[3] def _onReceive(self, data: bytes): if self.isBulk: # data contains the entire packet. stream = BytesIO(data) firstByte: bytes = data[0:1] stream.seek(1) else: firstByte = data # data only contained the first byte. Read the rest from the device. stream = self._dev if firstByte == b"\x1c": # A device is identifying itself deviceId: bytes = stream.read(2) # When a device identifies itself, the packets ends with 0x1f assert stream.read(1) == b"\x1f" self._handleIdentification(deviceId) elif firstByte == b"\xfa": # Command packets are ten bytes long packet = firstByte + stream.read(9) assert packet[2] == 0x01 # Fixed value CHECKSUM_INDEX = 8 checksum: int = packet[CHECKSUM_INDEX] assert packet[9] == 0xfb # Command End calcCheckSum: int = 0xff & sum(c for index, c in enumerate(packet) if (index != CHECKSUM_INDEX)) assert (calcCheckSum == checksum) self._handlePacket(packet) else: log.debug("Unknown first byte received: 0x%x" % ord(firstByte)) return def _sendPacket(self, packetType: bytes, mode: bytes, data1: bytes, data2: bytes = b""): d1Len = len(data1) d2Len = len(data2) # Construct the packet packet: List[bytes] = [ # Packet start packetType * 2, # Mode mode, # Always "\x01" according to the spec # Data block 1 start b"\xf0", # Data block 1 length d1Len.to_bytes(length=2, byteorder="little", signed=False), # Data block 1 data1, # Data block 1 end b"\xf1", # Data block 2 is currently not used, but it is part of the spec # Data block 2 start b"\xf2", # Data block 1 length d2Len.to_bytes(length=2, byteorder="little", signed=False), # Data block 2 data2, # Data block 2 end b"\xf3", # Reserved bytes b"\x00" * 4, # Reserved space for checksum # Note that the checksum has the -3rd position in the final packet bytearray, # whereas it has the -2nd position in the packet list b"\x00", # Packet end b"\xfd" * 2, ] packetB = bytearray(b"".join(packet)) # checksum is the 3rd index from the end because 'packet end' takes up # two bytes and 'packetB' is a bytearray checksumIndexInPacketB: int = -3 checksum: int = 0xff & sum(packetB) packetB[checksumIndexInPacketB] = checksum # check that the packet is the size we expect: ptLen = len(packetType) assert (ptLen == 1) mLen = len(mode) assert (mLen == 1) packetLength = ptLen * 2 + mLen + 1 + 2 + d1Len + 1 + 1 + 2 + d2Len + 1 + 4 + 1 + 2 assert (len(packetB) == packetLength) self._dev.write(bytes(packetB)) def terminate(self): try: super(BrailleDisplayDriver, self).terminate() finally: # We must sleep before closing the port as not doing this can leave the display in a bad state where it can not be re-initialized. time.sleep(self.timeout) # Make sure the device gets closed. # If it doesn't, we may not be able to re-open it later. self._dev.close() gestureMap = inputCore.GlobalGestureMap({ "globalCommands.GlobalCommands": { "braille_routeTo": ("br(hims):routing", ), "braille_scrollBack": ( "br(hims):leftSideScrollUp", "br(hims):rightSideScrollUp", "br(hims):leftSideScroll", ), "braille_scrollForward": ( "br(hims):leftSideScrollDown", "br(hims):rightSideScrollDown", "br(hims):rightSideScroll", ), "braille_previousLine": ("br(hims):leftSideScrollUp+rightSideScrollUp", ), "braille_nextLine": ("br(hims):leftSideScrollDown+rightSideScrollDown", ), "review_previousLine": ("br(hims):rightSideUpArrow", ), "review_nextLine": ("br(hims):rightSideDownArrow", ), "review_previousCharacter": ("br(hims):rightSideLeftArrow", ), "review_nextCharacter": ("br(hims):rightSideRightArrow", ), "braille_toFocus": ( "br(hims):leftSideScrollUp+leftSideScrollDown", "br(hims):rightSideScrollUp+rightSideScrollDown", "br(hims):leftSideScroll+rightSideScroll", ), "kb:control": ( "br(hims.smartbeetle):f1", "br(hims.brailleedge):f3", ), "kb:windows": ( "br(hims.smartbeetle):f2", "br(hims):f7", ), "kb:alt": ( "br(hims):dot1+dot3+dot4+space", "br(hims.smartbeetle):f3", "br(hims):f2", "br(hims.brailleedge):f4", ), "kb:shift": ("br(hims):f5", ), "kb:insert": ( "br(hims):dot2+dot4+space", "br(hims):f6", ), "kb:applications": ( "br(hims):dot1+dot2+dot3+dot4+space", "br(hims):f8", ), "kb:capsLock": ("br(hims):dot1+dot3+dot6+space", ), "kb:tab": ( "br(hims):dot4+dot5+space", "br(hims):f3", "br(hims.brailleedge):f2", ), "kb:shift+alt+tab": ("br(hims):f2+f3+f1", ), "kb:alt+tab": ("br(hims):f2+f3", ), "kb:shift+tab": ("br(hims):dot1+dot2+space", ), "kb:end": ("br(hims):dot4+dot6+space", ), "kb:control+end": ("br(hims):dot4+dot5+dot6+space", ), "kb:home": ( "br(hims):dot1+dot3+space", "br(hims.smartbeetle):f4", ), "kb:control+home": ("br(hims):dot1+dot2+dot3+space", ), "kb:alt+f4": ("br(hims):dot1+dot3+dot5+dot6+space", ), "kb:leftArrow": ( "br(hims):dot3+space", "br(hims):leftSideLeftArrow", ), "kb:control+shift+leftArrow": ("br(hims):dot2+dot8+space+f1", ), "kb:control+leftArrow": ("br(hims):dot2+space", ), "kb:shift+alt+leftArrow": ("br(hims):dot2+dot7+f1", ), "kb:alt+leftArrow": ("br(hims):dot2+dot7", ), "kb:rightArrow": ( "br(hims):dot6+space", "br(hims):leftSideRightArrow", ), "kb:control+shift+rightArrow": ("br(hims):dot5+dot8+space+f1", ), "kb:control+rightArrow": ("br(hims):dot5+space", ), "kb:shift+alt+rightArrow": ("br(hims):dot5+dot7+f1", ), "kb:alt+rightArrow": ("br(hims):dot5+dot7", ), "kb:pageUp": ("br(hims):dot1+dot2+dot6+space", ), "kb:control+pageUp": ("br(hims):dot1+dot2+dot6+dot8+space", ), "kb:upArrow": ( "br(hims):dot1+space", "br(hims):leftSideUpArrow", ), "kb:control+shift+upArrow": ("br(hims):dot2+dot3+dot8+space+f1", ), "kb:control+upArrow": ("br(hims):dot2+dot3+space", ), "kb:shift+alt+upArrow": ("br(hims):dot2+dot3+dot7+f1", ), "kb:alt+upArrow": ("br(hims):dot2+dot3+dot7", ), "kb:shift+upArrow": ("br(hims):leftSideScrollDown+space", ), "kb:pageDown": ("br(hims):dot3+dot4+dot5+space", ), "kb:control+pageDown": ("br(hims):dot3+dot4+dot5+dot8+space", ), "kb:downArrow": ( "br(hims):dot4+space", "br(hims):leftSideDownArrow", ), "kb:control+shift+downArrow": ("br(hims):dot5+dot6+dot8+space+f1", ), "kb:control+downArrow": ("br(hims):dot5+dot6+space", ), "kb:shift+alt+downArrow": ("br(hims):dot5+dot6+dot7+f1", ), "kb:alt+downArrow": ("br(hims):dot5+dot6+dot7", ), "kb:shift+downArrow": ("br(hims):space+rightSideScrollDown", ), "kb:escape": ( "br(hims):dot1+dot5+space", "br(hims):f4", "br(hims.brailleedge):f1", ), "kb:delete": ( "br(hims):dot1+dot3+dot5+space", "br(hims):dot1+dot4+dot5+space", ), "kb:f1": ("br(hims):dot1+dot2+dot5+space", ), "kb:f3": ("br(hims):dot1+dot2+dot4+dot8", ), "kb:f4": ("br(hims):dot7+f3", ), "kb:windows+b": ("br(hims):dot1+dot2+f1", ), "kb:windows+d": ("br(hims):dot1+dot4+dot5+f1", ), "kb:control+insert": ("br(hims.smartbeetle):f1+rightSideScroll", ), "kb:alt+insert": ("br(hims.smartbeetle):f3+rightSideScroll", ), } })
class BrailleDisplayDriver(braille.BrailleDisplayDriver, ScriptableObject): """ Driver for Freedom Scientific braille displays """ name = "freedomScientific" # Translators: Names of braille displays. description = _("Freedom Scientific Focus/PAC Mate series") isThreadSafe = True receivesAckPackets = True timeout = 0.2 wizWheelActions = [ # Translators: The name of a key on a braille display, that scrolls the display # to show previous/next part of a long line. (_("display scroll"), ("globalCommands", "GlobalCommands", "braille_scrollBack"), ("globalCommands", "GlobalCommands", "braille_scrollForward")), # Translators: The name of a key on a braille display, that scrolls the display to show the next/previous line. (_("line scroll"), ("globalCommands", "GlobalCommands", "braille_previousLine"), ("globalCommands", "GlobalCommands", "braille_nextLine")), ] def __init__(self, port="auto"): self.numCells = 0 self._ackPending = False self._pendingCells = [] self._keyBits = 0 self._extendedKeyBits = 0 self._ignoreKeyReleases = False self._model = None self._manufacturer = None self._firmwareVersion = None self.translationTable = None self.leftWizWheelActionCycle = itertools.cycle(self.wizWheelActions) action = self.leftWizWheelActionCycle.next() self.gestureMap.add("br(freedomScientific):leftWizWheelUp", *action[1]) self.gestureMap.add("br(freedomScientific):leftWizWheelDown", *action[2]) self.rightWizWheelActionCycle = itertools.cycle(self.wizWheelActions) action = self.rightWizWheelActionCycle.next() self.gestureMap.add("br(freedomScientific):rightWizWheelUp", *action[1]) self.gestureMap.add("br(freedomScientific):rightWizWheelDown", *action[2]) super(BrailleDisplayDriver, self).__init__() for portType, portId, port, portInfo in self._getTryPorts(port): self.isUsb = portType == bdDetect.KEY_CUSTOM # Try talking to the display. try: if self.isUsb: self._dev = hwIo.Bulk( port, epIn=1, epOut=0, onReceive=self._onReceive, writeSize=0, onReceiveSize=56 ) else: self._dev = hwIo.Serial( port, baudrate=BAUD_RATE, parity=PARITY, timeout=self.timeout, writeTimeout=self.timeout, onReceive=self._onReceive ) except EnvironmentError: log.debugWarning("", exc_info=True) continue # Send an identification request self._sendPacket(FS_PKT_QUERY) for _i in xrange(3): self._dev.waitForRead(self.timeout) if self.numCells and self._model: break if self.numCells and self._model: # A display responded. log.info("Found {device} connected via {type} ({port})".format( device=self._model, type=portType, port=port)) break self._dev.close() else: raise RuntimeError("No Freedom Scientific display found") self._configureDisplay() self.gestureMap.add("br(freedomScientific):topRouting1", "globalCommands", "GlobalCommands", "braille_scrollBack") self.gestureMap.add("br(freedomScientific):topRouting%d" % self.numCells, "globalCommands", "GlobalCommands", "braille_scrollForward") def terminate(self): try: super(BrailleDisplayDriver, self).terminate() finally: # Make sure the device gets closed. # If it doesn't, we may not be able to re-open it later. self._dev.close() def _sendPacket(self, packetType, arg1=FS_BYTE_NULL, arg2=FS_BYTE_NULL, arg3=FS_BYTE_NULL, data=FS_DATA_EMPTY): """Send a packet to the display @param packetType: Type of packet (first byte), use one of the FS_PKT constants @type packetType: str @param arg1: First argument (second byte of packet) @type arg1: str @param arg2: Second argument (third byte of packet) @type arg2: str @param arg3: Third argument (fourth byte of packet) @type arg3: str @param data: Data to send if this is an extended packet, required checksum will be added automatically @type data: str """ def handleArg(arg): if type(arg) == int: return int2byte(arg) return arg arg1 = handleArg(arg1) arg2 = handleArg(arg2) arg3 = handleArg(arg3) packet = [packetType, arg1, arg2, arg3, data] if data: packet.append(int2byte(BrailleDisplayDriver._calculateChecksum("".join(packet)))) self._dev.write("".join(packet)) def _onReceive(self, data): """Event handler when data from the display is received Formats a packet of four bytes in a packet type and three arguments. If the packet is known to have a payload, this is also fetched and the checksum is verified. The constructed packet is handed off to L{_handlePacket}. """ if self.isUsb: data = BytesIO(data) packetType = data.read(1) else: packetType = data data = self._dev arg1 = data.read(1) arg2 = data.read(1) arg3 = data.read(1) log.debug("Got packet of type %r with args: %r %r %r", packetType, arg1, arg2, arg3) # Info and extended key responses are the only packets with payload and checksum if packetType in (FS_PKT_INFO, FS_PKT_EXT_KEY): length = ord(arg1) payload = data.read(length) checksum = ord(data.read(1)) calculatedChecksum = BrailleDisplayDriver._calculateChecksum(packetType + arg1 + arg2 + arg3 + payload) assert calculatedChecksum == checksum, "Checksum mismatch, expected %s but got %s" % (checksum, payload[-1]) else: payload = FS_DATA_EMPTY self._handlePacket(packetType, arg1, arg2, arg3, payload) def _handlePacket(self, packetType, arg1, arg2, arg3, payload): """Handle a packet from the device" The following packet types are handled: * FS_PKT_ACK: See L{_handleAck} * FS_PKT_NAK: Logged and handled as an ACK * FS_PKT_INFO: Manufacturer, model and firmware version are extracted and set as properties on the object. Cell count is determined based on L{MODELS}. * arg1: length of payload * payload: manufacturer, model, firmware version in a fixed width field string * FS_PKT_WHEEL: The corresponding L{WheelGesture}s are sent for the wheel events. * arg1: movement direction (up/down) and number of clicks moved Bits: BBBAAA (least significant) * A: (bits 1-3) number of clicks the wheel has moved * B: (bits 4-6) which wheel (left/right) and what direction (up/down) * FS_PKT_BUTTON: the corresponding L{RoutingGesture} is sent * arg1: number of routing button * arg2: key press/release * arg3: if this is a button on the second row of routing buttons * FS_PKT_KEY: a key or button on the display is pressed/released (including the braille keyboard) * arg 1, 2, 3, 4: These bytes form the value indicating which of the 8 keys are pressed on the device. Key releases can be detected by comparing to the previous state, this work is done in L{_handleKeys}. * FS_PKT_EXT_KEY: ?? * payload: The 4 most significant bits from a single byte are used. More investigation is required. """ if packetType == FS_PKT_ACK: self._handleAck() elif packetType == FS_PKT_NAK: log.debugWarning("NAK received!") self._handleAck() elif packetType == FS_PKT_INFO: self._manufacturer = payload[INFO_MANU_START:INFO_MANU_END].replace(FS_BYTE_NULL, "") self._model = payload[INFO_MODEL_START:INFO_MODEL_END].replace(FS_BYTE_NULL, "") self._firmwareVersion = payload[INFO_VERSION_START:INFO_VERSION_END].replace(FS_BYTE_NULL, "") self.numCells = MODELS.get(self._model, 0) if self.numCells in FOCUS_1_CELL_COUNTS: # Focus first gen: apply custom translation table self.translationTable = FOCUS_1_TRANSLATION_TABLE log.debug("Device info: manufacturer: %s model: %s, version: %s", self._manufacturer, self._model, self._firmwareVersion) elif packetType == FS_PKT_WHEEL: threeLeastSigBitsMask = 0x7 count = ord(arg1) & threeLeastSigBitsMask wheelNumber = ((ord(arg1) >> 3) & threeLeastSigBitsMask) try: # There are only two wheels, one on the left, one on the right. # Either wheel could have moved up or down. isDown, isRight = [ (False, False), (True, False), (True, True), (False, True) ][wheelNumber] except IndexError: log.debugWarning("wheelNumber unknown") return for _i in xrange(count): gesture = WizWheelGesture(self._model, isDown, isRight) try: inputCore.manager.executeGesture(gesture) except inputCore.NoInputGestureAction: pass elif packetType == FS_PKT_BUTTON: key = ord(arg1) # the least significant bit is set when the key is pressed leastSigBitMask = 0x01 isPress = bool(ord(arg2) & leastSigBitMask) isTopRow = bool(ord(arg3)) if isPress: # Ignore keypresses return gesture = RoutingGesture(self._model, key, isTopRow) try: inputCore.manager.executeGesture(gesture) except inputCore.NoInputGestureAction: pass elif packetType == FS_PKT_KEY: keyBits = ord(arg1) | (ord(arg2) << 8) | (ord(arg3) << 16) self._handleKeys(keyBits) elif packetType == FS_PKT_EXT_KEY: keyBits = ord(payload[0]) >> 4 self._handleExtendedKeys(keyBits) else: log.debugWarning("Unknown packet of type: %r", packetType) def _handleAck(self): "Displays any queued cells after receiving an ACK" super(BrailleDisplayDriver, self)._handleAck() if self._pendingCells: self.display(self._pendingCells) @staticmethod def _updateKeyBits(keyBits, oldKeyBits, keyCount): """Helper function that reports if keys have been pressed and which keys have been released based on old and new keybits. """ isRelease = False keyBitsBeforeRelease = 0 newKeysPressed = False keyBit = 0X1 keyBits |= oldKeyBits & ~((0X1 << keyCount) - 1) while oldKeyBits != keyBits: oldKey = oldKeyBits & keyBit newKey = keyBits & keyBit if oldKey and not newKey: # A key has been released isRelease = True if not keyBitsBeforeRelease: keyBitsBeforeRelease = oldKeyBits oldKeyBits &= ~keyBit elif newKey and not oldKey: oldKeyBits |= keyBit newKeysPressed = True keyBit <<= 1 return oldKeyBits, isRelease, keyBitsBeforeRelease, newKeysPressed def _handleKeys(self, keyBits): """Send gestures if keys are released and update self._keyBits""" keyBits, isRelease, keyBitsBeforeRelease, newKeysPressed = self._updateKeyBits(keyBits, self._keyBits, 24) if newKeysPressed: self._ignoreKeyReleases = False self._keyBits = keyBits if isRelease and not self._ignoreKeyReleases: gesture = KeyGesture(self._model, keyBitsBeforeRelease, self._extendedKeyBits) try: inputCore.manager.executeGesture(gesture) except inputCore.NoInputGestureAction: pass self._ignoreKeyReleases = True def _handleExtendedKeys(self, keyBits): """Send gestures if keys are released and update self._extendedKeyBits""" keyBits, isRelease, keyBitsBeforeRelease, newKeysPressed = self._updateKeyBits(keyBits, self._extendedKeyBits, 24) if newKeysPressed: self._ignoreKeyReleases = False self._extendedKeyBits = keyBits if isRelease and not self._ignoreKeyReleases: gesture = KeyGesture(self._model, self._keyBits, keyBitsBeforeRelease) try: inputCore.manager.executeGesture(gesture) except inputCore.NoInputGestureAction: pass self._ignoreKeyReleases = True @staticmethod def _calculateChecksum(data): """Calculate the checksum for extended packets""" checksum = 0 for byte in data: checksum -= ord(byte) checksum = checksum & 0xFF return checksum def display(self, cells): if self.translationTable: cells = _translate(cells, FOCUS_1_TRANSLATION_TABLE) if not self._awaitingAck: cells = b"".join([int2byte(x) for x in cells]) self._sendPacket(FS_PKT_WRITE, int2byte(self.numCells), FS_BYTE_NULL, FS_BYTE_NULL, cells) self._pendingCells = [] else: self._pendingCells = cells def _configureDisplay(self): """Enable extended keys on Focus firmware 3 and up""" if not self._model or not self._firmwareVersion: return if self._model.startswith("Focus") and ord(self._firmwareVersion[0]) >= ord("3"): # Focus 2 or later. Make sure extended keys support is enabled. log.debug("Activating extended keys on freedom Scientific display. Display name: %s, firmware version: %s.", self._model, self._firmwareVersion) self._sendPacket(FS_PKT_CONFIG, FS_CFG_EXTKEY) def script_toggleLeftWizWheelAction(self, _gesture): action = self.leftWizWheelActionCycle.next() self.gestureMap.add("br(freedomScientific):leftWizWheelUp", *action[1], replace=True) self.gestureMap.add("br(freedomScientific):leftWizWheelDown", *action[2], replace=True) braille.handler.message(action[0]) def script_toggleRightWizWheelAction(self, _gesture): action = self.rightWizWheelActionCycle.next() self.gestureMap.add("br(freedomScientific):rightWizWheelUp", *action[1], replace=True) self.gestureMap.add("br(freedomScientific):rightWizWheelDown", *action[2], replace=True) braille.handler.message(action[0]) __gestures = { "br(freedomScientific):leftWizWheelPress": "toggleLeftWizWheelAction", "br(freedomScientific):rightWizWheelPress": "toggleRightWizWheelAction", } gestureMap = inputCore.GlobalGestureMap({ "globalCommands.GlobalCommands": { "braille_routeTo": ("br(freedomScientific):routing",), "braille_scrollBack": ("br(freedomScientific):leftAdvanceBar", "br(freedomScientific):leftBumperBarUp", "br(freedomScientific):rightBumperBarUp",), "braille_scrollForward": ("br(freedomScientific):rightAdvanceBar", "br(freedomScientific):leftBumperBarDown", "br(freedomScientific):rightBumperBarDown",), "braille_previousLine": ("br(freedomScientific):leftRockerBarUp", "br(freedomScientific):rightRockerBarUp",), "braille_nextLine": ("br(freedomScientific):leftRockerBarDown", "br(freedomScientific):rightRockerBarDown",), "kb:shift+tab": ("br(freedomScientific):dot1+dot2+brailleSpaceBar",), "kb:tab": ("br(freedomScientific):dot4+dot5+brailleSpaceBar",), "kb:upArrow": ("br(freedomScientific):dot1+brailleSpaceBar",), "kb:downArrow": ("br(freedomScientific):dot4+brailleSpaceBar",), "kb:leftArrow": ("br(freedomScientific):dot3+brailleSpaceBar",), "kb:rightArrow": ("br(freedomScientific):dot6+brailleSpaceBar",), "kb:control+leftArrow": ("br(freedomScientific):dot2+brailleSpaceBar",), "kb:control+rightArrow": ("br(freedomScientific):dot5+brailleSpaceBar",), "kb:home": ("br(freedomScientific):dot1+dot3+brailleSpaceBar",), "kb:control+home": ("br(freedomScientific):dot1+dot2+dot3+brailleSpaceBar",), "kb:end": ("br(freedomScientific):dot4+dot6+brailleSpaceBar",), "kb:control+end": ("br(freedomScientific):dot4+dot5+dot6+brailleSpaceBar",), "kb:alt": ("br(freedomScientific):dot1+dot3+dot4+brailleSpaceBar",), "kb:alt+tab": ("br(freedomScientific):dot2+dot3+dot4+dot5+brailleSpaceBar",), "kb:alt+shift+tab": ("br(freedomScientific):dot1+dot2+dot5+dot6+brailleSpaceBar",), "kb:windows+tab": ("br(freedomScientific):dot2+dot3+dot4+brailleSpaceBar",), "kb:escape": ("br(freedomScientific):dot1+dot5+brailleSpaceBar",), "kb:windows": ("br(freedomScientific):dot2+dot4+dot5+dot6+brailleSpaceBar",), "kb:windows+d": ("br(freedomScientific):dot1+dot2+dot3+dot4+dot5+dot6+brailleSpaceBar",), "reportCurrentLine": ("br(freedomScientific):dot1+dot4+brailleSpaceBar",), "showGui": ("br(freedomScientific):dot1+dot3+dot4+dot5+brailleSpaceBar",), "braille_toggleTether": ("br(freedomScientific):leftGDFButton+rightGDFButton",), } })
class BrailleDisplayDriver(braille.BrailleDisplayDriver, ScriptableObject): name = "handyTech" # Translators: The name of a series of braille displays. description = _("Handy Tech braille displays") isThreadSafe = True receivesAckPackets = True timeout = 0.2 @classmethod def check(cls): return True @classmethod def getPossiblePorts(cls): ports = OrderedDict() comPorts = list(hwPortUtils.listComPorts(onlyAvailable=True)) try: next(cls._getAutoPorts(comPorts)) ports.update((cls.AUTOMATIC_PORT, )) except StopIteration: pass for portInfo in comPorts: # Translators: Name of a serial communications port. ports[portInfo["port"]] = _("Serial: {portName}").format( portName=portInfo["friendlyName"]) return ports @classmethod def _getAutoPorts(cls, comPorts): for portInfo in hwPortUtils.listHidDevices(): if portInfo.get("usbID") in USB_IDS_HID_CONVERTER: yield portInfo["devicePath"], "USB HID serial converter" if portInfo.get("usbID") in USB_IDS_HID_NATIVE: yield portInfo["devicePath"], "USB HID" # Try bluetooth ports last. for portInfo in sorted(comPorts, key=lambda item: "bluetoothName" in item): port = portInfo["port"] hwId = portInfo["hardwareID"] if hwId.startswith(r"FTDIBUS\COMPORT"): # USB. # TODO: It seems there is also another chip (Gohubs) used in some models. See if we can autodetect that as well. portType = "USB serial" try: usbId = hwId.split("&", 1)[1] except IndexError: continue if usbId not in USB_IDS_SER: continue elif "bluetoothName" in portInfo: # Bluetooth. portType = "bluetooth" btName = portInfo["bluetoothName"] if not any( btName.startswith(prefix) for prefix in BLUETOOTH_NAMES): continue else: continue yield port, portType def __init__(self, port="auto"): super(BrailleDisplayDriver, self).__init__() self.numCells = 0 self._model = None self._ignoreKeyReleases = False self._keysDown = set() self._brailleInput = False self._hidSerialBuffer = "" if port == "auto": tryPorts = self._getAutoPorts( hwPortUtils.listComPorts(onlyAvailable=True)) else: tryPorts = ((port, "serial"), ) for port, portType in tryPorts: # At this point, a port bound to this display has been found. # Try talking to the display. self.isHid = portType.startswith("USB HID") self.isHidSerial = portType == "USB HID serial converter" try: if self.isHid: self._dev = hwIo.Hid(port, onReceive=self._onReceive) if self.isHidSerial: # This is either the standalone HID adapter cable for older displays, # or an older display with a HID - serial adapter built in # Send a flush to open the serial channel self._dev.write(HT_HID_RPT_InCommand + HT_HID_CMD_FlushBuffers) else: self._dev = hwIo.Serial(port, baudrate=BAUD_RATE, parity=PARITY, timeout=self.timeout, writeTimeout=self.timeout, onReceive=self._onReceive) except EnvironmentError: log.debugWarning("", exc_info=True) continue self.sendPacket(HT_PKT_RESET) for _i in xrange(3): # An expected response hasn't arrived yet, so wait for it. self._dev.waitForRead(self.timeout) if self.numCells and self._model: break if self.numCells: # A display responded. self._model.postInit() log.info("Found {device} connected via {type} ({port})".format( device=self._model.name, type=portType, port=port)) break self._dev.close() else: raise RuntimeError("No Handy Tech display found") def terminate(self): try: super(BrailleDisplayDriver, self).terminate() finally: # Make sure the device gets closed. # If it doesn't, we may not be able to re-open it later. self._dev.close() def sendPacket(self, packetType, data=""): if type(data) == bool or type(data) == int: data = chr(data) if self._model: data = self._model.deviceId + data if self.isHid: self._sendHidPacket(packetType + data) else: self._dev.write(packetType + data) def sendExtendedPacket(self, packetType, data=""): if type(data) == bool or type(data) == int: data = chr(data) packet = "{length}{extType}{data}\x16".format( extType=packetType, data=data, length=chr(len(data) + len(packetType))) self.sendPacket(HT_PKT_EXTENDED, packet) def _sendHidPacket(self, packet): assert self.isHid maxBlockSize = self._dev._writeSize - 3 # When the packet length exceeds C{writeSize}, the packet is split up into several packets. # These packets are of size C{blockSize}. # They contain C{HT_HID_RPT_InData}, the length of the data block, # the data block itself and a terminating null character. bytesRemaining = packet while bytesRemaining: blockSize = min(maxBlockSize, len(bytesRemaining)) hidPacket = HT_HID_RPT_InData + chr( blockSize) + bytesRemaining[:blockSize] + "\x00" self._dev.write(hidPacket) bytesRemaining = bytesRemaining[blockSize:] def _handleKeyRelease(self): if self._ignoreKeyReleases or not self._keysDown: return # The first key released executes the key combination. try: inputCore.manager.executeGesture( InputGesture(self._model, self._keysDown, self._brailleInput)) except inputCore.NoInputGestureAction: pass # Any further releases are just the rest of the keys in the combination # being released, so they should be ignored. self._ignoreKeyReleases = True # pylint: disable=R0912 # Pylint complains about many branches, might be worth refactoring def _onReceive(self, data): if self.isHidSerial: # The HID serial converter seems to wrap one or two bytes into a single HID packet hidLength = ord(data[1]) self._hidSerialBuffer += data[2:(2 + hidLength)] currentBufferLength = len(self._hidSerialBuffer) # We only support the extended packet based protocol # Thus, the only non-extended packet we expect is the device identification, which is of type HT_PKT_OK and two bytes in size serPacketType = self._hidSerialBuffer[0] if serPacketType != HT_PKT_EXTENDED: if currentBufferLength > 2: stream = StringIO(self._hidSerialBuffer[:2]) self._hidSerialBuffer = self._hidSerialBuffer[2:] elif currentBufferLength == 2: stream = StringIO(self._hidSerialBuffer) self._hidSerialBuffer = "" else: # The packet is not yet complete return # Extended packets are at least 5 bytes in size. elif serPacketType == HT_PKT_EXTENDED and currentBufferLength >= 5: # Check whether our packet is complete # The second byte is the model, the third byte is the data length, excluding the terminator packet_length = ord(self._hidSerialBuffer[2]) + 4 if len(self._hidSerialBuffer) < packet_length: # The packet is not yet complete return # We have a complete packet, but it must be isolated from another packet that could have landed in the buffer if len(self._hidSerialBuffer) > packet_length: stream = StringIO(self._hidSerialBuffer[:packet_length]) self._hidSerialBuffer = self._hidSerialBuffer[ packet_length:] else: assert self._hidSerialBuffer.endswith( "\x16") # Extended packets are terminated with \x16 stream = StringIO(self._hidSerialBuffer) self._hidSerialBuffer = "" else: # The packet is not yet complete return stream.seek(1) elif self.isHid: # data contains the entire packet. stream = StringIO(data) serPacketType = data[2] # Skip the header, so reading the stream will only give the rest of the data stream.seek(3) else: serPacketType = data # data only contained the packet type. Read the rest from the device. stream = self._dev modelId = stream.read(1) if not self._model: if not modelId in MODELS: log.debugWarning("Unknown model: %r" % modelId) raise RuntimeError( "The model with ID %r is not supported by this driver" % modelId) self._model = MODELS.get(modelId)(self) self.numCells = self._model.numCells elif self._model.deviceId != modelId: # Somehow the model ID of this display changed, probably another display # plugged in the same (already open) serial port. self.terminate() if serPacketType == HT_PKT_OK: pass elif serPacketType == HT_PKT_ACK: # This is unexpected, but we need to make sure that we handle old style ack self._handleAck() elif serPacketType == HT_PKT_NAK: log.debugWarning("NAK received!") elif serPacketType == HT_PKT_EXTENDED: packet_length = ord(stream.read(1)) packet = stream.read(packet_length) terminator = stream.read(1) assert terminator == "\x16" # Extended packets are terminated with \x16 extPacketType = packet[0] if extPacketType == HT_EXTPKT_CONFIRMATION: # Confirmation of a command. if packet[1] == HT_PKT_ACK: self._handleAck() elif packet[1] == HT_PKT_NAK: log.debugWarning("NAK received!") elif extPacketType == HT_EXTPKT_KEY: key = ord(packet[1]) release = (key & KEY_RELEASE) != 0 if release: key = key ^ KEY_RELEASE self._handleKeyRelease() self._keysDown.discard(key) else: # Press. # This begins a new key combination. self._ignoreKeyReleases = False self._keysDown.add(key) elif extPacketType == HT_EXTPKT_ATC_INFO: # Ignore ATC packets for now pass elif extPacketType == HT_EXTPKT_GET_PROTOCOL_PROPERTIES: pass else: # Unknown extended packet, log it log.debugWarning("Unhandled extended packet of type %r: %r" % (extPacketType, packet)) else: # Unknown packet type, log it log.debugWarning("Unhandled packet of type %r" % serPacketType) def display(self, cells): # cells will already be padded up to numCells. self._model.display(cells) scriptCategory = SCRCAT_BRAILLE def script_toggleBrailleInput(self, _gesture): self._brailleInput = not self._brailleInput if self._brailleInput: # Translators: message when braille input is enabled ui.message(_('Braille input enabled')) else: # Translators: message when braille input is disabled ui.message(_('Braille input disabled')) # Translators: description of the script to toggle braille input script_toggleBrailleInput.__doc__ = _("Toggle braille input") __gestures = { 'br(handytech):space+b1+b3+b4': 'toggleBrailleInput', 'br(handytech):leftSpace+b1+b3+b4': 'toggleBrailleInput', 'br(handytech):rightSpace+b1+b3+b4': 'toggleBrailleInput', 'br(handytech.easybraille):left+b1+b3+b4': 'toggleBrailleInput', 'br(handytech.easybraille):right+b1+b3+b4': 'toggleBrailleInput', 'br(handytech):space+dot1+dot2+dot7': 'toggleBrailleInput', } gestureMap = inputCore.GlobalGestureMap({ "globalCommands.GlobalCommands": { "braille_routeTo": ("br(handyTech):routing", ), "braille_scrollBack": ( "br(handytech):leftSpace", "br(handytech):leftTakTop", "br(handytech):rightTakTop", "br(handytech):b3", "br(handytech):left", ), "braille_previousLine": ("br(handytech):b4", ), "braille_nextLine": ("br(handytech):b5", ), "braille_scrollForward": ( "br(handytech):rightSpace", "br(handytech):leftTakBottom", "br(handytech):rightTakBottom", "br(handytech):b6", "br(handytech):right", ), "braille_toggleTether": ("br(handytech):b2", ), "braille_toggleFocusContextPresentation": ("br(handytech):b7", ), "braille_toggleShowCursor": ("br(handytech):b1", ), "kb:shift+tab": ( "br(handytech):leftTakTop+leftTakBottom", "br(handytech):escape", ), "kb:tab": ( "br(handytech):rightTakTop+rightTakBottom", "br(handytech):return", ), "kb:enter": ( "br(handytech):leftTakTop+leftTakBottom+rightTakTop+rightTakBottom", "br(handytech):b8", "br(handytech):escape+return", "br(handytech):joystickAction", ), "kb:alt": ("br(handytech):b2+b4+b5", ), "kb:escape": ("br(handytech):b4+b6", ), "kb:upArrow": ("br(handytech):joystickUp", ), "kb:downArrow": ("br(handytech):joystickDown", ), "kb:leftArrow": ("br(handytech):joystickLeft", ), "kb:rightArrow": ("br(handytech):joystickRight", ), "kb:1": ("br(handytech):n1", ), "kb:2": ("br(handytech):n2", ), "kb:3": ("br(handytech):n3", ), "kb:4": ("br(handytech):n4", ), "kb:5": ("br(handytech):n5", ), "kb:6": ("br(handytech):n6", ), "kb:7": ("br(handytech):n7", ), "kb:8": ("br(handytech):n8", ), "kb:9": ("br(handytech):n9", ), "kb:0": ("br(handytech):n0", ), "showGui": ("br(handytech):b2+b4+b5+b6", ), }, })
class BrailleDisplayDriver(braille.BrailleDisplayDriver): name = "hims" # Translators: The name of a series of braille displays. description = _( "HIMS Braille Sense/Braille EDGE/Smart Beetle/Sync Braille series") isThreadSafe = True timeout = 0.2 @classmethod def check(cls): return True @classmethod def getPossiblePorts(cls): ports = OrderedDict() comPorts = list(hwPortUtils.listComPorts(onlyAvailable=True)) try: next(cls._getAutoPorts(comPorts)) ports.update((cls.AUTOMATIC_PORT, )) except StopIteration: pass for portInfo in comPorts: if not "bluetoothName" in portInfo: continue # Translators: Name of a serial communications port. ports[portInfo["port"]] = _("Serial: {portName}").format( portName=portInfo["friendlyName"]) return ports @classmethod def _getAutoPorts(cls, comPorts): # USB bulk for bulkId in USB_IDS_BULK: portType = "USB bulk" try: rootKey = _winreg.OpenKey( _winreg.HKEY_LOCAL_MACHINE, r"SYSTEM\CurrentControlSet\Enum\USB\%s" % bulkId) except WindowsError: continue else: with rootKey: for index in itertools.count(): try: keyName = _winreg.EnumKey(rootKey, index) except WindowsError: break try: with _winreg.OpenKey( rootKey, os.path.join( keyName, "Device Parameters")) as paramsKey: yield _winreg.QueryValueEx( paramsKey, "SymbolicName")[0], portType, bulkId except WindowsError: continue # Try bluetooth ports last. for portInfo in sorted(comPorts, key=lambda item: "bluetoothName" in item): port = portInfo["port"] hwID = portInfo["hardwareID"] if hwID.startswith(r"FTDIBUS\COMPORT"): # USB. portType = "USB serial" try: usbID = hwID.split("&", 1)[1] except IndexError: continue if usbID != SyncBraille.usbId: continue yield portInfo['port'], portType, usbID elif "bluetoothName" in portInfo: # Bluetooth. portType = "bluetooth" btName = portInfo["bluetoothName"] for prefix in bluetoothPrefixes: if btName.startswith(prefix): btPrefix = prefix break else: btPrefix = None yield portInfo['port'], portType, btPrefix def __init__(self, port="auto"): super(BrailleDisplayDriver, self).__init__() self.numCells = 0 self._model = None if port == "auto": tryPorts = self._getAutoPorts( hwPortUtils.listComPorts(onlyAvailable=True)) else: try: btName = next( portInfo.get("bluetoothName", "") for portInfo in hwPortUtils.listComPorts() if portInfo.get("port") == port) btPrefix = next(prefix for prefix in bluetoothPrefixes if btName.startswith(prefix)) tryPorts = ((port, "bluetooth", btPrefix), ) except StopIteration: tryPorts = () for port, portType, identifier in tryPorts: self.isBulk = portType == "USB bulk" # Try talking to the display. try: if self.isBulk: # onReceiveSize based on max packet size according to USB endpoint information. self._dev = hwIo.Bulk(port, 0, 1, self._onReceive, writeSize=0, onReceiveSize=64) else: self._dev = hwIo.Serial(port, baudrate=BAUD_RATE, parity=PARITY, timeout=self.timeout, writeTimeout=self.timeout, onReceive=self._onReceive) except EnvironmentError: log.debugWarning("", exc_info=True) continue for i in xrange(3): self._sendCellCountRequest() # Wait for an expected response. if self.isBulk: # Hims Bulk devices sometimes present themselves to the system while not yet ready. # For example, when switching the connection mode toggle on the Braille EDGE from Bluetooth to USB, # the USB device is connected but not yet ready. # Wait ten times the timeout, which is ugly, but effective. self._dev.waitForRead(self.timeout * 10) else: self._dev.waitForRead(self.timeout) if self.numCells: break if not self.numCells: log.debugWarning("No response from potential Hims display") self._dev.close() continue if portType == "USB serial": self._model = SyncBraille() elif self.isBulk: self._sendIdentificationRequests(usbId=identifier) elif portType == "bluetooth" and identifier: self._sendIdentificationRequests(bluetoothPrefix=identifier) else: self._sendIdentificationRequests() if self._model: # A display responded. log.info("Found {device} connected via {type} ({port})".format( device=self._model.name, type=portType, port=port)) break self._dev.close() else: raise RuntimeError("No Hims display found") def display(self, cells): # cells will already be padded up to numCells. self._sendPacket("\xfc", "\x01", "".join(chr(cell) for cell in cells)) def _sendCellCountRequest(self): log.debug("Sending cell count request...") self._sendPacket("\xfb", "\x01", "\x00" * 32) def _sendIdentificationRequests(self, usbId=None, bluetoothPrefix=None): log.debug( "Considering sending identification requests: usbId=%s, bluetoothPrefix=%s" % (usbId, bluetoothPrefix)) if usbId and not bluetoothPrefix: map = [ modelTuple for modelTuple in modelMap if modelTuple[1].usbId == usbId ] elif not usbId and bluetoothPrefix: map = [ modelTuple for modelTuple in modelMap if modelTuple[1].bluetoothPrefix == bluetoothPrefix ] elif usbId and bluetoothPrefix: map = [ modelTuple for modelTuple in modelMap if modelTuple[1].usbId == usbId and modelCls.bluetoothPrefix == bluetoothPrefix ] else: # not usbId and not bluetoothPrefix map = modelMap if not map: raise ValueError( "The specified criteria to send identification requests didn't yield any results" ) if len(map) == 1: modelCls = map[0][1] numCells = self.numCells or modelCls.numCells if numCells: # There is only one model matching the criteria, and we have the proper number of cells. # There's no point in sending an identification request at all, just use this model log.debug( "Chose %s as model without sending an additional identification request" % modelCls.name) self._model = modelCls() self.numCells = numCells return self._model = None for id, cls in map: log.debug("Sending request for id %r" % id) self._dev.write("\x1c{id}\x1f".format(id=id)) self._dev.waitForRead(self.timeout) if self._model: log.debug("%s model has been set" % self._model.name) break def _handleIdentification(self, id): modelCls = None models = [modelCls for modelId, modelCls in modelMap if modelId == id] log.debug("Identification received, id %s" % id) if not models: raise ValueError("Device identification ID unknown in model map") if len(models) == 1: modelCls = models[0] self.numCells = self.numCells or modelCls.numCells log.debug("There is an exact match, %s found with %d cells" % (modelCls.name, self.numCells)) elif len(models) > 1: log.debug("Multiple models match: %s" % ", ".join(modelCls.name for modelCls in models)) try: modelCls = next(cls for cls in models if cls.numCells == self.numCells) log.debug( "There is an exact match out of multiple models, %s found with %d cells" % (modelCls.name, self.numCells)) except StopIteration: log.debugWarning( "No exact model match found for the reported %d cells display" % self.numCells) try: modelCls = next(cls for cls in models if not cls.numCells) except StopIteration: modelCls = Model if modelCls: self._model = modelCls() def _handlePacket(self, packet): mode = packet[1] if mode == "\x00": # Cursor routing routingIndex = ord(packet[3]) try: inputCore.manager.executeGesture( RoutingInputGesture(routingIndex)) except inputCore.NoInputGestureAction: pass elif mode == "\x01": # Braille input or function key if not self._model: return _keys = sum(ord(packet[4 + i]) << (i * 8) for i in xrange(4)) keys = set() for keyHex in self._model.keys: if _keys & keyHex: # This key is pressed _keys -= keyHex keys.add(keyHex) if _keys == 0: break if _keys: log.error("Unknown key(s) 0x%x received from Hims display" % _keys) return try: inputCore.manager.executeGesture( KeyInputGesture(self._model, keys)) except inputCore.NoInputGestureAction: pass elif mode == "\x02": # Cell count self.numCells = ord(packet[3]) def _onReceive(self, data): if self.isBulk: # data contains the entire packet. stream = StringIO(data) firstByte = data[0] stream.seek(1) else: firstByte = data # data only contained the first byte. Read the rest from the device. stream = self._dev if firstByte == "\x1c": # A device is identifying itself deviceId = stream.read(2) # When a device identifies itself, the packets ends with 0x1f assert stream.read(1) == "\x1f" self._handleIdentification(deviceId) elif firstByte == "\xfa": # Command packets are ten bytes long packet = firstByte + stream.read(9) assert packet[2] == "\x01" # Fixed value checksum = packet[8] assert packet[9] == "\xfb" # Command End assert (chr(sum(ord(c) for c in packet[0:8] + packet[9]) & 0xff) == checksum) self._handlePacket(packet) else: log.debug("Unknown first byte received: 0x%x" % ord(firstByte)) return def _sendPacket(self, type, mode, data1, data2=""): packetLength = 2 + 1 + 1 + 2 + len(data1) + 1 + 1 + 2 + len( data2) + 1 + 4 + 1 + 2 # Construct the packet packet = [ # Packet start type * 2, # Mode mode, # Always "\x01" according to the spec # Data block 1 start "\xf0", # Data block 1 length chr((len(data1) >> 0) & 0xff), chr((len(data1) >> 8) & 0xff), # Data block 1 data1, # Data block 1 end "\xf1", # Data block 2 is currently not used, but it is part of the spec # Data block 2 start "\xf2", # Data block 1 length chr((len(data2) >> 0) & 0xff), chr((len(data2) >> 8) & 0xff), # Data block 2 data2, # Data block 2 end "\xf3", # Reserved bytes "\x00" * 4, # Reserved space for checksum "\x00", # Packet end "\xfd" * 2, ] packetStrWithoutCheksum = "".join(s for s in packet) packet[-2] = chr(sum(ord(c) for c in packetStrWithoutCheksum) & 0xff) packetStrWithCheksum = "".join(s for s in packet) assert (len(packetStrWithCheksum) == packetLength) self._dev.write(packetStrWithCheksum) def terminate(self): try: super(BrailleDisplayDriver, self).terminate() finally: # We must sleep before closing the port as not doing this can leave the display in a bad state where it can not be re-initialized. time.sleep(self.timeout) # Make sure the device gets closed. # If it doesn't, we may not be able to re-open it later. self._dev.close() gestureMap = inputCore.GlobalGestureMap({ "globalCommands.GlobalCommands": { "braille_routeTo": ("br(hims):routing", ), "braille_scrollBack": ( "br(hims):leftSideScrollUp", "br(hims):rightSideScrollUp", "br(hims):leftSideScroll", ), "braille_scrollForward": ( "br(hims):leftSideScrollDown", "br(hims):rightSideScrollDown", "br(hims):rightSideScroll", ), "braille_previousLine": ("br(hims):leftSideScrollUp+rightSideScrollUp", ), "braille_nextLine": ("br(hims):leftSideScrollDown+rightSideScrollDown", ), "review_previousLine": ("br(hims):rightSideUpArrow", ), "review_nextLine": ("br(hims):rightSideDownArrow", ), "review_previousCharacter": ("br(hims):rightSideLeftArrow", ), "review_nextCharacter": ("br(hims):rightSideRightArrow", ), "braille_toFocus": ( "br(hims):leftSideScrollUp+leftSideScrollDown", "br(hims):rightSideScrollUp+rightSideScrollDown", "br(hims):leftSideScroll+rightSideScroll", ), "kb:control": ( "br(hims.smartbeetle):f1", "br(hims.brailleedge):f3", ), "kb:windows": ( "br(hims.smartbeetle):f2", "br(hims):f7", ), "kb:alt": ( "br(hims):dot1+dot3+dot4+space", "br(hims.smartbeetle):f3", "br(hims):f2", "br(hims.brailleedge):f4", ), "kb:shift": ("br(hims):f5", ), "kb:insert": ( "br(hims):dot2+dot4+space", "br(hims):f6", ), "kb:applications": ( "br(hims):dot1+dot2+dot3+dot4+space", "br(hims):f8", ), "kb:capsLock": ("br(hims):dot1+dot3+dot6+space", ), "kb:tab": ( "br(hims):dot4+dot5+space", "br(hims):f3", "br(hims.brailleedge):f2", ), "kb:shift+alt+tab": ("br(hims):f2+f3+f1", ), "kb:alt+tab": ("br(hims):f2+f3", ), "kb:shift+tab": ("br(hims):dot1+dot2+space", ), "kb:end": ("br(hims):dot4+dot6+space", ), "kb:control+end": ("br(hims):dot4+dot5+dot6+space", ), "kb:home": ( "br(hims):dot1+dot3+space", "br(hims.smartbeetle):f4", ), "kb:control+home": ("br(hims):dot1+dot2+dot3+space", ), "kb:alt+f4": ("br(hims):dot1+dot3+dot5+dot6+space", ), "kb:leftArrow": ( "br(hims):dot3+space", "br(hims):leftSideLeftArrow", ), "kb:control+shift+leftArrow": ("br(hims):dot2+dot8+space+f1", ), "kb:control+leftArrow": ("br(hims):dot2+space", ), "kb:shift+alt+leftArrow": ("br(hims):dot2+dot7+f1", ), "kb:alt+leftArrow": ("br(hims):dot2+dot7", ), "kb:rightArrow": ( "br(hims):dot6+space", "br(hims):leftSideRightArrow", ), "kb:control+shift+rightArrow": ("br(hims):dot5+dot8+space+f1", ), "kb:control+rightArrow": ("br(hims):dot5+space", ), "kb:shift+alt+rightArrow": ("br(hims):dot5+dot7+f1", ), "kb:alt+rightArrow": ("br(hims):dot5+dot7", ), "kb:pageUp": ("br(hims):dot1+dot2+dot6+space", ), "kb:control+pageUp": ("br(hims):dot1+dot2+dot6+dot8+space", ), "kb:upArrow": ( "br(hims):dot1+space", "br(hims):leftSideUpArrow", ), "kb:control+shift+upArrow": ("br(hims):dot2+dot3+dot8+space+f1", ), "kb:control+upArrow": ("br(hims):dot2+dot3+space", ), "kb:shift+alt+upArrow": ("br(hims):dot2+dot3+dot7+f1", ), "kb:alt+upArrow": ("br(hims):dot2+dot3+dot7", ), "kb:shift+upArrow": ("br(hims):leftSideScrollDown+space", ), "kb:pageDown": ("br(hims):dot3+dot4+dot5+space", ), "kb:control+pageDown": ("br(hims):dot3+dot4+dot5+dot8+space", ), "kb:downArrow": ( "br(hims):dot4+space", "br(hims):leftSideDownArrow", ), "kb:control+shift+downArrow": ("br(hims):dot5+dot6+dot8+space+f1", ), "kb:control+downArrow": ("br(hims):dot5+dot6+space", ), "kb:shift+alt+downArrow": ("br(hims):dot5+dot6+dot7+f1", ), "kb:alt+downArrow": ("br(hims):dot5+dot6+dot7", ), "kb:shift+downArrow": ("br(hims):space+rightSideScrollDown", ), "kb:escape": ( "br(hims):dot1+dot5+space", "br(hims):f4", "br(hims.brailleedge):f1", ), "kb:delete": ( "br(hims):dot1+dot3+dot5+space", "br(hims):dot1+dot4+dot5+space", ), "kb:f1": ("br(hims):dot1+dot2+dot5+space", ), "kb:f3": ("br(hims):dot1+dot2+dot4+dot8", ), "kb:f4": ("br(hims):dot7+f3", ), "kb:windows+b": ("br(hims):dot1+dot2+f1", ), "kb:windows+d": ("br(hims):dot1+dot4+dot5+f1", ), "kb:control+insert": ("br(hims.smartbeetle):f1+rightSideScroll", ), "kb:alt+insert": ("br(hims.smartbeetle):f3+rightSideScroll", ), } })
class BrailleDisplayDriver(braille.BrailleDisplayDriver): name = "seika" # Translators: Names of braille displays. description = _("Seika Braille Displays") numCells = 0 @classmethod def check(cls): return True def __init__(self): super(BrailleDisplayDriver, self).__init__() for portInfo in hwPortUtils.listComPorts(onlyAvailable=True): port = portInfo["port"] hwID = portInfo["hardwareID"] # Seika USB to Serial, in XP it is lowercase, in Win7 uppercase if not hwID.upper().startswith(r"USB\VID_10C4&PID_EA60"): continue # At this point, a port bound to this display has been found. # Try talking to the display. try: self._ser = serial.Serial( port, baudrate=BAUDRATE, timeout=TIMEOUT, writeTimeout=TIMEOUT, parity=serial.PARITY_ODD, bytesize=serial.EIGHTBITS, stopbits=serial.STOPBITS_ONE ) except serial.SerialException: continue log.debug(f"serial port open {port}") # get the version information VERSION_INFO_REQUEST = b"\x1C" self._ser.write(BUF_START + VERSION_INFO_REQUEST) self._ser.flush() # Read out the input buffer versionS = self._ser.read(13) log.debug(f"receive {versionS}") if versionS.startswith(b"seika80"): log.info(f"Found Seika80 connected via {port} Version {versionS}") self.numCells = 80 self._maxCellRead = 20 # data header for seika 80 self.sendHeader = (BUF_START + b"s80").ljust(8, b"\x00") break elif versionS.startswith(b"seika3"): log.info(f"Found Seika3/5 connected via {port} Version {versionS}") self.numCells = 40 self._maxCellRead = 10 # data header for v3, v5 self.sendHeader = (BUF_START + b"seika").ljust(8, b"\x00") break # is it a old Seika3? log.debug("test if it is a old Seika3") LEGACY_VERSION_REQUEST = b"\x0A" self._ser.write(BUF_START + LEGACY_VERSION_REQUEST) self._ser.flush() # Read out the input buffer versionS = self._ser.read(12) log.debug(f"receive {versionS}") if versionS.startswith(prefix=( b'\x00\x05(\x08v5.0\x01\x01\x01\x01', b'\x00\x05(\x08seika\x00' )): log.info(f"Found Seika3 old Version connected via {port} Version {versionS}") self.numCells = 40 self._maxCellRead = 10 self.sendHeader = BUF_START + b"\x04\x00\x63\x00\x50\x00" break self._ser.close() else: raise RuntimeError("No SEIKA40/80 display found") self._readTimer = wx.PyTimer(self.handleResponses) self._readTimer.Start(READ_INTERVAL) def terminate(self): try: super(BrailleDisplayDriver, self).terminate() self._readTimer.Stop() self._readTimer = None finally: self._ser.close() def display(self, cells: List[int]): # every transmitted line consists of the preamble 'sendHeader' and the Cells if 80 == self.numCells: lineBytes: bytes = self.sendHeader + bytes(cells) elif 40 == self.numCells: cellData = (b"\x00" + intToByte(cell) for cell in cells) lineBytes = self.sendHeader + b"".join(cellData) else: log.error("Unsupported cell count for seika braille device") return self._ser.write(lineBytes) def handleResponses(self): if not self._ser.in_waiting: return chars: bytes = self._ser.read(2) isCursorRoutingBlock = not chars[0] & 0x60 if not isCursorRoutingBlock: # normal key self._handleNormalKey(chars) else: # Cursor Routing Block chars: bytes = self._ser.read(self._maxCellRead) self._handleCursorRoutingBlock(chars) def _handleCursorRoutingBlock(self, chars: bytes) -> None: key = 0 i = 0 k = self._maxCellRead // 2 while i < k: j = 0 while j < 8: if chars[5 + i] & (1 << j): key = i * 8 + j + 1 break j += 1 i += 1 if key: # Routing key is pressed try: inputCore.manager.executeGesture(InputGestureRouting(key - 1)) except inputCore.NoInputGestureAction: log.debug("No Action for routing command") pass def _handleNormalKey(self, chars: bytes) -> None: keys = set() if chars[0] & 1: # LEFT keys.add("left") if chars[0] & 4: # RIGHT keys.add("right") if chars[1] & 2: # B1 keys.add("b1") if chars[1] & 8: keys.add("b2") if chars[1] & 16: keys.add("b3") if chars[0] & 16: keys.add("b4") if chars[0] & 8: keys.add("b5") if chars[0] & 2: keys.add("b6") data = "+".join(keys) try: inputCore.manager.executeGesture(InputGestureKeys(data)) except inputCore.NoInputGestureAction: log.debug(f"No Action for keys {data}") pass gestureMap = inputCore.GlobalGestureMap({ "globalCommands.GlobalCommands": { "braille_scrollBack": ("br(seika):left",), "braille_scrollForward": ("br(seika):right",), "braille_previousLine": ("br(seika):b3",), "braille_nextLine": ("br(seika):b4",), "braille_toggleTether": ("br(seika):b5",), "sayAll": ("br(seika):b6",), "kb:tab": ("br(seika):b1",), "kb:shift+tab": ("br(seika):b2",), "kb:alt+tab": ("br(seika):b1+b2",), "showGui": ("br(seika):left+right",), "braille_routeTo": ("br(seika):routing",), }, })