|
| 1 | +# A part of NonVisual Desktop Access (NVDA) |
| 2 | +# Copyright (C) 2012-2020 NV Access Limited, Ulf Beckmann <beckmann@flusoft.de> |
| 3 | +# This file may be used under the terms of the GNU General Public License, version 2 or later. |
| 4 | +# For more details see: https://www.gnu.org/licenses/gpl-2.0.html |
| 5 | + |
| 6 | +# This file represents the braille display driver for |
| 7 | +# Seika Mini, a product from Nippon Telesoft |
| 8 | +# see www.seika-braille.com for more details |
| 9 | +# 09.06.2012 |
| 10 | +# Dec14/Jan15 add BrilleInput |
| 11 | + |
| 12 | +# 02.10.2020 |
| 13 | +# Update by using Python 3. |
| 14 | + |
| 15 | +from typing import Optional, List |
| 16 | +from ctypes import * |
| 17 | +import time |
| 18 | +import wx |
| 19 | +import braille |
| 20 | +import brailleInput |
| 21 | +import inputCore |
| 22 | +import hwPortUtils |
| 23 | +import winUser |
| 24 | +import os |
| 25 | +from logHandler import log |
| 26 | + |
| 27 | +READ_INTERVAL = 50 |
| 28 | + |
| 29 | +DOT_1 = 0x1 |
| 30 | +DOT_2 = 0x2 |
| 31 | +DOT_3 = 0x4 |
| 32 | +DOT_4 = 0x8 |
| 33 | +DOT_5 = 0x10 |
| 34 | +DOT_6 = 0x20 |
| 35 | +DOT_7 = 0x40 |
| 36 | +DOT_8 = 0x80 |
| 37 | + |
| 38 | + |
| 39 | +_keyNames = { |
| 40 | + 0x000001: "BACKSPACE", |
| 41 | + 0x000002: "SPACE", |
| 42 | + 0x000004: "LB", |
| 43 | + 0x000008: "RB", |
| 44 | + 0x000010: "LJ_CENTER", |
| 45 | + 0x000020: "LJ_LEFT", |
| 46 | + 0x000040: "LJ_RIGHT", |
| 47 | + 0x000080: "LJ_UP", |
| 48 | + 0x000100: "LJ_DOWN", |
| 49 | + 0x000200: "RJ_CENTER", |
| 50 | + 0x000400: "RJ_LEFT", |
| 51 | + 0x000800: "RJ_RIGHT", |
| 52 | + 0x001000: "RJ_UP", |
| 53 | + 0x002000: "RJ_DOWN" |
| 54 | +} |
| 55 | + |
| 56 | +_dotNames = {} |
| 57 | +for i in range(1, 9): |
| 58 | + key = globals()["DOT_%d" % i] |
| 59 | + _dotNames[key] = "d%d" % i |
| 60 | + |
| 61 | + |
| 62 | +# try to load the SeikaDevice.dll |
| 63 | +# first get the path of the SeikaMini.py |
| 64 | +# get the current work directory |
| 65 | +# change to this directory |
| 66 | +# load the .dll, the SeikaDevice.dll can also load by Pathname + dllname |
| 67 | +# but not the SLABxxx.dll's |
| 68 | +# after loading, set the path back to the work path |
| 69 | + |
| 70 | +BASE_PATH = os.path.dirname(__file__) |
| 71 | +DLLNAME = "SeikaDevice.dll" |
| 72 | +WORK_PATH = os.getcwd() |
| 73 | + |
| 74 | +if not os.path.isfile(BASE_PATH + "\\" + DLLNAME): |
| 75 | + BASE_PATH = "brailleDisplayDrivers" |
| 76 | +os.chdir(BASE_PATH) |
| 77 | +try: |
| 78 | + seikaDll = cdll.LoadLibrary(DLLNAME) |
| 79 | +except: |
| 80 | + seikaDll = None |
| 81 | + log.info("LoadLibrary failed " + DLLNAME) |
| 82 | +os.chdir(WORK_PATH) |
| 83 | + |
| 84 | + |
| 85 | +class BrailleDisplayDriver(braille.BrailleDisplayDriver): |
| 86 | + name = "seikamini" |
| 87 | + description = "Seika Notetaker" |
| 88 | + |
| 89 | + numCells = 0 |
| 90 | + # numBtns = 0 |
| 91 | + |
| 92 | + @classmethod |
| 93 | + def check(cls): |
| 94 | + return bool(seikaDll) |
| 95 | + |
| 96 | + def seika_errcheck(res, func, args): |
| 97 | + if res != 0: |
| 98 | + raise RuntimeError("seikamini: %s: code %d" % (func.__name__, res)) |
| 99 | + return res |
| 100 | + |
| 101 | + def __init__(self): |
| 102 | + super(BrailleDisplayDriver, self).__init__() |
| 103 | + pint = c_int * 1 |
| 104 | + nCells = pint(0) |
| 105 | + nBut = pint(0) |
| 106 | + pN = "" |
| 107 | + |
| 108 | + # seikaDll.BrailleOpen.errcheck=self.seika_errcheck |
| 109 | + seikaDll.BrailleOpen.restype = c_int |
| 110 | + seikaDll.BrailleOpen.argtype = (c_int, c_int) |
| 111 | + |
| 112 | + # seikaDll.GetBrailleDisplayInfo.errcheck=self.seika_errcheck |
| 113 | + seikaDll.GetBrailleDisplayInfo.restype = c_int |
| 114 | + seikaDll.GetBrailleDisplayInfo.argtype = (c_void_p, c_void_p) |
| 115 | + |
| 116 | + # seikaDll.UpdateBrailleDisplay.errcheck=self.seika_errcheck |
| 117 | + seikaDll.UpdateBrailleDisplay.restype = c_int |
| 118 | + seikaDll.UpdateBrailleDisplay.argtype = (POINTER(c_ubyte), c_int) |
| 119 | + |
| 120 | + # seikaDll.GetBrailleKey.errcheck=self.seika_errcheck |
| 121 | + seikaDll.GetBrailleKey.restype = c_int |
| 122 | + seikaDll.GetBrailleKey.argtype = (c_void_p, c_void_p) |
| 123 | + |
| 124 | + seikaDll.BrailleClose.restype = c_int |
| 125 | + |
| 126 | + if seikaDll.BrailleOpen(2, 0): # test USB |
| 127 | + seikaDll.GetBrailleDisplayInfo(nCells, nBut) |
| 128 | + log.info("seikamini an USB-HID, Cells {c} Buttons {b}".format(c=nCells[0], b=nBut[0])) |
| 129 | + self.numCells = nCells[0] |
| 130 | + # |
| 131 | + else: # search the blutooth ports |
| 132 | + for portInfo in sorted(hwPortUtils.listComPorts(onlyAvailable=True), key=lambda item: "bluetoothName" in item): |
| 133 | + port = portInfo["port"] |
| 134 | + hwID = portInfo["hardwareID"] |
| 135 | + if not hwID.startswith(r"BTHENUM"): # Bluetooth Ports |
| 136 | + continue |
| 137 | + bName = "" |
| 138 | + try: |
| 139 | + bName = portInfo["bluetoothName"] |
| 140 | + except KeyError: |
| 141 | + continue |
| 142 | + if not bName.startswith(r"TSM"): # seikamini and then the 4-Digits |
| 143 | + continue |
| 144 | + |
| 145 | + try: |
| 146 | + pN = port.split("COM")[1] |
| 147 | + except IndexError: |
| 148 | + pN = "0" |
| 149 | + portNum = int(pN, 10) |
| 150 | + log.info("seikamini test {c}, {b}".format(c=port, b=bName)) |
| 151 | + if seikaDll.BrailleOpen(0, portNum): |
| 152 | + seikaDll.GetBrailleDisplayInfo(nCells, nBut) |
| 153 | + log.info("seikamini via Bluetooth {p} Cells {c} Buttons {b}".format(p=port, c=nCells[0], b=nBut[0])) |
| 154 | + self.numCells = nCells[0] |
| 155 | + # self.numBtns=nBut[0] |
| 156 | + break |
| 157 | + else: |
| 158 | + raise RuntimeError("No MINI-SEIKA display found") |
| 159 | + self._readTimer = wx.PyTimer(self.handleResponses) |
| 160 | + self._readTimer.Start(READ_INTERVAL) |
| 161 | + |
| 162 | + def terminate(self): |
| 163 | + try: |
| 164 | + super(BrailleDisplayDriver, self).terminate() |
| 165 | + self._readTimer.Stop() |
| 166 | + self._readTimer = None |
| 167 | + finally: |
| 168 | + seikaDll.BrailleClose() |
| 169 | + |
| 170 | + def display(self, cells: List[int]): |
| 171 | + # cells will already be padded up to numCells. |
| 172 | + cellBytes = bytes(cells) |
| 173 | + seikaDll.UpdateBrailleDisplay(cellBytes, self.numCells) |
| 174 | + |
| 175 | + def handleResponses(self): |
| 176 | + pint = c_int * 1 |
| 177 | + nKey = pint(0) |
| 178 | + nRou = pint(0) |
| 179 | + Key = 0 |
| 180 | + Brl = 0 |
| 181 | + Rou = 0 |
| 182 | + Btn = 0 |
| 183 | + keys = set() |
| 184 | + if seikaDll.GetBrailleKey(nKey, nRou): |
| 185 | + Rou = nRou[0] |
| 186 | + Btn = (nKey[0] & 0xff) << 16 |
| 187 | + Brl = (nKey[0] >> 8) & 0xff |
| 188 | + Key = (nKey[0] >> 16) & 0xffff |
| 189 | + space = (nKey[0] >> 16) & 0x2 |
| 190 | + log.info("GBK Key{c}-Brl{a}-Routing{b}".format(c=Key, b=Rou, a=Brl)) |
| 191 | + # log.info("Seika Brl {brl} Key {c} Buttons {b} Route {r}".format(brl=Brl, c=Key, b=Btn, r=Rou)) |
| 192 | + if not (Rou or Key or Btn or Brl): |
| 193 | + pass |
| 194 | + if Rou: # Routing key is pressed |
| 195 | + gesture = InputGestureRouting(Rou - 1) |
| 196 | + try: |
| 197 | + inputCore.manager.executeGesture(gesture) |
| 198 | + except inputCore.NoInputGestureAction: |
| 199 | + log.debug("No Action for routing command") |
| 200 | + pass |
| 201 | + |
| 202 | + if Key: # Mini Seika has 2 Top and 4 Front .... |
| 203 | + gesture = InputGesture(keys = Key) |
| 204 | + if Btn: # Mini Seika has no Btn .... |
| 205 | + gesture = InputGesture(keys = Btn) |
| 206 | + if Brl: # or how to handle Brailleinput? |
| 207 | + gesture = InputGesture(dots = Brl) |
| 208 | + if Key or Btn or Brl: |
| 209 | + try: |
| 210 | + inputCore.manager.executeGesture(gesture) |
| 211 | + except inputCore.NoInputGestureAction: |
| 212 | + log.debug("No Action for keys ") |
| 213 | + pass |
| 214 | + |
| 215 | + gestureMap = inputCore.GlobalGestureMap({ |
| 216 | + "globalCommands.GlobalCommands": { |
| 217 | + "braille_routeTo": ("br(seikamini):routing",), |
| 218 | + "braille_scrollBack": ("br(seikamini):LB",), |
| 219 | + "braille_scrollForward": ("br(seikamini):RB",), |
| 220 | + "braille_previousLine": ("br(seikamini):LJ_UP",), |
| 221 | + "braille_nextLine": ("br(seikamini):LJ_DOWN",), |
| 222 | + "braille_toggleTether": ("br(seikamini):LJ_CENTER",), |
| 223 | + "sayAll": ("br(seikamini):SPACE+BACKSPACE",), |
| 224 | + "showGui": ("br(seikamini):RB+LB",), |
| 225 | + "kb:tab": ("br(seikamini):LJ_RIGHT",), |
| 226 | + "kb:shift+tab": ("br(seikamini):LJ_LEFT",), |
| 227 | + "kb:upArrow": ("br(seikamini):RJ_UP",), |
| 228 | + "kb:downArrow": ("br(seikamini):RJ_DOWN",), |
| 229 | + "kb:leftArrow": ("br(seikamini):RJ_LEFT",), |
| 230 | + "kb:rightArrow": ("br(seikamini):RJ_RIGHT",), |
| 231 | + "kb:shift+upArrow": ("br(seikamini):SPACE+RJ_UP",), |
| 232 | + "kb:shift+downArrow": ("br(seikamini):SPACE+RJ_DOWN",), |
| 233 | + "kb:shift+leftArrow": ("br(seikamini):SPACE+RJ_LEFT",), |
| 234 | + "kb:shift+rightArrow": ("br(seikamini):SPACE+RJ_RIGHT",), |
| 235 | + "kb:escape": ("br(seikamini):SPACE+RJ_CENTER",), |
| 236 | + "kb:shift+upArrow": ("br(seikamini):BACKSPACE+RJ_UP",), |
| 237 | + "kb:shift+downArrow": ("br(seikamini):BACKSPACE+RJ_DOWN",), |
| 238 | + "kb:shift+leftArrow": ("br(seikamini):BACKSPACE+RJ_LEFT",), |
| 239 | + "kb:shift+rightArrow": ("br(seikamini):BACKSPACE+RJ_RIGHT",), |
| 240 | + "kb:windows": ("br(seikamini):BACKSPACE+RJ_CENTER",), |
| 241 | + "kb:space": ("br(seikamini):BACKSPACE", "br(seikamini):SPACE",), |
| 242 | + "kb:backspace": ("br(seikamini):d7",), |
| 243 | + "kb:pageup": ("br(seikamini):SPACE+LJ_RIGHT",), |
| 244 | + "kb:pagedown": ("br(seikamini):SPACE+LJ_LEFT",), |
| 245 | + "kb:home": ("br(seikamini):SPACE+LJ_UP",), |
| 246 | + "kb:end": ("br(seikamini):SPACE+LJ_DOWN",), |
| 247 | + "kb:control+home": ("br(seikamini):BACKSPACE+LJ_UP",), |
| 248 | + "kb:control+end": ("br(seikamini):BACKSPACE+LJ_DOWN",), |
| 249 | + "kb:enter": ("br(seikamini):RJ_CENTER", "br(seikamini):d8"), |
| 250 | + }, |
| 251 | + }) |
| 252 | + |
| 253 | + |
| 254 | +class InputGestureRouting(braille.BrailleDisplayGesture): |
| 255 | + |
| 256 | + source = BrailleDisplayDriver.name |
| 257 | + |
| 258 | + def __init__(self, index): |
| 259 | + super(InputGestureRouting, self).__init__() |
| 260 | + self.id = "routing" |
| 261 | + self.routingIndex = index |
| 262 | + |
| 263 | + |
| 264 | +class InputGesture(braille.BrailleDisplayGesture, brailleInput.BrailleInputGesture): |
| 265 | + source = BrailleDisplayDriver.name |
| 266 | + |
| 267 | + def __init__(self, keys = None, dots = None, space = False, routing = None): |
| 268 | + super(braille.BrailleDisplayGesture, self).__init__() |
| 269 | + # see what thumb keys are pressed: |
| 270 | + names = set() |
| 271 | + if keys is not None: |
| 272 | + names.update(_keyNames[1 << i] for i in range(22) |
| 273 | + if (1 << i) & keys) |
| 274 | + elif dots is not None: |
| 275 | + # now the dots |
| 276 | + self.dots = dots |
| 277 | + if space: |
| 278 | + self.space = space |
| 279 | + names.add(_keyNames[0]) |
| 280 | + names.update(_dotNames[1 << i] for i in range(8) |
| 281 | + if (1 << i) & dots) |
| 282 | + elif routing is not None: |
| 283 | + self.routingIndex = routing |
| 284 | + names.add('routing') |
| 285 | + self.id = "+".join(names) |
| 286 | + # log.info("keys {keys}".format(keys=names)) |
0 commit comments