| /* |
| * 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 <dave@mielke.cc>. |
| */ |
| |
| #include "prologue.h" |
| |
| #include <stdio.h> |
| #include <string.h> |
| #include <strings.h> |
| #include <ctype.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| |
| #ifdef __MINGW32__ |
| #include <ws2tcpip.h> |
| #include "system_windows.h" |
| #else /* __MINGW32__ */ |
| #include <sys/socket.h> |
| #include <sys/un.h> |
| #include <netinet/in.h> |
| #include <arpa/inet.h> |
| #include <netdb.h> |
| #endif /* __MINGW32__ */ |
| #include "get_select.h" |
| |
| #if !defined(AF_LOCAL) && defined(AF_UNIX) |
| #define AF_LOCAL AF_UNIX |
| #endif /* !defined(AF_LOCAL) && defined(AF_UNIX) */ |
| |
| #if !defined(PF_LOCAL) && defined(PF_UNIX) |
| #define PF_LOCAL PF_UNIX |
| #endif /* !defined(PF_LOCAL) && defined(PF_UNIX) */ |
| |
| #ifdef WINDOWS |
| #undef AF_LOCAL |
| #endif /* WINDOWS */ |
| |
| #ifdef __MINGW32__ |
| #define close(fd) CloseHandle((HANDLE)(fd)) |
| #define LogSocketError(msg) logWindowsSocketError(msg) |
| #else /* __MINGW32__ */ |
| #define LogSocketError(msg) logSystemError(msg) |
| #endif /* __MINGW32__ */ |
| |
| #include "log.h" |
| #include "io_misc.h" |
| #include "parse.h" |
| #include "async_wait.h" |
| #include "charset.h" |
| #include "cmd.h" |
| |
| #define BRL_STATUS_FIELDS sfGeneric |
| #define BRL_HAVE_STATUS_CELLS |
| #include "brl_driver.h" |
| #include "braille.h" |
| |
| static int fileDescriptor = -1; |
| |
| #define INPUT_SIZE 0X200 |
| static char inputBuffer[INPUT_SIZE]; |
| static size_t inputLength; |
| static size_t inputStart; |
| static int inputEnd; |
| static int inputCarriageReturn; |
| static const char *inputDelimiters = " "; |
| |
| #define OUTPUT_SIZE 0X200 |
| static char outputBuffer[OUTPUT_SIZE]; |
| static size_t outputLength; |
| |
| typedef struct { |
| const CommandEntry *entry; |
| unsigned int count; |
| } CommandDescriptor; |
| static CommandDescriptor *commandDescriptors = NULL; |
| static const size_t commandSize = sizeof(*commandDescriptors); |
| static size_t commandCount; |
| |
| static int brailleColumns; |
| static int brailleRows; |
| static int brailleCount; |
| static unsigned char *brailleCells = NULL; |
| static wchar_t *textCharacters = NULL; |
| |
| static int statusColumns; |
| static int statusRows; |
| static int statusCount; |
| static unsigned char *statusCells = NULL; |
| static unsigned char genericCells[GSC_COUNT]; |
| |
| typedef struct { |
| #ifdef AF_LOCAL |
| int (*getLocalConnection) (const struct sockaddr_un *address); |
| #endif /* AF_LOCAL */ |
| |
| #ifdef __MINGW32__ |
| int (*getNamedPipeConnection) (const char *path); |
| #endif /* __MINGW32__ */ |
| |
| int (*getInetConnection) (const struct sockaddr_in *address); |
| } ModeEntry; |
| static const ModeEntry *mode; |
| |
| typedef struct { |
| int (*read) (int descriptor, void *buffer, int size); |
| } OperationsEntry; |
| static const OperationsEntry *operations; |
| |
| static int |
| readNetworkSocket (int descriptor, void *buffer, int size) { |
| if (awaitSocketInput(descriptor, 0)) { |
| int count = recv(descriptor, buffer, size, 0); |
| if (count != -1) return count; |
| LogSocketError("recv"); |
| } |
| |
| return -1; |
| } |
| |
| static const OperationsEntry socketOperationsEntry = { |
| readNetworkSocket |
| }; |
| |
| static char * |
| formatSocketAddress (const struct sockaddr *address) { |
| char *string; |
| |
| switch (address->sa_family) { |
| #ifdef AF_LOCAL |
| case AF_LOCAL: { |
| const struct sockaddr_un *localAddress = (const struct sockaddr_un *)address; |
| |
| string = strdup(localAddress->sun_path); |
| break; |
| } |
| #endif /* AF_LOCAL */ |
| |
| case AF_INET: { |
| const struct sockaddr_in *inetAddress = (const struct sockaddr_in *)address; |
| const char *host = inet_ntoa(inetAddress->sin_addr); |
| unsigned short port = ntohs(inetAddress->sin_port); |
| char buffer[strlen(host) + 7]; |
| |
| snprintf(buffer, sizeof(buffer), "%s:%u", host, port); |
| string = strdup(buffer); |
| break; |
| } |
| |
| default: |
| string = strdup(""); |
| break; |
| } |
| |
| if (!string) logMallocError(); |
| return string; |
| } |
| |
| static int |
| acceptSocketConnection ( |
| int (*getSocket) (void), |
| int (*prepareQueue) (int socket), |
| void (*unbindAddress) (const struct sockaddr *address), |
| const struct sockaddr *localAddress, socklen_t localSize, |
| struct sockaddr *remoteAddress, socklen_t *remoteSize |
| ) { |
| int serverSocket = -1; |
| int queueSocket; |
| |
| if ((queueSocket = getSocket()) != -1) { |
| if (!prepareQueue || prepareQueue(queueSocket)) { |
| if (bind(queueSocket, localAddress, localSize) != -1) { |
| if (listen(queueSocket, 1) != -1) { |
| int attempts = 0; |
| |
| { |
| char *address = formatSocketAddress(localAddress); |
| |
| if (address) { |
| logMessage(LOG_NOTICE, "listening on: %s", address); |
| free(address); |
| } |
| } |
| |
| while (1) { |
| fd_set readMask; |
| struct timeval timeout; |
| |
| FD_ZERO(&readMask); |
| FD_SET(queueSocket, &readMask); |
| |
| memset(&timeout, 0, sizeof(timeout)); |
| timeout.tv_sec = 10; |
| |
| ++attempts; |
| switch (select(queueSocket+1, &readMask, NULL, NULL, &timeout)) { |
| case -1: |
| if (errno == EINTR) continue; |
| LogSocketError("select"); |
| break; |
| |
| case 0: |
| logMessage(LOG_DEBUG, "no connection yet, still waiting (%d).", attempts); |
| continue; |
| |
| default: { |
| if (!FD_ISSET(queueSocket, &readMask)) continue; |
| |
| if ((serverSocket = accept(queueSocket, remoteAddress, remoteSize)) != -1) { |
| char *address = formatSocketAddress(remoteAddress); |
| |
| if (address) { |
| logMessage(LOG_NOTICE, "client is: %s", address); |
| free(address); |
| } |
| } else { |
| LogSocketError("accept"); |
| } |
| } |
| } |
| break; |
| } |
| } else { |
| LogSocketError("listen"); |
| } |
| |
| if (unbindAddress) unbindAddress(localAddress); |
| } else { |
| LogSocketError("bind"); |
| } |
| } |
| |
| close(queueSocket); |
| } else { |
| LogSocketError("socket"); |
| } |
| |
| operations = &socketOperationsEntry; |
| return serverSocket; |
| } |
| |
| static int |
| requestConnection ( |
| int (*getSocket) (void), |
| const struct sockaddr *remoteAddress, socklen_t remoteSize |
| ) { |
| int clientSocket; |
| |
| { |
| char *address = formatSocketAddress(remoteAddress); |
| |
| if (address) { |
| logMessage(LOG_DEBUG, "connecting to: %s", address); |
| free(address); |
| } |
| } |
| |
| if ((clientSocket = getSocket()) != -1) { |
| if (connect(clientSocket, remoteAddress, remoteSize) != -1) { |
| { |
| char *address = formatSocketAddress(remoteAddress); |
| |
| if (address) { |
| logMessage(LOG_NOTICE, "connected to: %s", address); |
| free(address); |
| } |
| } |
| |
| operations = &socketOperationsEntry; |
| return clientSocket; |
| } else { |
| logMessage(LOG_WARNING, "connect error: %s", strerror(errno)); |
| } |
| |
| close(clientSocket); |
| } else { |
| LogSocketError("socket"); |
| } |
| |
| return -1; |
| } |
| |
| static int |
| setSocketReuseAddress (int socket) { |
| int yes = 1; |
| |
| if (setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes)) != -1) { |
| return 1; |
| } else { |
| LogSocketError("setsockopt REUSEADDR"); |
| } |
| return 0; |
| } |
| |
| #ifdef AF_LOCAL |
| static int |
| setLocalAddress (const char *string, struct sockaddr_un *address) { |
| int ok = 1; |
| |
| memset(address, 0, sizeof(*address)); |
| address->sun_family = AF_LOCAL; |
| |
| if (strlen(string) < sizeof(address->sun_path)) { |
| strncpy(address->sun_path, string, sizeof(address->sun_path)-1); |
| } else { |
| ok = 0; |
| logMessage(LOG_WARNING, "Local socket path too long: %s", string); |
| } |
| |
| return ok; |
| } |
| |
| static int |
| getLocalSocket (void) { |
| return socket(PF_LOCAL, SOCK_STREAM, 0); |
| } |
| |
| static void |
| unbindLocalAddress (const struct sockaddr *address) { |
| const struct sockaddr_un *localAddress = (const struct sockaddr_un *)address; |
| if (unlink(localAddress->sun_path) == -1) { |
| logSystemError("unlink"); |
| } |
| } |
| |
| static int |
| acceptLocalConnection (const struct sockaddr_un *localAddress) { |
| struct sockaddr_un remoteAddress; |
| socklen_t remoteSize = sizeof(remoteAddress); |
| |
| return acceptSocketConnection(getLocalSocket, NULL, unbindLocalAddress, |
| (const struct sockaddr *)localAddress, sizeof(*localAddress), |
| (struct sockaddr *)&remoteAddress, &remoteSize); |
| } |
| |
| static int |
| requestLocalConnection (const struct sockaddr_un *remoteAddress) { |
| return requestConnection(getLocalSocket, |
| (const struct sockaddr *)remoteAddress, sizeof(*remoteAddress)); |
| } |
| #endif /* AF_LOCAL */ |
| |
| #ifdef __MINGW32__ |
| static int |
| readNamedPipe (int descriptor, void *buffer, int size) { |
| { |
| DWORD available; |
| |
| if (!PeekNamedPipe((HANDLE)descriptor, NULL, 0, NULL, &available, NULL)) { |
| logWindowsSystemError("PeekNamedPipe"); |
| return 0; |
| } |
| |
| if (!available) { |
| errno = EAGAIN; |
| return -1; |
| } |
| |
| if (available < size) size = available; |
| } |
| |
| { |
| DWORD received; |
| OVERLAPPED overl = {0, 0, {{0, 0}}, NULL}; |
| overl.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); |
| |
| if (!ReadFile((HANDLE)descriptor, buffer, size, &received, &overl)) { |
| if (GetLastError() != ERROR_IO_PENDING) { |
| logWindowsSystemError("ReadPipe"); |
| received = 0; |
| } else if (!GetOverlappedResult((HANDLE)descriptor, &overl, &received, TRUE)) { |
| logWindowsSystemError("GetOverlappedResult"); |
| received = 0; |
| } |
| } |
| |
| CloseHandle(overl.hEvent); |
| return received; |
| } |
| } |
| |
| static const OperationsEntry namedPipeOperationsEntry = { |
| readNamedPipe |
| }; |
| |
| static int |
| acceptNamedPipeConnection (const char *path) { |
| HANDLE h; |
| OVERLAPPED overl = {0, 0, {{0, 0}}, NULL}; |
| DWORD res; |
| int attempts = 0; |
| |
| if ((h = CreateNamedPipe(path, |
| PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, |
| PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, |
| 1, 0, 0, 0, NULL)) == INVALID_HANDLE_VALUE) { |
| logWindowsSystemError("CreateNamedPipe"); |
| return -1; |
| } |
| |
| overl.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); |
| if (!ConnectNamedPipe(h, &overl)) { |
| switch (GetLastError()) { |
| case ERROR_IO_PENDING: |
| while ((res = WaitForSingleObject(overl.hEvent, 10000)) != WAIT_OBJECT_0) { |
| if (res == WAIT_TIMEOUT) { |
| ++attempts; |
| logMessage(LOG_DEBUG, "no connection yet, still waiting (%d).", attempts); |
| } else { |
| logWindowsSystemError("ConnectNamedPipe"); |
| CloseHandle(h); |
| h = (HANDLE) -1; |
| break; |
| } |
| } |
| |
| case ERROR_PIPE_CONNECTED: |
| break; |
| |
| default: |
| logWindowsSystemError("ConnectNamedPipe"); |
| CloseHandle(h); |
| h = (HANDLE) -1; |
| break; |
| } |
| } |
| |
| CloseHandle(overl.hEvent); |
| operations = &namedPipeOperationsEntry; |
| return (int)h; |
| } |
| |
| static int |
| requestNamedPipeConnection (const char *path) { |
| HANDLE h; |
| |
| if ((h = CreateFile(path, |
| GENERIC_READ|GENERIC_WRITE, |
| FILE_SHARE_READ|FILE_SHARE_WRITE, |
| NULL, OPEN_EXISTING, 0, NULL)) == INVALID_HANDLE_VALUE) { |
| logWindowsSystemError("Connect to named pipe"); |
| return -1; |
| } |
| |
| operations = &namedPipeOperationsEntry; |
| return (int)h; |
| } |
| #endif /* __MINGW32__ */ |
| |
| static int |
| setInetAddress (const char *string, struct sockaddr_in *address) { |
| int ok = 1; |
| char *hostName = strdup(string); |
| |
| if (hostName) { |
| char *portNumber = strchr(hostName, ':'); |
| |
| if (portNumber) { |
| *portNumber++ = 0; |
| if (!*portNumber) portNumber = NULL; |
| } |
| |
| memset(address, 0, sizeof(*address)); |
| address->sin_family = AF_INET; |
| |
| if (*hostName) { |
| const struct hostent *host = gethostbyname(hostName); |
| if (host && (host->h_addrtype == AF_INET) && (host->h_length == sizeof(address->sin_addr))) { |
| memcpy(&address->sin_addr, host->h_addr, sizeof(address->sin_addr)); |
| } else { |
| ok = 0; |
| logMessage(LOG_WARNING, "Unknown host name: %s", hostName); |
| } |
| } else { |
| address->sin_addr.s_addr = INADDR_ANY; |
| } |
| |
| if (portNumber) { |
| int port; |
| |
| if (isInteger(&port, portNumber)) { |
| if ((port > 0) && (port <= 0XFFFF)) { |
| address->sin_port = htons(port); |
| } else { |
| ok = 0; |
| logMessage(LOG_WARNING, "Invalid port number: %s", portNumber); |
| } |
| } else { |
| const struct servent *service = getservbyname(portNumber, "tcp"); |
| |
| if (service) { |
| address->sin_port = service->s_port; |
| } else { |
| ok = 0; |
| logMessage(LOG_WARNING, "Unknown service: %s", portNumber); |
| } |
| } |
| } else { |
| address->sin_port = htons(VR_DEFAULT_PORT); |
| } |
| |
| free(hostName); |
| } else { |
| ok = 0; |
| logMallocError(); |
| } |
| |
| return ok; |
| } |
| |
| static int |
| getInetSocket (void) { |
| return socket(PF_INET, SOCK_STREAM, 0); |
| } |
| |
| static int |
| prepareInetQueue (int socket) { |
| if (setSocketReuseAddress(socket)) return 1; |
| return 0; |
| } |
| |
| static int |
| acceptInetConnection (const struct sockaddr_in *localAddress) { |
| struct sockaddr_in remoteAddress; |
| socklen_t remoteSize = sizeof(remoteAddress); |
| |
| return acceptSocketConnection(getInetSocket, prepareInetQueue, NULL, |
| (const struct sockaddr *)localAddress, sizeof(*localAddress), |
| (struct sockaddr *)&remoteAddress, &remoteSize); |
| } |
| |
| static int |
| requestInetConnection (const struct sockaddr_in *remoteAddress) { |
| return requestConnection(getInetSocket, |
| (const struct sockaddr *)remoteAddress, sizeof(*remoteAddress)); |
| } |
| |
| static char * |
| makeString (const char *characters, int count) { |
| char *string = malloc(count+1); |
| |
| if (string) { |
| memcpy(string, characters, count); |
| string[count] = 0; |
| } else { |
| logMallocError(); |
| } |
| |
| return string; |
| } |
| |
| static char * |
| copyString (const char *string) { |
| return makeString(string, strlen(string)); |
| } |
| |
| static int |
| fillInputBuffer (void) { |
| if ((inputLength < INPUT_SIZE) && !inputEnd) { |
| int count = operations->read(fileDescriptor, &inputBuffer[inputLength], INPUT_SIZE-inputLength); |
| if (!count) { |
| inputEnd = 1; |
| } else if (count != -1) { |
| inputLength += count; |
| } else if (errno != EAGAIN) { |
| return 0; |
| } |
| } |
| return 1; |
| } |
| |
| static char * |
| readCommandLine (void) { |
| if (fillInputBuffer()) { |
| if (inputStart < inputLength) { |
| const char *newline = memchr(&inputBuffer[inputStart], '\n', inputLength-inputStart); |
| |
| if (newline) { |
| char *string; |
| int stringLength = newline - inputBuffer; |
| inputCarriageReturn = 0; |
| |
| if ((newline != inputBuffer) && (*(newline-1) == '\r')) { |
| inputCarriageReturn = 1; |
| stringLength -= 1; |
| } |
| |
| string = makeString(inputBuffer, stringLength); |
| inputLength -= ++newline - inputBuffer; |
| memmove(inputBuffer, newline, inputLength); |
| inputStart = 0; |
| return string; |
| } else { |
| inputStart = inputLength; |
| } |
| } else if (inputEnd) { |
| char *string; |
| |
| if (inputLength) { |
| string = makeString(inputBuffer, inputLength); |
| inputLength = 0; |
| inputStart = 0; |
| } else { |
| string = copyString("quit"); |
| } |
| |
| return string; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static const char * |
| nextWord (void) { |
| return strtok(NULL, inputDelimiters); |
| } |
| |
| static int |
| compareWords (const char *word1, const char *word2) { |
| return strcasecmp(word1, word2); |
| } |
| |
| static int |
| testWord (const char *suppliedWord, const char *desiredWord) { |
| return compareWords(suppliedWord, desiredWord) == 0; |
| } |
| |
| static int |
| flushOutput (void) { |
| const char *buffer = outputBuffer; |
| size_t length = outputLength; |
| |
| while (length) { |
| #ifdef __MINGW32__ |
| DWORD sent; |
| OVERLAPPED overl = {0, 0, {{0, 0}}, CreateEvent(NULL, TRUE, FALSE, NULL)}; |
| if ((!WriteFile((HANDLE) fileDescriptor, buffer, length, &sent, &overl) |
| && GetLastError() != ERROR_IO_PENDING) || |
| !GetOverlappedResult((HANDLE) fileDescriptor, &overl, &sent, TRUE)) { |
| LogSocketError("WriteFile"); |
| CloseHandle(overl.hEvent); |
| memmove(outputBuffer, buffer, (outputLength = length)); |
| return 0; |
| } |
| CloseHandle(overl.hEvent); |
| #else /* __MINGW32__ */ |
| int sent; |
| sent = send(fileDescriptor, buffer, length, 0); |
| |
| if (sent == -1) { |
| if (errno == EINTR) continue; |
| LogSocketError("send"); |
| memmove(outputBuffer, buffer, (outputLength = length)); |
| return 0; |
| } |
| #endif /* __MINGW32__ */ |
| |
| buffer += sent; |
| length -= sent; |
| } |
| |
| outputLength = 0; |
| return 1; |
| } |
| |
| static int |
| writeBytes (const char *bytes, size_t length) { |
| while (length) { |
| size_t count = OUTPUT_SIZE - outputLength; |
| if (length < count) count = length; |
| memcpy(&outputBuffer[outputLength], bytes, count); |
| bytes += count; |
| length -= count; |
| if ((outputLength += count) == OUTPUT_SIZE) |
| if (!flushOutput()) |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| static int |
| writeByte (char byte) { |
| return writeBytes(&byte, 1); |
| } |
| |
| static int |
| writeString (const char *string) { |
| return writeBytes(string, strlen(string)); |
| } |
| |
| static int |
| writeCharacter (wchar_t character) { |
| Utf8Buffer buffer; |
| size_t count = convertWcharToUtf8(character, buffer); |
| return writeBytes(buffer, count); |
| } |
| |
| static int |
| writeDots (const unsigned char *cells, int count) { |
| const unsigned char *cell = cells; |
| |
| while (count-- > 0) { |
| char dots[9]; |
| char *d = dots; |
| |
| if (cell != cells) *d++ = '|'; |
| if (*cell) { |
| if (*cell & BRL_DOT1) *d++ = '1'; |
| if (*cell & BRL_DOT2) *d++ = '2'; |
| if (*cell & BRL_DOT3) *d++ = '3'; |
| if (*cell & BRL_DOT4) *d++ = '4'; |
| if (*cell & BRL_DOT5) *d++ = '5'; |
| if (*cell & BRL_DOT6) *d++ = '6'; |
| if (*cell & BRL_DOT7) *d++ = '7'; |
| if (*cell & BRL_DOT8) *d++ = '8'; |
| } else { |
| *d++ = ' '; |
| } |
| ++cell; |
| |
| if (!writeBytes(dots, d-dots)) return 0; |
| } |
| |
| return 1; |
| } |
| |
| static int |
| writeLine (void) { |
| if (inputCarriageReturn) |
| if (!writeByte('\r')) |
| return 0; |
| |
| if (writeByte('\n')) |
| if (flushOutput()) |
| return 1; |
| |
| return 0; |
| } |
| |
| static void |
| sortCommands (int (*compareCommands) (const void *item1, const void *item2)) { |
| qsort(commandDescriptors, commandCount, commandSize, compareCommands); |
| } |
| |
| static int |
| compareCommandCodes (const void *item1, const void *item2) { |
| const CommandDescriptor *descriptor1 = item1; |
| const CommandDescriptor *descriptor2 = item2; |
| |
| int code1 = descriptor1->entry->code; |
| int code2 = descriptor2->entry->code; |
| |
| if (code1 < code2) return -1; |
| if (code1 > code2) return 1; |
| return 0; |
| } |
| |
| static void |
| sortCommandsByCode (void) { |
| sortCommands(compareCommandCodes); |
| } |
| |
| static int |
| compareCommandNames (const void *item1, const void *item2) { |
| const CommandDescriptor *descriptor1 = item1; |
| const CommandDescriptor *descriptor2 = item2; |
| |
| return strcmp(descriptor1->entry->name, descriptor2->entry->name); |
| } |
| |
| static void |
| sortCommandsByName (void) { |
| sortCommands(compareCommandNames); |
| } |
| |
| static int |
| allocateCommandDescriptors (void) { |
| if (!commandDescriptors) { |
| commandCount = getCommandCount(); |
| commandDescriptors = malloc(commandCount * commandSize); |
| |
| if (!commandDescriptors) { |
| logMallocError(); |
| return 0; |
| } |
| |
| { |
| CommandDescriptor *descriptor = commandDescriptors; |
| const CommandEntry *entry = commandTable; |
| while (entry->name) { |
| descriptor->entry = entry++; |
| descriptor->count = 0; |
| ++descriptor; |
| } |
| } |
| |
| sortCommandsByCode(); |
| { |
| CommandDescriptor *descriptor = commandDescriptors + commandCount; |
| int previousBlock = -1; |
| |
| while (descriptor-- != commandDescriptors) { |
| int code = descriptor->entry->code; |
| int currentBlock = code & BRL_MSK_BLK; |
| |
| if (currentBlock != previousBlock) { |
| if (currentBlock) { |
| descriptor->count = (BRL_MSK_ARG + 1) - (code & BRL_MSK_ARG); |
| } |
| previousBlock = currentBlock; |
| } |
| } |
| } |
| |
| sortCommandsByName(); |
| } |
| |
| return 1; |
| } |
| |
| static void |
| deallocateCommandDescriptors (void) { |
| if (commandDescriptors) { |
| free(commandDescriptors); |
| commandDescriptors = NULL; |
| } |
| } |
| |
| static int |
| compareCommandName (const void *key, const void *item) { |
| const char *name = key; |
| const CommandDescriptor *descriptor = item; |
| return compareWords(name, descriptor->entry->name); |
| } |
| |
| static const CommandDescriptor * |
| findCommand (const char *name) { |
| return bsearch(name, commandDescriptors, commandCount, commandSize, compareCommandName); |
| } |
| |
| static int |
| dimensionsChanged (BrailleDisplay *brl) { |
| int ok = 1; |
| const char *word; |
| |
| int columns1; |
| int rows1; |
| |
| int columns2 = 0; |
| int rows2 = 0; |
| |
| if ((word = nextWord())) { |
| if (isInteger(&columns1, word) && (columns1 > 0)) { |
| rows1 = 1; |
| |
| if ((word = nextWord())) { |
| if (isInteger(&rows1, word) && (rows1 > 0)) { |
| if ((word = nextWord())) { |
| if (isInteger(&columns2, word) && (columns2 > 0)) { |
| rows2 = 0; |
| |
| if ((word = nextWord())) { |
| if (isInteger(&rows2, word) && (rows2 > 0)) { |
| } else { |
| logMessage(LOG_WARNING, "invalid status row count: %s", word); |
| ok = 0; |
| } |
| } |
| } else { |
| logMessage(LOG_WARNING, "invalid status column count: %s", word); |
| ok = 0; |
| } |
| } |
| } else { |
| logMessage(LOG_WARNING, "invalid text row count: %s", word); |
| ok = 0; |
| } |
| } |
| } else { |
| logMessage(LOG_WARNING, "invalid text column count: %s", word); |
| ok = 0; |
| } |
| } else { |
| logMessage(LOG_WARNING, "missing text column count"); |
| ok = 0; |
| } |
| |
| if (ok) { |
| int count1 = columns1 * rows1; |
| int count2 = columns2 * rows2; |
| unsigned char *braille; |
| wchar_t *text; |
| unsigned char *status; |
| |
| if ((braille = calloc(count1, sizeof(*braille)))) { |
| if ((text = calloc(count1, sizeof(*text)))) { |
| if ((status = calloc(count2, sizeof(*status)))) { |
| brailleColumns = columns1; |
| brailleRows = rows1; |
| brailleCount = count1; |
| |
| statusColumns = columns2; |
| statusRows = rows2; |
| statusCount = count2; |
| |
| if (brailleCells) free(brailleCells); |
| brailleCells = braille; |
| memset(brailleCells, 0, count1); |
| |
| if (textCharacters) free(textCharacters); |
| textCharacters = text; |
| wmemset(textCharacters, WC_C(' '), count1); |
| |
| if (statusCells) free(statusCells); |
| statusCells = status; |
| memset(statusCells, 0, count2); |
| memset(genericCells, 0, GSC_COUNT); |
| |
| brl->textColumns = brailleColumns; |
| brl->textRows = brailleRows; |
| brl->statusColumns = statusColumns; |
| brl->statusRows = statusRows; |
| return 1; |
| } |
| |
| free(text); |
| } |
| |
| free(braille); |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int |
| brl_construct (BrailleDisplay *brl, char **parameters, const char *device) { |
| if (!allocateCommandDescriptors()) return 0; |
| |
| inputLength = 0; |
| inputStart = 0; |
| inputEnd = 0; |
| outputLength = 0; |
| |
| if (hasQualifier(&device, "client")) { |
| static const ModeEntry clientModeEntry = { |
| #ifdef AF_LOCAL |
| requestLocalConnection, |
| #endif /* AF_LOCAL */ |
| |
| #ifdef __MINGW32__ |
| requestNamedPipeConnection, |
| #endif /* __MINGW32__ */ |
| |
| requestInetConnection |
| }; |
| mode = &clientModeEntry; |
| } else if (hasQualifier(&device, "server")) { |
| static const ModeEntry serverModeEntry = { |
| #ifdef AF_LOCAL |
| acceptLocalConnection, |
| #endif /* AF_LOCAL */ |
| |
| #ifdef __MINGW32__ |
| acceptNamedPipeConnection, |
| #endif /* __MINGW32__ */ |
| |
| acceptInetConnection |
| }; |
| mode = &serverModeEntry; |
| } else { |
| unsupportedDeviceIdentifier(device); |
| goto failed; |
| } |
| if (!*device) device = VR_DEFAULT_SOCKET; |
| |
| #ifdef AF_LOCAL |
| if (device[0] == '/') { |
| struct sockaddr_un address; |
| if (setLocalAddress(device, &address)) { |
| fileDescriptor = mode->getLocalConnection(&address); |
| } |
| } else |
| #endif /* AF_LOCAL */ |
| |
| #ifdef __MINGW32__ |
| if (device[0] == '\\') { |
| fileDescriptor = mode->getNamedPipeConnection(device); |
| } else { |
| static WSADATA wsadata; |
| if (WSAStartup(MAKEWORD(1, 1), &wsadata)) { |
| logWindowsSystemError("socket library start"); |
| goto failed; |
| } |
| } |
| #endif /* __MINGW32__ */ |
| |
| { |
| struct sockaddr_in address; |
| if (setInetAddress(device, &address)) { |
| fileDescriptor = mode->getInetConnection(&address); |
| } |
| } |
| |
| if (fileDescriptor != -1) { |
| char *line = NULL; |
| |
| while (1) { |
| if (line) free(line); |
| if ((line = readCommandLine())) { |
| const char *word; |
| logMessage(LOG_DEBUG, "command received: %s", line); |
| |
| if ((word = strtok(line, inputDelimiters))) { |
| if (testWord(word, "cells")) { |
| if (dimensionsChanged(brl)) { |
| free(line); |
| return 1; |
| } |
| } else if (testWord(word, "quit")) { |
| break; |
| } else { |
| logMessage(LOG_WARNING, "unexpected command: %s", word); |
| } |
| } |
| } else { |
| asyncWait(1000); |
| } |
| } |
| if (line) free(line); |
| |
| close(fileDescriptor); |
| fileDescriptor = -1; |
| } |
| |
| failed: |
| deallocateCommandDescriptors(); |
| return 0; |
| } |
| |
| static void |
| brl_destruct (BrailleDisplay *brl) { |
| if (statusCells) { |
| free(statusCells); |
| statusCells = NULL; |
| } |
| |
| if (textCharacters) { |
| free(textCharacters); |
| textCharacters = NULL; |
| } |
| |
| if (brailleCells) { |
| free(brailleCells); |
| brailleCells = NULL; |
| } |
| |
| if (fileDescriptor != -1) { |
| close(fileDescriptor); |
| fileDescriptor = -1; |
| } |
| |
| deallocateCommandDescriptors(); |
| } |
| |
| static int |
| brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) { |
| if (text) { |
| if (wmemcmp(text, textCharacters, brailleCount) != 0) { |
| const wchar_t *address = text; |
| int count = brailleCount; |
| |
| writeString("Visual \""); |
| |
| while (count-- > 0) { |
| wchar_t character = *address++; |
| |
| switch (character) { |
| case WC_C('"'): |
| case WC_C('\\'): |
| writeCharacter(WC_C('\\')); |
| /* fall through */ |
| default: |
| writeCharacter(character); |
| break; |
| } |
| } |
| |
| writeString("\""); |
| writeLine(); |
| |
| wmemcpy(textCharacters, text, brailleCount); |
| } |
| } |
| |
| if (cellsHaveChanged(brailleCells, brl->buffer, brailleCount, NULL, NULL, NULL)) { |
| writeString("Braille \""); |
| writeDots(brl->buffer, brailleCount); |
| writeString("\""); |
| writeLine(); |
| } |
| |
| return 1; |
| } |
| |
| static int |
| brl_writeStatus (BrailleDisplay *brl, const unsigned char *status) { |
| int generic = status[GSC_FIRST] == GSC_MARKER; |
| unsigned char *cells; |
| int count; |
| |
| if (generic) { |
| cells = genericCells; |
| count = GSC_COUNT; |
| } else { |
| cells = statusCells; |
| count = statusCount; |
| } |
| |
| if (cellsHaveChanged(cells, status, count, NULL, NULL, NULL)) { |
| if (generic) { |
| int all = cells[GSC_FIRST] != GSC_MARKER; |
| int i; |
| |
| for (i=1; i<count; ++i) { |
| unsigned char value = status[i]; |
| if (all || (value != cells[i])) { |
| static const char *const names[] = { |
| [GSC_FIRST] = NULL, |
| [gscBrailleWindowColumn] = "BRLCOL", |
| [gscBrailleWindowRow] = "BRLROW", |
| [gscScreenCursorColumn] = "CSRCOL", |
| [gscScreenCursorRow] = "CSRROW", |
| [gscScreenNumber] = "SCRNUM", |
| [gscFrozenScreen] = "FREEZE", |
| [gscDisplayMode] = "DISPMD", |
| [gscSixDotComputerBraille] = "SIXDOTS", |
| [gscContractedBraille] = "CONTRACTED", |
| [gscSlidingBrailleWindow] = "SLIDEWIN", |
| [gscSkipIdenticalLines] = "SKPIDLNS", |
| [gscSkipBlankBrailleWindows] = "SKPBLNKWINS", |
| [gscShowScreenCursor] = "CSRVIS", |
| [gscHideScreenCursor] = "CSRHIDE", |
| [gscTrackScreenCursor] = "CSRTRK", |
| [gscScreenCursorStyle] = "CSRSIZE", |
| [gscBlinkingScreenCursor] = "CSRBLINK", |
| [gscShowAttributes] = "ATTRVIS", |
| [gscBlinkingAttributes] = "ATTRBLINK", |
| [gscBlinkingCapitals] = "CAPBLINK", |
| [gscAlertTunes] = "TUNES", |
| [gscAutorepeat] = "AUTOREPEAT", |
| [gscAutospeak] = "AUTOSPEAK", |
| [gscBrailleTypingMode] = "BRLUCDOTS" |
| }; |
| const int nameCount = ARRAY_COUNT(names); |
| |
| if (i < nameCount) { |
| const char *name = names[i]; |
| if (name) { |
| char buffer[0X40]; |
| snprintf(buffer, sizeof(buffer), "%s %d", name, value); |
| writeString(buffer); |
| writeLine(); |
| } |
| } |
| } |
| } |
| } else { |
| writeString("Status \""); |
| writeDots(cells, count); |
| writeString("\""); |
| writeLine(); |
| } |
| } |
| |
| return 1; |
| } |
| |
| static int |
| brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) { |
| int command = EOF; |
| char *line = readCommandLine(); |
| |
| if (line) { |
| const char *word; |
| logMessage(LOG_DEBUG, "Command received: %s", line); |
| |
| if ((word = strtok(line, inputDelimiters))) { |
| if (testWord(word, "cells")) { |
| if (dimensionsChanged(brl)) brl->resizeRequired = 1; |
| } else if (testWord(word, "quit")) { |
| command = BRL_CMD_RESTARTBRL; |
| } else { |
| const CommandDescriptor *descriptor = findCommand(word); |
| if (descriptor) { |
| int needsNumber = descriptor->count > 0; |
| int numberSpecified = 0; |
| int switchSpecified = 0; |
| int block; |
| |
| command = descriptor->entry->code; |
| block = command & BRL_MSK_BLK; |
| |
| while ((word = nextWord())) { |
| if (block == 0) { |
| if (!switchSpecified) { |
| if (testWord(word, "on")) { |
| switchSpecified = 1; |
| command |= BRL_FLG_TOGGLE_ON; |
| continue; |
| } |
| |
| if (testWord(word, "off")) { |
| switchSpecified = 1; |
| command |= BRL_FLG_TOGGLE_OFF; |
| continue; |
| } |
| } |
| } |
| |
| if (needsNumber && !numberSpecified) { |
| int number; |
| if (isInteger(&number, word)) { |
| if ((number > 0) && (number <= descriptor->count)) { |
| numberSpecified = 1; |
| command += number; |
| continue; |
| } else { |
| logMessage(LOG_WARNING, "Number out of range."); |
| } |
| } |
| } |
| |
| logMessage(LOG_WARNING, "unknown option: %s", word); |
| } |
| |
| if (needsNumber && !numberSpecified) { |
| logMessage(LOG_WARNING, "Number not specified."); |
| command = EOF; |
| } |
| } else { |
| logMessage(LOG_WARNING, "unknown command: %s", word); |
| } |
| } |
| } |
| |
| free(line); |
| } |
| |
| return command; |
| } |