blob: aecb41a4fe1b64f5f7787e48779b2731723bc84c [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 <string.h>
#include <errno.h>
#include "log.h"
#include "report.h"
#include "alert.h"
#include "brl_cmds.h"
#include "unicode.h"
#include "scr_driver.h"
#include "system_java.h"
#include "core.h"
static JNIEnv *env = NULL;
static jclass screenDriverClass = NULL;
static jclass inputHandlersClass = NULL;
static jint screenNumber, screenColumns, screenRows;
static jint locationLeft, locationTop, locationRight, locationBottom;
static jint selectionLeft, selectionTop, selectionRight, selectionBottom;
static const char *problemText;
static int
findScreenDriverClass (void) {
return findJavaClass(env, &screenDriverClass, JAVA_OBJ_BRLTTY("ScreenDriver"));
}
static int
findInputHandlersClass (void) {
return findJavaClass(env, &inputHandlersClass, JAVA_OBJ_BRLTTY("InputHandlers"));
}
static int
callSimpleInputHandler (jmethodID *method, const char *name) {
if (findInputHandlersClass()) {
if (findJavaStaticMethod(env, method, inputHandlersClass, name,
JAVA_SIG_METHOD(JAVA_SIG_BOOLEAN,
))) {
jboolean result = (*env)->CallStaticBooleanMethod(
env, inputHandlersClass, *method
);
if (!clearJavaException(env, 1)) {
if (result != JNI_FALSE) {
return 1;
}
}
}
}
return 0;
}
REPORT_LISTENER(androidScreenDriverReportListener) {
char *event = parameters->listenerData;
if (findScreenDriverClass()) {
static jmethodID method = 0;
if (findJavaStaticMethod(env, &method, screenDriverClass, "reportEvent",
JAVA_SIG_METHOD(JAVA_SIG_VOID,
JAVA_SIG_CHAR // event
))) {
(*env)->CallStaticVoidMethod(env, screenDriverClass, method, *event);
clearJavaException(env, 1);
}
}
}
typedef struct {
ReportListenerInstance *listener;
ReportIdentifier identifier;
char character;
} ReportEntry;
static ReportEntry reportEntries[] = {
{ .character = 'b',
.identifier = REPORT_BRAILLE_DEVICE_ONLINE
},
{ .character = 'B',
.identifier = REPORT_BRAILLE_DEVICE_OFFLINE
},
{ .character = 'k',
.identifier = REPORT_BRAILLE_KEY_EVENT
},
{ .character = 0 }
};
static int
construct_AndroidScreen (void) {
for (ReportEntry *rpt=reportEntries; rpt->character; rpt+=1) {
rpt->listener = registerReportListener(
rpt->identifier, androidScreenDriverReportListener, &rpt->character
);
if (!rpt->listener) break;
}
return 1;
}
static void
destruct_AndroidScreen (void) {
for (ReportEntry *rpt=reportEntries; rpt->character; rpt+=1) {
if (rpt->listener) {
unregisterReportListener(rpt->listener);
rpt->listener = NULL;
}
}
}
static int
poll_AndroidScreen (void) {
return 0;
}
JAVA_STATIC_METHOD (
org_a11y_brltty_android_ScreenDriver, screenUpdated, void
) {
mainScreenUpdated();
}
JAVA_STATIC_METHOD (
org_a11y_brltty_android_ScreenDriver, exportScreenProperties, void,
jint number, jint columns, jint rows,
jint locLeft, jint locTop, int locRight, int locBottom,
jint selLeft, jint selTop, int selRight, int selBottom
) {
screenNumber = number;
screenColumns = columns;
screenRows = rows;
locationLeft = locLeft;
locationTop = locTop;
locationRight = locRight;
locationBottom = locBottom;
selectionLeft = selLeft;
selectionTop = selTop;
selectionRight = selRight;
selectionBottom = selBottom;
}
static int
refresh_AndroidScreen (void) {
problemText = NULL;
if (findScreenDriverClass()) {
static jmethodID method = 0;
if (findJavaStaticMethod(env, &method, screenDriverClass, "refreshScreen",
JAVA_SIG_METHOD(JAVA_SIG_CHAR,
))) {
jchar result = (*env)->CallStaticCharMethod(env, screenDriverClass, method);
if (clearJavaException(env, 1)) {
problemText = gettext("Java exception occurred");
} else {
if (result == 'l') {
setBrailleOff(gettext("screen locked"));
return 1;
}
if (result == 'r') {
setBrailleOff(gettext("braille released"));
return 1;
}
}
} else {
problemText = gettext("Java method not found");
}
} else {
problemText = gettext("Java class not found");
}
setBrailleOn();
return 1;
}
static void
describe_AndroidScreen (ScreenDescription *description) {
if ((description->unreadable = problemText)) {
description->cols = strlen(problemText);
description->rows = 1;
description->posx = 0;
description->posy = 0;
description->number = 0;
} else {
description->number = screenNumber;
description->cols = screenColumns;
description->rows = screenRows;
description->posx = locationLeft + selectionLeft;
description->posy = locationTop + selectionTop;
description->hasCursor = 0;
description->hasSelection = 0;
if (selectionLeft >= 0) {
if ((selectionLeft == selectionRight) && (selectionTop == selectionBottom)) {
description->hasCursor = 1;
} else {
description->hasSelection = 1;
}
}
}
}
static int
getRowCharacters (JNIEnv *env, ScreenCharacter *characters, jcharArray jCharacters, jint rowIndex, jint columnIndex, jint columnCount) {
jint characterCount = (*env)->GetArrayLength(env, jCharacters);
if (clearJavaException(env, 1)) return 0;
{
ScreenCharacter *target = characters;
ScreenCharacter *targetEnd = target + columnCount;
if (characterCount > 0) {
jchar cCharacters[characterCount];
(*env)->GetCharArrayRegion(env, jCharacters, 0, characterCount, cCharacters);
if (clearJavaException(env, 1)) return 0;
const jchar *source = cCharacters;
const jchar *sourceEnd = source + characterCount;
while (source < sourceEnd) {
if (target == targetEnd) break;
target->text = *source;
target->attributes = SCR_COLOUR_DEFAULT;
target += 1;
source += 1;
}
}
clearScreenCharacters(target, (targetEnd - target));
}
int top = locationTop + selectionTop;
int bottom = locationTop + selectionBottom;
if ((rowIndex >= top) && (rowIndex < bottom)) {
int from = MAX(locationLeft, columnIndex);
int to = MIN(locationRight, (columnIndex + columnCount));
if (rowIndex == top) {
int left = locationLeft + selectionLeft;
if (left > from) from = left;
}
if ((rowIndex + 1) == bottom) {
int right = locationLeft + selectionRight;
if (right < to) to = right;
}
if (from < to) {
from -= columnIndex;
to -= columnIndex;
if (to > characterCount) to = characterCount;
ScreenCharacter *target = characters + from;
const ScreenCharacter *targetEnd = characters + to;
while (target < targetEnd) {
target->attributes = SCR_COLOUR_FG_BLACK | SCR_COLOUR_BG_LIGHT_GREY;
target += 1;
}
}
}
return 1;
}
static int
readRowCharacters (ScreenCharacter *characters, jint rowIndex, jint columnIndex, jint columnCount) {
if (findScreenDriverClass()) {
static jmethodID method = 0;
if (findJavaStaticMethod(env, &method, screenDriverClass, "getRowText",
JAVA_SIG_METHOD(JAVA_SIG_ARRAY(JAVA_SIG_CHAR),
JAVA_SIG_INT // row
JAVA_SIG_INT // column
))) {
jcharArray jCharacters = (*env)->CallStaticObjectMethod(
env, screenDriverClass, method, rowIndex, columnIndex
);
if (!clearJavaException(env, 1)) {
if (jCharacters) {
int ok = getRowCharacters(
env, characters, jCharacters,
rowIndex, columnIndex, columnCount
);
(*env)->DeleteLocalRef(env, jCharacters);
jCharacters = NULL;
return ok;
}
}
}
}
return 0;
}
static int
readCharacters_AndroidScreen (const ScreenBox *box, ScreenCharacter *buffer) {
if (validateScreenBox(box, screenColumns, screenRows)) {
if (problemText) {
setScreenMessage(box, buffer, problemText);
} else {
for (int rowIndex=0; rowIndex<box->height; rowIndex+=1) {
if (!readRowCharacters(&buffer[rowIndex * box->width], (rowIndex + box->top), box->left, box->width)) return 0;
}
}
return 1;
}
return 0;
}
static int
routeCursor_AndroidScreen (int column, int row, int screen) {
if (findScreenDriverClass()) {
static jmethodID method = 0;
if (findJavaStaticMethod(env, &method, screenDriverClass, "routeCursor",
JAVA_SIG_METHOD(JAVA_SIG_BOOLEAN,
JAVA_SIG_INT // column
JAVA_SIG_INT // row
))) {
jboolean result = (*env)->CallStaticBooleanMethod(env, screenDriverClass, method, column, row);
if (!clearJavaException(env, 1)) {
if (result != JNI_FALSE) {
return 1;
}
}
}
}
return 0;
}
static int
insertKey_AndroidScreen (ScreenKey key) {
if (findInputHandlersClass()) {
wchar_t character = key & SCR_KEY_CHAR_MASK;
setScreenKeyModifiers(&key, 0);
if (!isSpecialKey(key)) {
static jmethodID method = 0;
if (findJavaStaticMethod(env, &method, inputHandlersClass, "inputCharacter",
JAVA_SIG_METHOD(JAVA_SIG_BOOLEAN,
JAVA_SIG_CHAR // character
))) {
jboolean result = (*env)->CallStaticBooleanMethod(env, inputHandlersClass, method, character);
if (!clearJavaException(env, 1)) {
if (result != JNI_FALSE) {
return 1;
}
}
}
} else if (character < SCR_KEY_FUNCTION) {
#define SIZE UNICODE_CELL_NUMBER(SCR_KEY_FUNCTION)
#define KEY(key,method) [UNICODE_CELL_NUMBER(SCR_KEY_##key)] = method
static const char *const methodNames[SIZE] = {
KEY(ENTER, "keyHandler_enter"),
KEY(TAB, "keyHandler_tab"),
KEY(BACKSPACE, "keyHandler_backspace"),
KEY(ESCAPE, "keyHandler_escape"),
KEY(CURSOR_LEFT, "keyHandler_cursorLeft"),
KEY(CURSOR_RIGHT, "keyHandler_cursorRight"),
KEY(CURSOR_UP, "keyHandler_cursorUp"),
KEY(CURSOR_DOWN, "keyHandler_cursorDown"),
KEY(PAGE_UP, "keyHandler_pageUp"),
KEY(PAGE_DOWN, "keyHandler_pageDown"),
KEY(HOME, "keyHandler_home"),
KEY(END, "keyHandler_end"),
KEY(INSERT, "keyHandler_insert"),
KEY(DELETE, "keyHandler_delete"),
};
const unsigned int key = UNICODE_CELL_NUMBER(character);
const char *methodName = methodNames[key];
if (!methodName) return 0;
static jmethodID methodIdentifiers[SIZE];
jmethodID *methodIdentifier = &methodIdentifiers[key];
#undef SIZE
#undef KEY
if (findJavaStaticMethod(env, methodIdentifier, inputHandlersClass, methodName,
JAVA_SIG_METHOD(JAVA_SIG_BOOLEAN,
))) {
jboolean result = (*env)->CallStaticBooleanMethod(env, inputHandlersClass, *methodIdentifier);
if (!clearJavaException(env, 1)) {
if (result != JNI_FALSE) {
return 1;
}
}
}
} else {
static jmethodID method = 0;
if (findJavaStaticMethod(env, &method, inputHandlersClass, "keyHandler_function",
JAVA_SIG_METHOD(JAVA_SIG_BOOLEAN,
JAVA_SIG_INT // key
))) {
jboolean result = (*env)->CallStaticBooleanMethod(env, inputHandlersClass, method, character-SCR_KEY_FUNCTION);
if (!clearJavaException(env, 1)) {
if (result != JNI_FALSE) {
return 1;
}
}
}
}
}
logMessage(LOG_WARNING, "unsupported key: %04X", key);
return 0;
}
#define SIMPLE_INPUT_HANDLER_COMMAND(command, handler) \
case BRL_CMD_##command: { \
static jmethodID method = 0; \
if (callSimpleInputHandler(&method, handler)) return 1; \
break; \
}
static int
handleCommand_AndroidScreen (int command) {
int blk = command & BRL_MSK_BLK;
int arg = command & BRL_MSK_ARG;
int cmd = blk | arg;
switch (cmd) {
SIMPLE_INPUT_HANDLER_COMMAND(TXTSEL_ALL, "textHandler_selectAll")
SIMPLE_INPUT_HANDLER_COMMAND(HOST_COPY, "textHandler_copySelection")
SIMPLE_INPUT_HANDLER_COMMAND(HOST_CUT, "textHandler_cutSelection")
SIMPLE_INPUT_HANDLER_COMMAND(HOST_PASTE, "textHandler_pasteClipboard")
SIMPLE_INPUT_HANDLER_COMMAND(INDICATORS, "globalAction_showStatusIndicators")
SIMPLE_INPUT_HANDLER_COMMAND(GUI_TITLE, "globalAction_showWindowTitle")
SIMPLE_INPUT_HANDLER_COMMAND(GUI_BRL_ACTIONS, "globalAction_brailleActions")
SIMPLE_INPUT_HANDLER_COMMAND(GUI_HOME, "globalAction_homeScreen")
SIMPLE_INPUT_HANDLER_COMMAND(GUI_BACK, "globalAction_backButton")
SIMPLE_INPUT_HANDLER_COMMAND(GUI_DEV_SETTINGS, "globalAction_quickSettings")
SIMPLE_INPUT_HANDLER_COMMAND(GUI_DEV_OPTIONS, "globalAction_deviceOptions")
SIMPLE_INPUT_HANDLER_COMMAND(GUI_APP_LIST, "globalAction_recentApplications")
SIMPLE_INPUT_HANDLER_COMMAND(GUI_APP_MENU, "globalAction_menuButton")
SIMPLE_INPUT_HANDLER_COMMAND(GUI_APP_ALERTS, "globalAction_notificationsShade")
SIMPLE_INPUT_HANDLER_COMMAND(GUI_AREA_ACTV, "globalAction_toActiveWindow")
SIMPLE_INPUT_HANDLER_COMMAND(GUI_AREA_PREV, "globalAction_toPreviousWindow")
SIMPLE_INPUT_HANDLER_COMMAND(GUI_AREA_NEXT, "globalAction_toNextWindow")
SIMPLE_INPUT_HANDLER_COMMAND(GUI_ITEM_FRST, "globalAction_toFirstItem")
SIMPLE_INPUT_HANDLER_COMMAND(GUI_ITEM_PREV, "globalAction_toPreviousItem")
SIMPLE_INPUT_HANDLER_COMMAND(GUI_ITEM_NEXT, "globalAction_toNextItem")
SIMPLE_INPUT_HANDLER_COMMAND(GUI_ITEM_LAST, "globalAction_toLastItem")
default: {
switch (blk) {
case BRL_CMD_BLK(PASSDOTS): {
if ((command & BRL_FLG_INPUT_META) == 0) return 0;
if (findInputHandlersClass()) {
static jmethodID method = 0;
if (findJavaStaticMethod(env, &method, inputHandlersClass, "performStructuralMotion",
JAVA_SIG_METHOD(JAVA_SIG_BOOLEAN,
JAVA_SIG_BYTE // dots
))) {
jbyte dots = arg;
jboolean result = (*env)->CallStaticBooleanMethod(env, inputHandlersClass, method, dots);
if (clearJavaException(env, 1)) result = JNI_FALSE;
if (result == JNI_TRUE) return 1;
}
}
break;
}
default:
return 0;
}
break;
}
}
alert(ALERT_COMMAND_REJECTED);
return 1;
}
static int
clearSelection_AndroidScreen (void) {
static jmethodID method = 0;
return callSimpleInputHandler(&method, "textHandler_clearSelection");
}
static int
setSelection_AndroidScreen (int startColumn, int startRow, int endColumn, int endRow) {
if (findInputHandlersClass()) {
static jmethodID method = 0;
if (findJavaStaticMethod(env, &method, inputHandlersClass, "textHandler_setSelection",
JAVA_SIG_METHOD(JAVA_SIG_BOOLEAN,
JAVA_SIG_INT // startColumn
JAVA_SIG_INT // startRow
JAVA_SIG_INT // endColumn
JAVA_SIG_INT // endRow
))) {
jboolean result = (*env)->CallStaticBooleanMethod(
env, inputHandlersClass, method,
startColumn, startRow, endColumn, endRow
);
if (!clearJavaException(env, 1)) {
if (result != JNI_FALSE) {
return 1;
}
}
}
}
return 0;
}
static void
scr_initialize (MainScreen *main) {
initializeRealScreen(main);
main->base.poll = poll_AndroidScreen;
main->base.refresh = refresh_AndroidScreen;
main->base.describe = describe_AndroidScreen;
main->base.readCharacters = readCharacters_AndroidScreen;
main->base.routeCursor = routeCursor_AndroidScreen;
main->base.insertKey = insertKey_AndroidScreen;
main->base.handleCommand = handleCommand_AndroidScreen;
main->base.clearSelection = clearSelection_AndroidScreen;
main->base.setSelection = setSelection_AndroidScreen;
main->construct = construct_AndroidScreen;
main->destruct = destruct_AndroidScreen;
env = getJavaNativeInterface();
}