/* * 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-2021 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 . */ #include "prologue.h" #include #include #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; rowIndexheight; 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, "unsuported 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(); }