| /* |
| * 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 <jni.h> |
| #include <stdio.h> |
| #include <stdarg.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <dlfcn.h> |
| |
| #include "embed.h" |
| #include "system_java.h" |
| |
| #if defined(__ANDROID__) |
| #include <android/log.h> |
| #define LOG(...) __android_log_print(ANDROID_LOG_DEBUG, PACKAGE_TARNAME, __VA_ARGS__) |
| |
| #else /* LOG() */ |
| #warning logging not supported |
| #define LOG(...) |
| #endif /* LOG() */ |
| |
| SYMBOL_POINTER(brlttyConstruct); |
| SYMBOL_POINTER(setJavaClassLoader); |
| SYMBOL_POINTER(brlttyDestruct); |
| |
| SYMBOL_POINTER(brlttyEnableInterrupt); |
| SYMBOL_POINTER(brlttyDisableInterrupt); |
| |
| SYMBOL_POINTER(brlttyInterrupt); |
| SYMBOL_POINTER(brlttyWait); |
| |
| SYMBOL_POINTER(changeLogLevel); |
| SYMBOL_POINTER(changeLogCategories); |
| |
| SYMBOL_POINTER(changeTextTable); |
| SYMBOL_POINTER(changeAttributesTable); |
| SYMBOL_POINTER(changeContractionTable); |
| SYMBOL_POINTER(changeKeyboardTable); |
| |
| SYMBOL_POINTER(restartBrailleDriver); |
| SYMBOL_POINTER(changeBrailleDriver); |
| SYMBOL_POINTER(changeBrailleParameters); |
| SYMBOL_POINTER(changeBrailleDevice); |
| |
| SYMBOL_POINTER(restartSpeechDriver); |
| SYMBOL_POINTER(changeSpeechDriver); |
| SYMBOL_POINTER(changeSpeechParameters); |
| |
| SYMBOL_POINTER(restartScreenDriver); |
| SYMBOL_POINTER(changeScreenDriver); |
| SYMBOL_POINTER(changeScreenParameters); |
| |
| SYMBOL_POINTER(changeMessageLocale); |
| SYMBOL_POINTER(showMessage); |
| |
| typedef struct { |
| const char *name; |
| void *pointer; |
| } SymbolEntry; |
| |
| #define BEGIN_SYMBOL_TABLE static const SymbolEntry symbolTable[] = { |
| #define END_SYMBOL_TABLE {.name=NULL} }; |
| #define SYMBOL_ENTRY(symbol) {.name=#symbol, .pointer=&symbol##_p} |
| |
| BEGIN_SYMBOL_TABLE |
| SYMBOL_ENTRY(brlttyConstruct), |
| SYMBOL_ENTRY(setJavaClassLoader), |
| SYMBOL_ENTRY(brlttyDestruct), |
| |
| SYMBOL_ENTRY(brlttyEnableInterrupt), |
| SYMBOL_ENTRY(brlttyDisableInterrupt), |
| |
| SYMBOL_ENTRY(brlttyInterrupt), |
| SYMBOL_ENTRY(brlttyWait), |
| |
| SYMBOL_ENTRY(changeLogLevel), |
| SYMBOL_ENTRY(changeLogCategories), |
| |
| SYMBOL_ENTRY(changeTextTable), |
| SYMBOL_ENTRY(changeAttributesTable), |
| SYMBOL_ENTRY(changeContractionTable), |
| SYMBOL_ENTRY(changeKeyboardTable), |
| |
| SYMBOL_ENTRY(restartBrailleDriver), |
| SYMBOL_ENTRY(changeBrailleDriver), |
| SYMBOL_ENTRY(changeBrailleParameters), |
| SYMBOL_ENTRY(changeBrailleDevice), |
| |
| SYMBOL_ENTRY(restartSpeechDriver), |
| SYMBOL_ENTRY(changeSpeechDriver), |
| SYMBOL_ENTRY(changeSpeechParameters), |
| |
| SYMBOL_ENTRY(restartScreenDriver), |
| SYMBOL_ENTRY(changeScreenDriver), |
| SYMBOL_ENTRY(changeScreenParameters), |
| |
| SYMBOL_ENTRY(changeMessageLocale), |
| SYMBOL_ENTRY(showMessage), |
| END_SYMBOL_TABLE |
| |
| static void *coreHandle = NULL; |
| |
| static jobject jArgumentArray = NULL; |
| static const char **cArgumentArray = NULL; |
| static int cArgumentCount; |
| |
| static void reportProblem ( |
| JNIEnv *env, const char *throwable, |
| const char *format, ... |
| ) PRINTF(3, 4); |
| |
| static void |
| reportProblem ( |
| JNIEnv *env, const char *throwable, |
| const char *format, ... |
| ) { |
| char message[0X100]; |
| |
| { |
| va_list arguments; |
| |
| va_start(arguments, format); |
| vsnprintf(message, sizeof(message), format, arguments); |
| va_end(arguments); |
| } |
| |
| if (0) { |
| FILE *stream = stderr; |
| |
| fprintf(stream, "%s\n", message); |
| fflush(stream); |
| } |
| |
| { |
| jclass object = (*env)->FindClass(env, throwable); |
| |
| if (object) { |
| (*env)->ThrowNew(env, object, message); |
| (*env)->DeleteLocalRef(env, object); |
| } |
| } |
| } |
| |
| static void |
| reportOutOfMemory (JNIEnv *env, const char *description) { |
| reportProblem(env, JAVA_OBJ_OUT_OF_MEMORY_ERROR, "cannot allocate %s", description); |
| } |
| |
| static int |
| prepareProgramArguments (JNIEnv *env, jstring arguments) { |
| jsize count = (*env)->GetArrayLength(env, arguments); |
| |
| if ((jArgumentArray = (*env)->NewGlobalRef(env, arguments))) { |
| if ((cArgumentArray = malloc((count + 2) * sizeof(*cArgumentArray)))) { |
| cArgumentArray[0] = PACKAGE_TARNAME; |
| cArgumentArray[count+1] = NULL; |
| |
| { |
| unsigned int i; |
| |
| for (i=1; i<=count; i+=1) cArgumentArray[i] = NULL; |
| |
| for (i=1; i<=count; i+=1) { |
| jstring jArgument = (*env)->GetObjectArrayElement(env, arguments, i-1); |
| jboolean isCopy; |
| const char *cArgument = (*env)->GetStringUTFChars(env, jArgument, &isCopy); |
| |
| (*env)->DeleteLocalRef(env, jArgument); |
| jArgument = NULL; |
| |
| if (!cArgument) { |
| reportOutOfMemory(env, "C argument string"); |
| break; |
| } |
| |
| cArgumentArray[i] = cArgument; |
| } |
| |
| if (i > count) { |
| cArgumentCount = count + 1; |
| return 1; |
| } |
| } |
| } else { |
| reportOutOfMemory(env, "C argument array"); |
| } |
| } else { |
| reportOutOfMemory(env, "Java arguments array global reference"); |
| } |
| |
| return 0; |
| } |
| |
| static int |
| loadCoreLibrary (JNIEnv *env) { |
| if (coreHandle) return 1; |
| |
| if ((coreHandle = dlopen("libbrltty_core.so", RTLD_NOW | RTLD_GLOBAL))) { |
| int allFound = 1; |
| const SymbolEntry *symbol = symbolTable; |
| |
| while (symbol->name) { |
| const void **pointer = symbol->pointer; |
| |
| if ((*pointer = dlsym(coreHandle, symbol->name))) { |
| LOG("core symbol: %s -> %p", symbol->name, *pointer); |
| } else { |
| LOG("core symbol not found: %s", symbol->name); |
| allFound = 0; |
| } |
| |
| symbol += 1; |
| } |
| |
| if (allFound) return 1; |
| } |
| |
| reportProblem(env, JAVA_OBJ_UNSATISFIED_LINK_ERROR, "%s", dlerror()); |
| return 0; |
| } |
| |
| JAVA_STATIC_METHOD ( |
| org_a11y_brltty_core_CoreWrapper, coreConstruct, jint, |
| jobjectArray arguments, jobject classLoader |
| ) { |
| if (prepareProgramArguments(env, arguments)) { |
| if (loadCoreLibrary(env)) { |
| setJavaClassLoader_p(env, classLoader); |
| return brlttyConstruct_p(cArgumentCount, (char **)cArgumentArray); |
| } |
| } |
| |
| return PROG_EXIT_FATAL; |
| } |
| |
| JAVA_STATIC_METHOD ( |
| org_a11y_brltty_core_CoreWrapper, coreDestruct, jboolean |
| ) { |
| jboolean result = brlttyDestruct_p()? JNI_TRUE: JNI_FALSE; |
| |
| /* |
| { |
| const SymbolEntry *symbol = symbolTable; |
| |
| while (symbol->name) { |
| const void **pointer = symbol->pointer; |
| *pointer = NULL; |
| symbol += 1; |
| } |
| } |
| |
| if (coreHandle) { |
| dlclose(coreHandle); |
| coreHandle = NULL; |
| } |
| */ |
| |
| if (jArgumentArray) { |
| if (cArgumentArray) { |
| unsigned int i = 0; |
| |
| while (cArgumentArray[++i]) { |
| jstring jArgument = (*env)->GetObjectArrayElement(env, jArgumentArray, i-1); |
| (*env)->ReleaseStringUTFChars(env, jArgument, cArgumentArray[i]); |
| (*env)->DeleteLocalRef(env, jArgument); |
| jArgument = NULL; |
| } |
| |
| free(cArgumentArray); |
| cArgumentArray = NULL; |
| } |
| |
| (*env)->DeleteGlobalRef(env, jArgumentArray); |
| jArgumentArray = NULL; |
| } |
| |
| return result; |
| } |
| |
| JAVA_STATIC_METHOD ( |
| org_a11y_brltty_core_CoreWrapper, coreEnableInterrupt, jboolean |
| ) { |
| return brlttyEnableInterrupt_p()? JNI_TRUE: JNI_FALSE; |
| } |
| |
| JAVA_STATIC_METHOD ( |
| org_a11y_brltty_core_CoreWrapper, coreDisableInterrupt, jboolean |
| ) { |
| return brlttyDisableInterrupt_p()? JNI_TRUE: JNI_FALSE; |
| } |
| |
| JAVA_STATIC_METHOD ( |
| org_a11y_brltty_core_CoreWrapper, coreInterrupt, jboolean, |
| jboolean stop |
| ) { |
| return brlttyInterrupt_p((stop != JNI_FALSE)? WAIT_STOP: WAIT_CONTINUE)? JNI_TRUE: JNI_FALSE; |
| } |
| |
| JAVA_STATIC_METHOD ( |
| org_a11y_brltty_core_CoreWrapper, coreWait, jboolean, |
| jint duration |
| ) { |
| return (brlttyWait_p(duration) != WAIT_STOP)? JNI_TRUE: JNI_FALSE; |
| } |
| |
| static jboolean |
| changeStringValue (JNIEnv *env, int (*change) (const char *cValue), jstring jValue) { |
| jboolean result = JNI_FALSE; |
| const char *cValue = (*env)->GetStringUTFChars(env, jValue, NULL); |
| |
| if (cValue) { |
| if (change(cValue)) result = JNI_TRUE; |
| (*env)->ReleaseStringUTFChars(env, jValue, cValue); |
| } else { |
| reportOutOfMemory(env, "C new value string"); |
| } |
| |
| return result; |
| } |
| |
| JAVA_STATIC_METHOD ( |
| org_a11y_brltty_core_CoreWrapper, changeLogLevel, jboolean, |
| jstring level |
| ) { |
| return changeStringValue(env, changeLogLevel_p, level); |
| } |
| |
| JAVA_STATIC_METHOD ( |
| org_a11y_brltty_core_CoreWrapper, changeLogCategories, jboolean, |
| jstring categories |
| ) { |
| return changeStringValue(env, changeLogCategories_p, categories); |
| } |
| |
| JAVA_STATIC_METHOD ( |
| org_a11y_brltty_core_CoreWrapper, changeTextTable, jboolean, |
| jstring name |
| ) { |
| return changeStringValue(env, changeTextTable_p, name); |
| } |
| |
| JAVA_STATIC_METHOD ( |
| org_a11y_brltty_core_CoreWrapper, changeAttributesTable, jboolean, |
| jstring name |
| ) { |
| return changeStringValue(env, changeAttributesTable_p, name); |
| } |
| |
| JAVA_STATIC_METHOD ( |
| org_a11y_brltty_core_CoreWrapper, changeContractionTable, jboolean, |
| jstring name |
| ) { |
| return changeStringValue(env, changeContractionTable_p, name); |
| } |
| |
| JAVA_STATIC_METHOD ( |
| org_a11y_brltty_core_CoreWrapper, changeKeyboardTable, jboolean, |
| jstring name |
| ) { |
| return changeStringValue(env, changeKeyboardTable_p, name); |
| } |
| |
| JAVA_STATIC_METHOD ( |
| org_a11y_brltty_core_CoreWrapper, restartBrailleDriver, jboolean |
| ) { |
| restartBrailleDriver_p(); |
| return JNI_TRUE; |
| } |
| |
| JAVA_STATIC_METHOD ( |
| org_a11y_brltty_core_CoreWrapper, changeBrailleDriver, jboolean, |
| jstring driver |
| ) { |
| return changeStringValue(env, changeBrailleDriver_p, driver); |
| } |
| |
| JAVA_STATIC_METHOD ( |
| org_a11y_brltty_core_CoreWrapper, changeBrailleParameters, jboolean, |
| jstring parameters |
| ) { |
| return changeStringValue(env, changeBrailleParameters_p, parameters); |
| } |
| |
| JAVA_STATIC_METHOD ( |
| org_a11y_brltty_core_CoreWrapper, changeBrailleDevice, jboolean, |
| jstring device |
| ) { |
| return changeStringValue(env, changeBrailleDevice_p, device); |
| } |
| |
| JAVA_STATIC_METHOD ( |
| org_a11y_brltty_core_CoreWrapper, restartSpeechDriver, jboolean |
| ) { |
| restartSpeechDriver_p(); |
| return JNI_TRUE; |
| } |
| |
| JAVA_STATIC_METHOD ( |
| org_a11y_brltty_core_CoreWrapper, changeSpeechDriver, jboolean, |
| jstring driver |
| ) { |
| return changeStringValue(env, changeSpeechDriver_p, driver); |
| } |
| |
| JAVA_STATIC_METHOD ( |
| org_a11y_brltty_core_CoreWrapper, changeSpeechParameters, jboolean, |
| jstring parameters |
| ) { |
| return changeStringValue(env, changeSpeechParameters_p, parameters); |
| } |
| |
| JAVA_STATIC_METHOD ( |
| org_a11y_brltty_core_CoreWrapper, restartScreenDriver, jboolean |
| ) { |
| restartScreenDriver_p(); |
| return JNI_TRUE; |
| } |
| |
| JAVA_STATIC_METHOD ( |
| org_a11y_brltty_core_CoreWrapper, changeScreenDriver, jboolean, |
| jstring driver |
| ) { |
| return changeStringValue(env, changeScreenDriver_p, driver); |
| } |
| |
| JAVA_STATIC_METHOD ( |
| org_a11y_brltty_core_CoreWrapper, changeScreenParameters, jboolean, |
| jstring parameters |
| ) { |
| return changeStringValue(env, changeScreenParameters_p, parameters); |
| } |
| |
| JAVA_STATIC_METHOD ( |
| org_a11y_brltty_core_CoreWrapper, changeMessageLocale, jboolean, |
| jstring locale |
| ) { |
| return changeStringValue(env, changeMessageLocale_p, locale); |
| } |
| |
| JAVA_STATIC_METHOD ( |
| org_a11y_brltty_core_CoreWrapper, showMessage, void, |
| jstring jMessage |
| ) { |
| const char *cMessage = (*env)->GetStringUTFChars(env, jMessage, NULL); |
| |
| if (cMessage) { |
| showMessage_p(cMessage); |
| (*env)->ReleaseStringUTFChars(env, jMessage, cMessage); |
| } else { |
| reportOutOfMemory(env, "C new value string"); |
| } |
| } |
| |
| JAVA_STATIC_METHOD ( |
| org_a11y_brltty_core_CoreWrapper, setEnvironmentVariable, jboolean, |
| jstring jName, jstring jValue |
| ) { |
| jboolean isCopy; |
| const char *cName = (*env)->GetStringUTFChars(env, jName, &isCopy); |
| const char *cValue = (*env)->GetStringUTFChars(env, jValue, &isCopy); |
| |
| int cResult = setenv(cName, cValue, 1) != -1; |
| jboolean jResult = cResult? JNI_TRUE: JNI_FALSE; |
| |
| if (cResult) { |
| LOG("environment variable set: %s: %s", cName, cValue); |
| } else { |
| LOG("environment variable not set: %s: %s", cName, strerror(errno)); |
| } |
| |
| (*env)->ReleaseStringUTFChars(env, jName, cName); |
| (*env)->ReleaseStringUTFChars(env, jValue, cValue); |
| return jResult; |
| } |
| |
| JNIEXPORT jint |
| JNI_OnLoad (JavaVM *vm, void *reserved) { |
| JNIEnv *env; |
| |
| if ((*vm)->GetEnv(vm, (void **)&env, JAVA_JNI_VERSION) == JNI_OK) { |
| loadCoreLibrary(env); |
| } |
| |
| return JAVA_JNI_VERSION; |
| } |