/* * BRLTTY - A background process providing access to the console screen (when in * text mode) for a blind person using a refreshable braille display. * * Copyright (C) 1995-2023 by The BRLTTY Developers. * * BRLTTY comes with ABSOLUTELY NO WARRANTY. * * This is free software, placed under the terms of the * GNU Lesser General Public License, as published by the Free Software * Foundation; either version 2.1 of the License, or (at your option) any * later version. Please see the file LICENSE-LGPL for details. * * Web Page: http://brltty.app/ * * This software is maintained by Dave Mielke . */ /* TSI/braille.c - Braille display driver for TSI displays * * Written by Stéphane Doyon (s.doyon@videotron.ca) * * It attempts full support for Navigator 20/40/80 and Powerbraille 40/65/80. * It is designed to be compiled into BRLTTY version 3.5. * * History: * Version 2.74 apr2004: use message() to report low battery condition. * Version 2.73 jan2004: Fix key bindings for speech commands for PB80. * Add CMD_SPKHOME to help. * Version 2.72 jan2003: brl->buffer now allocated by core. * Version 2.71: Added CMD_LEARN, BRL_CMD_NXPROMPT/CMD_PRPROMPT and CMD_SIXDOTS. * Version 2.70: Added CR_CUTAPPEND, BRL_BLK_CUTLINE, BRL_BLK_SETMARK, BRL_BLK_GOTOMARK * and CR_SETLEFT. Changed binding for NXSEARCH.. Adjusted PB80 cut&paste * bindings. Replaced CMD_CUT_BEG/CMD_CUT_END by CR_CUTBEGIN/CR_CUTRECT, * and CMD_CSRJMP by CR_ROUTE+0. Adjusted cut_cursor for new cut&paste * bindings (untested). * Version 2.61: Adjusted key bindings for preferences menu. * Version 2.60: Use TCSADRAIN when closing serial port. Slight API and * name changes for BRLTTY 3.0. Argument to readbrl now ignore, instead * of being validated. * Version 2.59: Added bindings for CMD_LNBEG/LNEND. * Version 2.58: Added bindings for CMD_BACK and CR_MSGATTRIB. * Version 2.57: Fixed help screen/file for Nav80. We finally have a * user who confirms it works! * Version 2.56: Added key binding for NXSEARCH. * Version 2.55: Added key binding for NXINDENT and NXBLNKLNS. * Version 2.54: Added key binding for switchvt. * Version 2.53: The IXOFF bit in the termios setting was inverted? * Version 2.52: Changed LOG_NOTICE to LOG_INFO. Was too noisy. * Version 2.51: Added CMD_RESTARTSPEECH. * Version 2.5: Added CMD_SPKHOME, sacrificed LNBEG and LNEND. * Version 2.4: Refresh display even if unchanged after every now and then so * that it will clear up if it was garbled. Added speech key bindings (had * to change a few bindings to make room). Added SKPEOLBLNK key binding. * Version 2.3: Reset serial port attributes at each detection attempt in * initbrl. This should help BRLTTY recover if another application (such * as kudzu) scrambles the serial port while BRLTTY is running. * Unnumbered version: Fixes for dynmically loading drivers (declare all * non-exported functions and variables static). * Version 2.2beta3: Option to disable CTS checking. Apparently, Vario * does not raise CTS when connected. * Version 2.2beta1: Exploring problems with emulators of TSI (PB40): BAUM * and mdv mb408s. See if we can provide timing options for more flexibility. * Version 2.1: Help screen fix for new keys in preferences menu. * Version 2.1beta1: Less delays in writing braille to display for * nav20/40 and pb40, delays still necessary for pb80 on probably for nav80. * Additional routing keys for navigator. Cut&paste binding that combines * routing key and normal key. * Version 2.0: Tested with Nav40 PB40 PB80. Support for functions added * in BRLTTY 2.0: added key bindings for new fonctions (attributes and * routing). Support for PB at 19200baud. Live detection of display, checks * both at 9600 and 19200baud. RS232 wire monitoring. Ping when idle to * detect when display turned off and issue a CMD_RESTARTBRL. * Version 1.2 (not released) introduces support for PB65/80. Rework of key * binding mechanism and readbrl(). Slight modifications to routing keys * support, + corrections. May have broken routing key support for PB40. * Version 1.1 worked on nav40 and was reported to work on pb40. */ #include "prologue.h" #include #include #include #include "log.h" #include "parse.h" #include "io_generic.h" #include "message.h" typedef enum { PARM_HIGH_BAUD, PARM_SET_BAUD } DriverParameter; #define BRLPARMS "highbaud", "setbaud" #include "brl_driver.h" #include "braille.h" #include "brldefs-ts.h" BEGIN_KEY_NAME_TABLE(routing) KEY_GROUP_ENTRY(TS_GRP_RoutingKeys, "RoutingKey"), END_KEY_NAME_TABLE BEGIN_KEY_NAME_TABLE(nav_small) KEY_NAME_ENTRY(TS_KEY_CursorLeft, "CursorLeft"), KEY_NAME_ENTRY(TS_KEY_CursorRight, "CursorRight"), KEY_NAME_ENTRY(TS_KEY_CursorUp, "CursorUp"), KEY_NAME_ENTRY(TS_KEY_CursorDown, "CursorDown"), KEY_NAME_ENTRY(TS_KEY_NavLeft, "NavLeft"), KEY_NAME_ENTRY(TS_KEY_NavRight, "NavRight"), KEY_NAME_ENTRY(TS_KEY_NavUp, "NavUp"), KEY_NAME_ENTRY(TS_KEY_NavDown, "NavDown"), KEY_NAME_ENTRY(TS_KEY_ThumbLeft, "ThumbLeft"), KEY_NAME_ENTRY(TS_KEY_ThumbRight, "ThumbRight"), END_KEY_NAME_TABLE BEGIN_KEY_NAME_TABLE(nav_large) KEY_NAME_ENTRY(TS_KEY_CursorLeft, "CursorLeft"), KEY_NAME_ENTRY(TS_KEY_CursorRight, "CursorRight"), KEY_NAME_ENTRY(TS_KEY_CursorUp, "CursorUp"), KEY_NAME_ENTRY(TS_KEY_CursorDown, "CursorDown"), KEY_NAME_ENTRY(TS_KEY_NavLeft, "LeftOuter"), KEY_NAME_ENTRY(TS_KEY_NavRight, "RightOuter"), KEY_NAME_ENTRY(TS_KEY_NavUp, "LeftInner"), KEY_NAME_ENTRY(TS_KEY_NavDown, "RightInner"), KEY_NAME_ENTRY(TS_KEY_ThumbLeft, "LeftThumb"), KEY_NAME_ENTRY(TS_KEY_ThumbRight, "RightThumb"), END_KEY_NAME_TABLE BEGIN_KEY_NAME_TABLE(pb_small) KEY_NAME_ENTRY(TS_KEY_CursorUp, "LeftRockerUp"), KEY_NAME_ENTRY(TS_KEY_CursorDown, "LeftRockerDown"), KEY_NAME_ENTRY(TS_KEY_NavLeft, "Backward"), KEY_NAME_ENTRY(TS_KEY_NavRight, "Forward"), KEY_NAME_ENTRY(TS_KEY_NavUp, "RightRockerUp"), KEY_NAME_ENTRY(TS_KEY_NavDown, "RightRockerDown"), KEY_NAME_ENTRY(TS_KEY_ThumbLeft, "Convex"), KEY_NAME_ENTRY(TS_KEY_ThumbRight, "Concave"), END_KEY_NAME_TABLE BEGIN_KEY_NAME_TABLE(pb_large) KEY_NAME_ENTRY(TS_KEY_Button1, "Button1"), KEY_NAME_ENTRY(TS_KEY_Button2, "Button2"), KEY_NAME_ENTRY(TS_KEY_Button3, "Button3"), KEY_NAME_ENTRY(TS_KEY_Button4, "Button4"), KEY_NAME_ENTRY(TS_KEY_Bar1, "Bar1"), KEY_NAME_ENTRY(TS_KEY_Bar2, "Bar2"), KEY_NAME_ENTRY(TS_KEY_Bar3, "Bar3"), KEY_NAME_ENTRY(TS_KEY_Bar4, "Bar4"), KEY_NAME_ENTRY(TS_KEY_Switch1Up, "Switch1Up"), KEY_NAME_ENTRY(TS_KEY_Switch1Down, "Switch1Down"), KEY_NAME_ENTRY(TS_KEY_Switch2Up, "Switch2Up"), KEY_NAME_ENTRY(TS_KEY_Switch2Down, "Switch2Down"), KEY_NAME_ENTRY(TS_KEY_Switch3Up, "Switch3Up"), KEY_NAME_ENTRY(TS_KEY_Switch3Down, "Switch3Down"), KEY_NAME_ENTRY(TS_KEY_Switch4Up, "Switch4Up"), KEY_NAME_ENTRY(TS_KEY_Switch4Down, "Switch4Down"), KEY_NAME_ENTRY(TS_KEY_LeftRockerUp, "LeftRockerUp"), KEY_NAME_ENTRY(TS_KEY_LeftRockerDown, "LeftRockerDown"), KEY_NAME_ENTRY(TS_KEY_RightRockerUp, "RightRockerUp"), KEY_NAME_ENTRY(TS_KEY_RightRockerDown, "RightRockerDown"), KEY_NAME_ENTRY(TS_KEY_Convex, "Convex"), KEY_NAME_ENTRY(TS_KEY_Concave, "Concave"), END_KEY_NAME_TABLE BEGIN_KEY_NAME_TABLES(nav20) KEY_NAME_TABLE(nav_small), END_KEY_NAME_TABLES BEGIN_KEY_NAME_TABLES(nav40) KEY_NAME_TABLE(nav_small), END_KEY_NAME_TABLES BEGIN_KEY_NAME_TABLES(nav80) KEY_NAME_TABLE(nav_large), KEY_NAME_TABLE(routing), END_KEY_NAME_TABLES BEGIN_KEY_NAME_TABLES(pb40) KEY_NAME_TABLE(pb_small), KEY_NAME_TABLE(routing), END_KEY_NAME_TABLES BEGIN_KEY_NAME_TABLES(pb65) KEY_NAME_TABLE(pb_large), KEY_NAME_TABLE(routing), END_KEY_NAME_TABLES BEGIN_KEY_NAME_TABLES(pb80) KEY_NAME_TABLE(pb_large), KEY_NAME_TABLE(routing), END_KEY_NAME_TABLES DEFINE_KEY_TABLE(nav20) DEFINE_KEY_TABLE(nav40) DEFINE_KEY_TABLE(nav80) DEFINE_KEY_TABLE(pb40) DEFINE_KEY_TABLE(pb65) DEFINE_KEY_TABLE(pb80) BEGIN_KEY_TABLE_LIST &KEY_TABLE_DEFINITION(nav20), &KEY_TABLE_DEFINITION(nav40), &KEY_TABLE_DEFINITION(nav80), &KEY_TABLE_DEFINITION(pb40), &KEY_TABLE_DEFINITION(pb65), &KEY_TABLE_DEFINITION(pb80), END_KEY_TABLE_LIST /* Stabilization delay after changing baud rate */ #define BAUD_DELAY 100 /* for routing keys */ #define ROUTING_BYTES_VERTICAL 4 #define ROUTING_BYTES_MAXIMUM 11 #define ROUTING_BYTES_40 9 #define ROUTING_BYTES_80 14 #define ROUTING_BYTES_81 15 /* Description of reply to query */ #define IDENTITY_H1 0X00 #define IDENTITY_H2 0X05 /* Routing keys information (2 bytes header) */ #define ROUTING_H1 0x00 #define ROUTING_H2 0x08 /* input codes signaling low battery power (2bytes) */ #define BATTERY_H1 0x00 #define BATTERY_H2 0x01 /* Bit definition of key codes returned by the display. * Navigator and pb40 return 2 bytes, pb65/80 returns 6. Each byte has a * different specific mask/signature in the 3 most significant bits. * Other bits indicate whether a specific key is pressed. * See readbrl(). */ /* We combine all key bits into one KeyNumberSet. Each byte is masked by the * corresponding "mask" to extract valid bits then those are shifted by * "shift" and or'ed into the 32bits "code". */ /* bits to take into account when checking each byte's signature */ #define KEYS_BYTE_SIGNATURE_MASK 0XE0 /* how we describe each byte */ typedef struct { unsigned char signature; /* it's signature */ unsigned char mask; /* bits that do represent keys */ unsigned char shift; /* where to shift them into "code" */ } KeysByteDescriptor; /* Description of bytes for navigator and pb40. */ static const KeysByteDescriptor keysDescriptor_Navigator[] = { {.signature=0X60, .mask=0X1F, .shift=0}, {.signature=0XE0, .mask=0X1F, .shift=5} }; /* Description of bytes for pb65/80 */ static const KeysByteDescriptor keysDescriptor_PowerBraille[] = { {.signature=0X40, .mask=0X0F, .shift=10}, {.signature=0XC0, .mask=0X0F, .shift=14}, {.signature=0X20, .mask=0X05, .shift=18}, {.signature=0XA0, .mask=0X05, .shift=21}, {.signature=0X60, .mask=0X1F, .shift=24}, {.signature=0XE0, .mask=0X1F, .shift=5} }; typedef struct { const char *modelName; const KeyTableDefinition *keyTableDefinition; unsigned char routingBytes; signed char routingKeyCount; unsigned slowUpdate:2; unsigned highBaudSupported:1; } ModelEntry; static const ModelEntry modelNavigator20 = { .modelName = "Navigator 20", .routingBytes = ROUTING_BYTES_40, .routingKeyCount = 20, .keyTableDefinition = &KEY_TABLE_DEFINITION(nav20) }; static const ModelEntry modelNavigator40 = { .modelName = "Navigator 40", .routingBytes = ROUTING_BYTES_40, .routingKeyCount = 40, .slowUpdate = 1, .keyTableDefinition = &KEY_TABLE_DEFINITION(nav40) }; static const ModelEntry modelNavigator80 = { .modelName = "Navigator 80", .routingBytes = ROUTING_BYTES_80, .routingKeyCount = 80, .slowUpdate = 2, .keyTableDefinition = &KEY_TABLE_DEFINITION(nav80) }; static const ModelEntry modelPowerBraille40 = { .modelName = "Power Braille 40", .routingBytes = ROUTING_BYTES_40, .routingKeyCount = 40, .highBaudSupported = 1, .keyTableDefinition = &KEY_TABLE_DEFINITION(pb40) }; static const ModelEntry modelPowerBraille65 = { .modelName = "Power Braille 65", .routingBytes = ROUTING_BYTES_81, .routingKeyCount = 65, .slowUpdate = 2, .highBaudSupported = 1, .keyTableDefinition = &KEY_TABLE_DEFINITION(pb65) }; static const ModelEntry modelPowerBraille80 = { .modelName = "Power Braille 80", .routingBytes = ROUTING_BYTES_81, .routingKeyCount = 81, .slowUpdate = 2, .highBaudSupported = 1, .keyTableDefinition = &KEY_TABLE_DEFINITION(pb80) }; typedef enum { IPT_IDENTITY, IPT_ROUTING, IPT_BATTERY, IPT_KEYS } InputPacketType; typedef struct { union { unsigned char bytes[1]; struct { unsigned char header[2]; unsigned char columns; unsigned char dots; char version[4]; unsigned char checksum[4]; } identity; struct { unsigned char header[2]; unsigned char count; unsigned char vertical[ROUTING_BYTES_VERTICAL]; unsigned char horizontal[0X100 - 4]; } routing; unsigned char keys[6]; } fields; InputPacketType type; union { struct { unsigned char count; } routing; struct { const KeysByteDescriptor *descriptor; unsigned char count; } keys; } data; } InputPacket; struct BrailleDataStruct { const ModelEntry *model; SerialParameters serialParameters; unsigned char routingKeys[ROUTING_BYTES_MAXIMUM]; unsigned char forceWrite; unsigned char cellCount; unsigned char cells[0XFF]; struct { unsigned char major; unsigned char minor; } version; /* Type of delay the display requires after sending it a command. * 0 -> no delay, 1 -> drain only, 2 -> drain + wait for SEND_DELAY. */ unsigned char slowUpdate; }; static ssize_t writeBytes (BrailleDisplay *brl, const void *data, size_t size) { brl->writeDelay += brl->data->slowUpdate * 24; return writeBraillePacket(brl, NULL, data, size); } static BraillePacketVerifierResult verifyPacket ( BrailleDisplay *brl, unsigned char *bytes, size_t size, size_t *length, void *data ) { InputPacket *packet = data; const off_t index = size - 1; const unsigned char byte = bytes[index]; if (size == 1) { switch (byte) { case IDENTITY_H1: packet->type = IPT_IDENTITY; *length = 2; break; default: if ((byte & KEYS_BYTE_SIGNATURE_MASK) == keysDescriptor_Navigator[0].signature) { packet->data.keys.descriptor = keysDescriptor_Navigator; packet->data.keys.count = ARRAY_COUNT(keysDescriptor_Navigator); goto isKeys; } if ((byte & KEYS_BYTE_SIGNATURE_MASK) == keysDescriptor_PowerBraille[0].signature) { packet->data.keys.descriptor = keysDescriptor_PowerBraille; packet->data.keys.count = ARRAY_COUNT(keysDescriptor_PowerBraille); goto isKeys; } return BRL_PVR_INVALID; isKeys: packet->type = IPT_KEYS; *length = packet->data.keys.count; break; } } else { switch (packet->type) { case IPT_IDENTITY: if (size == 2) { switch (byte) { case IDENTITY_H2: *length = sizeof(packet->fields.identity); break; case ROUTING_H2: packet->type = IPT_ROUTING; *length = 3; break; case BATTERY_H2: packet->type = IPT_BATTERY; break; default: return BRL_PVR_INVALID; } } break; case IPT_ROUTING: if (size == 3) { packet->data.routing.count = byte; *length += packet->data.routing.count; } break; case IPT_KEYS: if ((byte & KEYS_BYTE_SIGNATURE_MASK) != packet->data.keys.descriptor[index].signature) return BRL_PVR_INVALID; break; default: break; } } return BRL_PVR_INCLUDE; } static size_t readPacket (BrailleDisplay *brl, InputPacket *packet) { return readBraillePacket(brl, NULL, &packet->fields, sizeof(packet->fields), verifyPacket, packet); } static int getIdentity (BrailleDisplay *brl, InputPacket *reply) { static const unsigned char request[] = {0xFF, 0xFF, 0x0A}; if (writeBytes(brl, request, sizeof(request))) { if (awaitBrailleInput(brl, 100)) { size_t count = readPacket(brl, reply); if (count > 0) { if (reply->type == IPT_IDENTITY) return 1; logUnexpectedPacket(reply->fields.bytes, count); } } else { logMessage(LOG_DEBUG, "no response"); } } return 0; } static int setAutorepeatProperties (BrailleDisplay *brl, int on, int delay, int interval) { const unsigned char request[] = { 0XFF, 0XFF, 0X0D, on? ((delay + 9) / 10) /* 10ms */: 0XFF /* long delay */, on? ((interval + 9) / 10) /* 10ms */: 0XFF /* long interval */ }; return writeBytes(brl, request, sizeof(request)); } static int setLocalBaud (BrailleDisplay *brl, unsigned int baud) { SerialParameters *parameters = &brl->data->serialParameters; logMessage(LOG_DEBUG, "trying at %u baud", baud); if (parameters->baud == baud) return 1; parameters->baud = baud; return gioReconfigureResource(brl->gioEndpoint, parameters); } static int setRemoteBaud (BrailleDisplay *brl, unsigned int baud) { unsigned char request[] = {0xFF, 0xFF, 0x05, 0}; unsigned char *byte = &request[sizeof(request) - 1]; switch (baud) { case TS_BAUD_LOW: *byte = 2; break; case TS_BAUD_NORMAL: *byte = 3; break; case TS_BAUD_HIGH: *byte = 4; break; default: logMessage(LOG_WARNING, "display does not support %u baud", baud); return 0; } logMessage(LOG_WARNING, "switching display to %u baud", baud); return writeBraillePacket(brl, NULL, request, sizeof(request)); } static int connectResource (BrailleDisplay *brl, const char *identifier) { static const SerialParameters serialParameters = { SERIAL_DEFAULT_PARAMETERS }; GioDescriptor descriptor; gioInitializeDescriptor(&descriptor); descriptor.serial.parameters = &serialParameters; if (connectBrailleResource(brl, identifier, &descriptor, NULL)) { brl->data->serialParameters = serialParameters; return 1; } return 0; } static void disconnectResource (BrailleDisplay *brl) { disconnectBrailleResource(brl, NULL); } static int brl_construct (BrailleDisplay *brl, char **parameters, const char *device) { if ((brl->data = malloc(sizeof(*brl->data)))) { memset(brl->data, 0, sizeof(*brl->data)); if (connectResource(brl, device)) { InputPacket reply; unsigned int allowHighBaud = 1; unsigned int oldBaud; unsigned int newBaud; { const char *parameter = parameters[PARM_HIGH_BAUD]; if (parameter && *parameter) { if (!validateYesNo(&allowHighBaud, parameter)) { logMessage(LOG_WARNING, "unsupported high baud setting: %s", parameter); } } } { static const unsigned int bauds[] = { TS_BAUD_NORMAL, TS_BAUD_HIGH, 0 }; const unsigned int *baud = bauds; while (1) { if (!*baud) goto failure; oldBaud = *baud++; if (allowHighBaud || (oldBaud <= TS_BAUD_NORMAL)) { if (setLocalBaud(brl, oldBaud)) { if (getIdentity(brl, &reply)) { break; } } } } } brl->data->cellCount = reply.fields.identity.columns; brl->data->version.major = reply.fields.identity.version[1] - '0'; brl->data->version.minor = reply.fields.identity.version[3] - '0'; logMessage(LOG_INFO, "display replied: %u cells, version %u.%u", brl->data->cellCount, brl->data->version.major, brl->data->version.minor); switch (brl->data->cellCount) { case 20: brl->data->model = &modelNavigator20; break; case 40: brl->data->model = (brl->data->version.major > 3)? &modelPowerBraille40: &modelNavigator40; break; case 80: brl->data->model = &modelNavigator80; break; case 65: brl->data->model = &modelPowerBraille65; break; case 81: brl->data->model = &modelPowerBraille80; break; default: logMessage(LOG_ERR, "unrecognized braille display size: %u", brl->data->cellCount); goto failure; } logMessage(LOG_INFO, "detected %s", brl->data->model->modelName); brl->data->slowUpdate = brl->data->model->slowUpdate; #ifdef FORCE_DRAIN_AFTER_SEND brl->data->slowUpdate = 1; #endif /* FORCE_DRAIN_AFTER_SEND */ #ifdef FORCE_FULL_SEND_DELAY brl->data->slowUpdate = 2; #endif /* FORCE_FULL_SEND_DELAY */ newBaud = oldBaud; if (allowHighBaud && brl->data->model->highBaudSupported) newBaud = TS_BAUD_HIGH; { const char *parameter = parameters[PARM_SET_BAUD]; if (parameter && *parameter) { static const int minimum = 1; int value; if (validateInteger(&value, parameter, &minimum, NULL)) { newBaud = value; } else { logMessage(LOG_WARNING, "unsupported set baud setting: %s", parameter); } } } if (newBaud != oldBaud) { if (!setRemoteBaud(brl, newBaud)) goto failure; drainBrailleOutput(brl, BAUD_DELAY); if (!setLocalBaud(brl, newBaud)) goto failure; logMessage(LOG_DEBUG, "now using %u baud - checking if display followed", newBaud); if (getIdentity(brl, &reply)) { logMessage(LOG_DEBUG, "display responded at %u baud", newBaud); } else { logMessage(LOG_INFO, "display did not respond at %u baud" " - going back to %u baud", newBaud, oldBaud); if (!setLocalBaud(brl, oldBaud)) goto failure; drainBrailleOutput(brl, BAUD_DELAY); if (getIdentity(brl, &reply)) { logMessage(LOG_INFO, "found display again at %u baud", oldBaud); } else { logMessage(LOG_ERR, "display lost after baud switch"); goto failure; } } } setBrailleKeyTable(brl, brl->data->model->keyTableDefinition); makeOutputTable(dotsTable_ISO11548_1); brl->textColumns = brl->data->cellCount; /* initialise size of display */ brl->setAutorepeatProperties = setAutorepeatProperties; memset(brl->data->routingKeys, 0, sizeof(brl->data->routingKeys)); brl->data->forceWrite = 1; return 1; failure: disconnectResource(brl); } free(brl->data); } else { logMallocError(); } return 0; } static void brl_destruct (BrailleDisplay *brl) { disconnectResource(brl); if (brl->data) { free(brl->data); brl->data = NULL; } } static int writeCells (BrailleDisplay *brl, unsigned int from, unsigned int to) { static const unsigned char header[] = { 0XFF, 0XFF, 0X04, 0X00, 0X99, 0X00 }; unsigned int length = to - from; unsigned char packet[sizeof(header) + 2 + (length * 2)]; unsigned char *byte = packet; unsigned int i; byte = mempcpy(byte, header, sizeof(header)); *byte++ = 2 * length; *byte++ = from; for (i=0; idata->cells[from + i]); } /* Some displays apparently don't like rapid updating. Most or all apprently * don't do flow control. If we update the display too often and too fast, * then the packets queue up in the send queue, the info displayed is not up * to date, and the info displayed continues to change after we stop * updating while the queue empties (like when you release the arrow key and * the display continues changing for a second or two). We also risk * overflows which put garbage on the display, or often what happens is that * some cells from previously displayed lines will remain and not be cleared * or replaced; also the pinging fails and the display gets * reinitialized... To expose the problem skim/scroll through a long file * (with long lines) holding down the up/down arrow key on the PC keyboard. * * pb40 has no problems: it apparently can take whatever we throw at * it. Nav40 is good but we drain just to be safe. * * pb80 (twice larger but twice as fast as nav40) cannot take a continuous * full speed flow. There is no flow control: apparently not supported * properly on at least pb80. My pb80 is recent yet the hardware version is * v1.0a, so this may be a hardware problem that was fixed on pb40. There's * some UART handshake mode that might be relevant but only seems to break * everything (on both pb40 and pb80)... * * Nav80 is untested but as it receives at 9600, we probably need to * compensate there too. * * Finally, some TSI emulators (at least the mdv mb408s) may have timing * limitations. * * I no longer have access to a Nav40 and PB80 for testing: I only have a * PB40. */ return writeBytes(brl, packet, (byte - packet)); } static int brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) { unsigned int from, to; if (cellsHaveChanged(brl->data->cells, brl->buffer, brl->data->cellCount, &from, &to, &brl->data->forceWrite)) { if (!writeCells(brl, from, to)) return 0; } return 1; } static int handleInputPacket (BrailleDisplay *brl, const InputPacket *packet) { switch (packet->type) { case IPT_KEYS: { KeyNumberSet keys = 0; unsigned int i; for (i=0; idata.keys.count; i+=1) { const KeysByteDescriptor *kbd = &packet->data.keys.descriptor[i]; keys |= (packet->fields.keys[i] & kbd->mask) << kbd->shift; } enqueueKeys(brl, keys, TS_GRP_NavigationKeys, 0); return 1; } case IPT_ROUTING: { if (packet->data.routing.count != brl->data->model->routingBytes) return 0; enqueueUpdatedKeyGroup(brl, brl->data->model->routingKeyCount, packet->fields.routing.horizontal, brl->data->routingKeys, TS_GRP_RoutingKeys); return 1; } case IPT_BATTERY: message(NULL, gettext("battery low"), 0); return 1; default: return 0; } } static int brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) { /* Key press codes come in pairs of bytes for nav and pb40, in 6bytes * for pb65/80. Each byte has bits representing individual keys + a special * mask/signature in the most significant 3bits. * * The low battery warning from the display is a specific 2bytes code. * * Finally, the routing keys have a special 2bytes header followed by 9, 14 * or 15 bytes of info (1bit for each routing key). The first 4bytes describe * vertical routing keys and are ignored in this driver. * * We might get a query reply, since we send queries when we don't get * any keys in a certain time. That a 2byte header + 10 more bytes ignored. */ InputPacket packet; size_t size; while ((size = readPacket(brl, &packet))) { if (!handleInputPacket(brl, &packet)) { logUnexpectedPacket(packet.fields.bytes, size); } } return (errno == EAGAIN)? EOF: BRL_CMD_RESTARTBRL; }