| /* |
| * 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 <stdarg.h> |
| #include <string.h> |
| #include <errno.h> |
| |
| #ifdef HAVE_SIGNAL_H |
| #include <signal.h> |
| #endif /* HAVE_SIGNAL_H */ |
| |
| #ifdef SIGUSR1 |
| #include <sys/wait.h> |
| #endif /* SIGUSR1 */ |
| |
| #include "parameters.h" |
| #include "log.h" |
| #include "program.h" |
| #include "thread.h" |
| #include "async_wait.h" |
| #include "timing.h" |
| #include "scr.h" |
| #include "routing.h" |
| |
| typedef enum { |
| CRR_DONE, |
| CRR_NEAR, |
| CRR_FAIL |
| } RoutingResult; |
| |
| typedef struct { |
| #ifdef SIGUSR1 |
| struct { |
| sigset_t mask; |
| } signal; |
| #endif /* SIGUSR1 */ |
| |
| struct { |
| int number; |
| int width; |
| int height; |
| } screen; |
| |
| struct { |
| int scroll; |
| int row; |
| ScreenCharacter *buffer; |
| } vertical; |
| |
| struct { |
| int column; |
| int row; |
| } current; |
| |
| struct { |
| int column; |
| int row; |
| } previous; |
| |
| struct { |
| long sum; |
| int count; |
| } time; |
| } CursorRoutingData; |
| |
| typedef enum { |
| CURSOR_DIR_LEFT, |
| CURSOR_DIR_RIGHT, |
| CURSOR_DIR_UP, |
| CURSOR_DIR_DOWN |
| } CursorDirection; |
| |
| typedef struct { |
| const char *name; |
| ScreenKey key; |
| } CursorDirectionEntry; |
| |
| static const CursorDirectionEntry cursorDirectionTable[] = { |
| [CURSOR_DIR_LEFT] = {.name="left" , .key=SCR_KEY_CURSOR_LEFT }, |
| [CURSOR_DIR_RIGHT] = {.name="right", .key=SCR_KEY_CURSOR_RIGHT}, |
| [CURSOR_DIR_UP] = {.name="up" , .key=SCR_KEY_CURSOR_UP }, |
| [CURSOR_DIR_DOWN] = {.name="down" , .key=SCR_KEY_CURSOR_DOWN } |
| }; |
| |
| typedef enum { |
| CURSOR_AXIS_HORIZONTAL, |
| CURSOR_AXIS_VERTICAL |
| } CursorAxis; |
| |
| typedef struct { |
| const CursorDirectionEntry *forward; |
| const CursorDirectionEntry *backward; |
| } CursorAxisEntry; |
| |
| static const CursorAxisEntry cursorAxisTable[] = { |
| [CURSOR_AXIS_HORIZONTAL] = { |
| .forward = &cursorDirectionTable[CURSOR_DIR_RIGHT], |
| .backward = &cursorDirectionTable[CURSOR_DIR_LEFT] |
| } |
| , |
| [CURSOR_AXIS_VERTICAL] = { |
| .forward = &cursorDirectionTable[CURSOR_DIR_DOWN], |
| .backward = &cursorDirectionTable[CURSOR_DIR_UP] |
| } |
| }; |
| |
| #define logRouting(...) logMessage(LOG_CATEGORY(CURSOR_ROUTING), __VA_ARGS__) |
| |
| static int |
| readRow (CursorRoutingData *crd, ScreenCharacter *buffer, int row) { |
| if (!buffer) buffer = crd->vertical.buffer; |
| if (readScreenRow(row, crd->screen.width, buffer)) return 1; |
| logRouting("read failed: row=%d", row); |
| return 0; |
| } |
| |
| static int |
| getCurrentPosition (CursorRoutingData *crd) { |
| ScreenDescription description; |
| describeScreen(&description); |
| |
| if (description.number != crd->screen.number) { |
| logRouting("screen changed: %d -> %d", crd->screen.number, description.number); |
| crd->screen.number = description.number; |
| return 0; |
| } |
| |
| if (!crd->vertical.buffer) { |
| crd->screen.width = description.cols; |
| crd->screen.height = description.rows; |
| crd->vertical.scroll = 0; |
| |
| if (!(crd->vertical.buffer = malloc(ARRAY_SIZE(crd->vertical.buffer, crd->screen.width)))) { |
| logMallocError(); |
| goto error; |
| } |
| |
| logRouting("screen: num=%d cols=%d rows=%d", |
| crd->screen.number, |
| crd->screen.width, crd->screen.height); |
| } else if ((crd->screen.width != description.cols) || |
| (crd->screen.height != description.rows)) { |
| logRouting("size changed: %dx%d -> %dx%d", |
| crd->screen.width, crd->screen.height, |
| description.cols, description.rows); |
| goto error; |
| } |
| |
| crd->current.row = description.posy + crd->vertical.scroll; |
| crd->current.column = description.posx; |
| return 1; |
| |
| error: |
| crd->screen.number = -1; |
| return 0; |
| } |
| |
| static void |
| handleVerticalScrolling (CursorRoutingData *crd, int direction) { |
| int firstRow = crd->vertical.row; |
| int currentRow = firstRow; |
| |
| int bestRow = firstRow; |
| int bestLength = 0; |
| |
| do { |
| ScreenCharacter buffer[crd->screen.width]; |
| if (!readRow(crd, buffer, currentRow)) break; |
| |
| int length; |
| { |
| int before = crd->current.column; |
| int after = before; |
| |
| while (buffer[before].text == crd->vertical.buffer[before].text) |
| if (--before < 0) |
| break; |
| |
| while (buffer[after].text == crd->vertical.buffer[after].text) |
| if (++after >= crd->screen.width) |
| break; |
| |
| length = after - before - 1; |
| } |
| |
| if (length > bestLength) { |
| bestRow = currentRow; |
| if ((bestLength = length) == crd->screen.width) break; |
| } |
| |
| currentRow -= direction; |
| } while ((currentRow >= 0) && (currentRow < crd->screen.height)); |
| |
| int delta = bestRow - firstRow; |
| crd->vertical.scroll -= delta; |
| crd->current.row -= delta; |
| } |
| |
| static int |
| awaitCursorMotion (CursorRoutingData *crd, int direction) { |
| crd->previous.column = crd->current.column; |
| crd->previous.row = crd->current.row; |
| |
| TimeValue start; |
| getMonotonicTime(&start); |
| |
| int moved = 0; |
| long int timeout = crd->time.sum / crd->time.count; |
| |
| while (1) { |
| asyncWait(ROUTING_POLL_INTERVAL); |
| |
| TimeValue now; |
| getMonotonicTime(&now); |
| long int time = millisecondsBetween(&start, &now) + 1; |
| |
| int oldy = crd->current.row; |
| int oldx = crd->current.column; |
| if (!getCurrentPosition(crd)) return 0; |
| |
| if ((crd->current.row != oldy) || (crd->current.column != oldx)) { |
| logRouting("moved: [%d,%d] -> [%d,%d] (%ldms)", |
| oldx, oldy, crd->current.column, crd->current.row, time); |
| |
| if (!moved) { |
| moved = 1; |
| timeout = (time * 2) + 1; |
| |
| crd->time.sum += time * 8; |
| crd->time.count += 1; |
| } |
| |
| if (ROUTING_POLL_INTERVAL) { |
| start = now; |
| } else { |
| asyncWait(1); |
| getMonotonicTime(&start); |
| } |
| } else if (time > timeout) { |
| break; |
| } |
| } |
| |
| handleVerticalScrolling(crd, direction); |
| return 1; |
| } |
| |
| static int |
| moveCursor (CursorRoutingData *crd, const CursorDirectionEntry *direction) { |
| crd->vertical.row = crd->current.row - crd->vertical.scroll; |
| if (!readRow(crd, NULL, crd->vertical.row)) return 0; |
| |
| #ifdef SIGUSR1 |
| sigset_t oldMask; |
| sigprocmask(SIG_BLOCK, &crd->signal.mask, &oldMask); |
| #endif /* SIGUSR1 */ |
| |
| logRouting("move: %s", direction->name); |
| insertScreenKey(direction->key); |
| |
| #ifdef SIGUSR1 |
| sigprocmask(SIG_SETMASK, &oldMask, NULL); |
| #endif /* SIGUSR1 */ |
| |
| return 1; |
| } |
| |
| static RoutingResult |
| adjustCursorPosition (CursorRoutingData *crd, int where, int trgy, int trgx, const CursorAxisEntry *axis) { |
| logRouting("to: [%d,%d]", trgx, trgy); |
| |
| while (1) { |
| int dify = trgy - crd->current.row; |
| int difx = (trgx < 0)? 0: (trgx - crd->current.column); |
| int dir; |
| |
| /* determine which direction the cursor needs to move in */ |
| if (dify) { |
| dir = (dify > 0)? 1: -1; |
| } else if (difx) { |
| dir = (difx > 0)? 1: -1; |
| } else { |
| return CRR_DONE; |
| } |
| |
| /* tell the cursor to move in the needed direction */ |
| if (!moveCursor(crd, ((dir > 0)? axis->forward: axis->backward))) return CRR_FAIL; |
| if (!awaitCursorMotion(crd, dir)) return CRR_FAIL; |
| |
| if (crd->current.row != crd->previous.row) { |
| if (crd->previous.row != trgy) { |
| if (((crd->current.row - crd->previous.row) * dir) > 0) { |
| int dif = trgy - crd->current.row; |
| if ((dif * dify) >= 0) continue; |
| if (where > 0) { |
| if (crd->current.row > trgy) return CRR_NEAR; |
| } else if (where < 0) { |
| if (crd->current.row < trgy) return CRR_NEAR; |
| } else { |
| if ((dif * dif) < (dify * dify)) return CRR_NEAR; |
| } |
| } |
| } |
| } else if (crd->current.column != crd->previous.column) { |
| if (((crd->current.column - crd->previous.column) * dir) > 0) { |
| int dif = trgx - crd->current.column; |
| if (crd->current.row != trgy) continue; |
| if ((dif * difx) >= 0) continue; |
| if (where > 0) { |
| if (crd->current.column > trgx) return CRR_NEAR; |
| } else if (where < 0) { |
| if (crd->current.column < trgx) return CRR_NEAR; |
| } else { |
| if ((dif * dif) < (difx * difx)) return CRR_NEAR; |
| } |
| } |
| } else { |
| return CRR_NEAR; |
| } |
| |
| /* We're getting farther from our target. Before giving up, let's |
| * try going back to the previous position since it was obviously |
| * the nearest ever reached. |
| */ |
| if (!moveCursor(crd, ((dir > 0)? axis->backward: axis->forward))) return CRR_FAIL; |
| return awaitCursorMotion(crd, -dir)? CRR_NEAR: CRR_FAIL; |
| } |
| } |
| |
| static RoutingResult |
| adjustCursorHorizontally (CursorRoutingData *crd, int where, int row, int column) { |
| return adjustCursorPosition(crd, where, row, column, &cursorAxisTable[CURSOR_AXIS_HORIZONTAL]); |
| } |
| |
| static RoutingResult |
| adjustCursorVertically (CursorRoutingData *crd, int where, int row) { |
| return adjustCursorPosition(crd, where, row, -1, &cursorAxisTable[CURSOR_AXIS_VERTICAL]); |
| } |
| |
| typedef struct { |
| int column; |
| int row; |
| int screen; |
| } RoutingParameters; |
| |
| static RoutingStatus |
| routeCursor (const RoutingParameters *parameters) { |
| CursorRoutingData crd; |
| |
| #ifdef SIGUSR1 |
| /* Set up the signal mask. */ |
| sigemptyset(&crd.signal.mask); |
| sigaddset(&crd.signal.mask, SIGUSR1); |
| sigprocmask(SIG_UNBLOCK, &crd.signal.mask, NULL); |
| #endif /* SIGUSR1 */ |
| |
| /* initialize the routing data structure */ |
| crd.screen.number = parameters->screen; |
| crd.vertical.buffer = NULL; |
| crd.time.sum = ROUTING_MAXIMUM_TIMEOUT; |
| crd.time.count = 1; |
| |
| if (getCurrentPosition(&crd)) { |
| logRouting("from: [%d,%d]", crd.current.column, crd.current.row); |
| |
| if (parameters->column < 0) { |
| adjustCursorVertically(&crd, 0, parameters->row); |
| } else { |
| if (adjustCursorVertically(&crd, -1, parameters->row) != CRR_FAIL) { |
| if (adjustCursorHorizontally(&crd, 0, parameters->row, parameters->column) == CRR_NEAR) { |
| if (crd.current.row < parameters->row) { |
| if (adjustCursorVertically(&crd, 1, crd.current.row+1) != CRR_FAIL) { |
| adjustCursorHorizontally(&crd, 0, parameters->row, parameters->column); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| if (crd.vertical.buffer) free(crd.vertical.buffer); |
| |
| if (crd.screen.number != parameters->screen) return ROUTING_STATUS_FAILURE; |
| if (crd.current.row != parameters->row) return ROUTING_STATUS_ROW; |
| if ((parameters->column >= 0) && (crd.current.column != parameters->column)) return ROUTING_STATUS_COLUMN; |
| return ROUTING_STATUS_SUCCEESS; |
| } |
| |
| #ifdef SIGUSR1 |
| #define NOT_ROUTING 0 |
| |
| static pid_t routingProcess = NOT_ROUTING; |
| |
| int |
| isRouting (void) { |
| return routingProcess != NOT_ROUTING; |
| } |
| |
| RoutingStatus |
| getRoutingStatus (int wait) { |
| if (isRouting()) { |
| int options = 0; |
| if (!wait) options |= WNOHANG; |
| |
| doWait: |
| { |
| int status; |
| pid_t process = waitpid(routingProcess, &status, options); |
| |
| if (process == routingProcess) { |
| routingProcess = NOT_ROUTING; |
| return WIFEXITED(status)? WEXITSTATUS(status): ROUTING_STATUS_FAILURE; |
| } |
| |
| if (process == -1) { |
| if (errno == EINTR) goto doWait; |
| |
| if (errno == ECHILD) { |
| routingProcess = NOT_ROUTING; |
| return ROUTING_STATUS_FAILURE; |
| } |
| |
| logSystemError("waitpid"); |
| } |
| } |
| } |
| |
| return ROUTING_STATUS_NONE; |
| } |
| |
| static void |
| stopRouting (void) { |
| if (isRouting()) { |
| kill(routingProcess, SIGUSR1); |
| getRoutingStatus(1); |
| } |
| } |
| |
| static void |
| exitCursorRouting (void *data) { |
| stopRouting(); |
| } |
| #else /* SIGUSR1 */ |
| static RoutingStatus routingStatus = ROUTING_STATUS_NONE; |
| |
| RoutingStatus |
| getRoutingStatus (int wait) { |
| RoutingStatus status = routingStatus; |
| routingStatus = ROUTING_STATUS_NONE; |
| return status; |
| } |
| |
| int |
| isRouting (void) { |
| return 0; |
| } |
| #endif /* SIGUSR1 */ |
| |
| static int |
| startRoutingProcess (const RoutingParameters *parameters) { |
| #ifdef SIGUSR1 |
| int started = 0; |
| |
| stopRouting(); |
| |
| switch (routingProcess = fork()) { |
| case 0: { /* child: cursor routing subprocess */ |
| RoutingStatus status = ROUTING_STATUS_FAILURE; |
| |
| if (!ROUTING_POLL_INTERVAL) { |
| int niceness = nice(ROUTING_PROCESS_NICENESS); |
| |
| if (niceness == -1) { |
| logSystemError("nice"); |
| } |
| } |
| |
| if (constructRoutingScreen()) { |
| status = routeCursor(parameters); /* terminate child process */ |
| destructRoutingScreen(); /* close second thread of screen reading */ |
| } |
| |
| _exit(status); /* terminate child process */ |
| } |
| |
| case -1: /* error: fork() failed */ |
| logSystemError("fork"); |
| routingProcess = NOT_ROUTING; |
| break; |
| |
| default: { |
| /* parent: continue while cursor is being routed */ |
| |
| { |
| static int first = 1; |
| |
| if (first) { |
| first = 0; |
| onProgramExit("cursor-routing", exitCursorRouting, NULL); |
| } |
| } |
| |
| started = 1; |
| break; |
| } |
| } |
| |
| return started; |
| #else /* SIGUSR1 */ |
| routingStatus = routeCursor(parameters); |
| return 1; |
| #endif /* SIGUSR1 */ |
| } |
| |
| #ifdef GOT_PTHREADS |
| typedef struct { |
| const RoutingParameters *const parameters; |
| |
| int result; |
| } RoutingThreadArgument; |
| |
| THREAD_FUNCTION(runRoutingThread) { |
| RoutingThreadArgument *rta = argument; |
| |
| rta->result = startRoutingProcess(rta->parameters); |
| return NULL; |
| } |
| #endif /* GOT_PTHREADS */ |
| |
| int |
| startRouting (int column, int row, int screen) { |
| const RoutingParameters parameters = { |
| .column = column, |
| .row = row, |
| .screen = screen |
| }; |
| |
| #ifdef GOT_PTHREADS |
| int started = 0; |
| |
| RoutingThreadArgument rta = { |
| .parameters = ¶meters |
| }; |
| |
| if (callThreadFunction("cursor-routing", runRoutingThread, &rta, NULL)) { |
| if (rta.result) { |
| started = 1; |
| } |
| } |
| |
| return started; |
| #else /* GOT_PTHREADS */ |
| return startRoutingProcess(¶meters); |
| #endif /* GOT_PTHREADS */ |
| } |