blob: f9824f2d22114eb71b48470add8b18e55ee19c11 [file] [log] [blame] [edit]
/*
* 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 = &parameters
};
if (callThreadFunction("cursor-routing", runRoutingThread, &rta, NULL)) {
if (rta.result) {
started = 1;
}
}
return started;
#else /* GOT_PTHREADS */
return startRoutingProcess(&parameters);
#endif /* GOT_PTHREADS */
}