#pragma clang diagnostic push #pragma ide diagnostic ignored "readability-isolate-declaration" #pragma ide diagnostic ignored "cppcoreguidelines-avoid-magic-numbers" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "backend.h" #include "log.h" static void checkEGLError(int line) { char* error = NULL; switch(eglGetError()) { #define E(code, desc) case code: error = desc; break; case EGL_SUCCESS: return; // "No error" E(EGL_NOT_INITIALIZED, "EGL not initialized or failed to initialize"); E(EGL_BAD_ACCESS, "Resource inaccessible"); E(EGL_BAD_ALLOC, "Cannot allocate resources"); E(EGL_BAD_ATTRIBUTE, "Unrecognized attribute or attribute value"); E(EGL_BAD_CONTEXT, "Invalid EGL context"); E(EGL_BAD_CONFIG, "Invalid EGL frame buffer configuration"); E(EGL_BAD_CURRENT_SURFACE, "Current surface is no longer valid"); E(EGL_BAD_DISPLAY, "Invalid EGL display"); E(EGL_BAD_SURFACE, "Invalid surface"); E(EGL_BAD_MATCH, "Inconsistent arguments"); E(EGL_BAD_PARAMETER, "Invalid argument"); E(EGL_BAD_NATIVE_PIXMAP, "Invalid native pixmap"); E(EGL_BAD_NATIVE_WINDOW, "Invalid native window"); E(EGL_CONTEXT_LOST, "Context lost"); #undef E default: error = "Unknown error"; } LOGE("EGL: %s after %d", error, line); } #define checkEGLError() checkEGLError(__LINE__) enum { ACTION_KEY = 1, ACTION_LAYOUT_CHANGE = 2, ACTION_POINTER = 3, ACTION_WIN_CHANGE = 4, #ifdef __ANDROID__ ACTION_JNI_WIN_CHANGE = 5, #endif }; static const EGLint ctxattr[] = { EGL_CONTEXT_CLIENT_VERSION, 2, // Request opengl ES2.0 EGL_NONE }; static const EGLint sfcattr[] = { EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_BLUE_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_RED_SIZE, 8, EGL_NONE }; typedef struct { uint8_t type; union { struct { uint8_t state; uint16_t key; } key; struct { char *layout; } layout_change; struct { uint16_t state, button; uint32_t x, y; } pointer; struct { EGLDisplay dpy; EGLNativeWindowType win; uint32_t width, height, mmWidth, mmHeight; } window_change; #ifdef __ANDROID__ struct { JavaVM* jvm; jobject surface; uint32_t width, height; } jni_window_change; #endif }; } lorie_event; struct backend_android { EGLDisplay dpy; EGLContext ctx; EGLNativeDisplayType win; EGLConfig config; EGLSurface sfc; struct callbacks callbacks; struct xkb_context *xkb_context; struct xkb_rule_names xkb_names; struct xkb_keymap *xkb_keymap; int fds[2]; }; struct backend_android *backend = NULL; void lorie_key_event(void __unused *b, uint8_t state, uint16_t key) { if (!backend) return; lorie_event e; memset(&e, 0, sizeof(lorie_event)); e.type = ACTION_KEY; e.key.state = state; e.key.key = key; write(backend->fds[0], &e, sizeof(lorie_event)); } void lorie_layout_change_event(void __unused *b, char *layout) { if (!backend) return; lorie_event e; memset(&e, 0, sizeof(lorie_event)); e.type = ACTION_LAYOUT_CHANGE; e.layout_change.layout = layout; write(backend->fds[0], &e, sizeof(lorie_event)); } void lorie_pointer_event(void __unused *b, uint8_t state, uint16_t button, uint32_t x, uint32_t y) { if (!backend) return; lorie_event e; memset(&e, 0, sizeof(lorie_event)); e.type = ACTION_POINTER; e.pointer.state = state; e.pointer.button = button; e.pointer.x = x; e.pointer.y = y; write(backend->fds[0], &e, sizeof(lorie_event)); } void lorie_window_change_event(void __unused *b, EGLDisplay dpy, EGLNativeWindowType win, uint32_t width, uint32_t height, uint32_t mmWidth, uint32_t mmHeight) { if (!backend) return; lorie_event e; memset(&e, 0, sizeof(lorie_event)); e.type = ACTION_WIN_CHANGE; e.window_change.dpy = dpy; e.window_change.win = win; e.window_change.width = width; e.window_change.height = height; e.window_change.mmWidth = mmWidth; e.window_change.mmHeight = mmHeight; write(backend->fds[0], &e, sizeof(lorie_event)); } void lorie_jni_window_change_event(void __unused *b, JavaVM* jvm, jobject surface, uint32_t width, uint32_t height) { if (!backend) return; lorie_event e; memset(&e, 0, sizeof(lorie_event)); e.type = ACTION_JNI_WIN_CHANGE; e.jni_window_change.jvm = jvm; e.jni_window_change.surface = surface; e.jni_window_change.width = width; e.jni_window_change.height = height; write(backend->fds[0], &e, sizeof(lorie_event)); } static int has_data(int socket) { int count = 0; ioctl(socket, FIONREAD, &count); return count; } JNIEXPORT jlong JNICALL Java_com_termux_wtermux_LorieService_createLorieThread(JNIEnv __unused *env, jobject __unused instance) { pthread_t thread; //void *server = NULL; setenv("XDG_RUNTIME_DIR", "/data/data/com.termux/files/usr/tmp", 1); pthread_create(&thread, NULL, (void *(*)(void *))lorie_start, NULL); while(backend == NULL) { usleep(10); } //return (jlong) compositor; return 1; } JNIEXPORT void JNICALL Java_com_termux_wtermux_LorieService_windowChanged(JNIEnv *env, jobject __unused instance, jlong __unused jcompositor, jobject jsurface, jint width, jint height, jint mmWidth, jint mmHeight) { if (backend == NULL) return; #if 1 EGLNativeWindowType win = ANativeWindow_fromSurface(env, jsurface); if (win == NULL) { LOGE("Surface is invalid"); lorie_window_change_event(backend, NULL, NULL, (uint32_t) width, (uint32_t) height, (uint32_t) mmWidth, (uint32_t) mmHeight); return; } lorie_window_change_event(backend, NULL, win, (uint32_t) width, (uint32_t) height, (uint32_t) mmWidth, (uint32_t) mmHeight); #else JavaVM* jvm; (*env)->GetJavaVM(env, &jvm); if (jvm == NULL) { LOGE("Error getting jvm instance"); return; } jobject surface = (*env)->NewGlobalRef(env, jsurface); if (surface == NULL) { LOGE("Error creating global reference of Surface object"); return; } lorie_jni_window_change_event(backend, jvm, surface, width, height); #endif } JNIEXPORT void JNICALL Java_com_termux_wtermux_LorieService_onTouch(JNIEnv __unused *env, jobject __unused instance, jlong __unused jcompositor, jint type, jint button, jint x, jint y) { if (backend == NULL) return; lorie_pointer_event(backend, (uint8_t) type, (uint16_t) button, (uint32_t) x, (uint32_t) y); } int android_keycode_to_linux_event_code(int keyCode, int *eventCode, int *shift, char **sym); void get_character_data(char** layout, int *shift, int *eventCode, char *ch); JNIEXPORT void JNICALL Java_com_termux_wtermux_LorieService_onKey(JNIEnv *env, jobject instance, jlong compositor, jint type, jint keyCode, jint jshift, jstring characters_) { char *characters = NULL; int eventCode = 0; int shift = jshift; if (characters_ != NULL) characters = (char*) (*env)->GetStringUTFChars(env, characters_, 0); if (keyCode && !characters) { android_keycode_to_linux_event_code(keyCode, &eventCode, &shift, &characters); if (backend->xkb_names.layout != "us") { lorie_layout_change_event(backend, "us"); } } if (!keyCode && characters) { char *layout = NULL; get_character_data(&layout, &shift, &eventCode, characters); if (layout && backend->xkb_names.layout != layout) { lorie_layout_change_event(backend, layout); } } LOGE("Keyboard input: keyCode: %d; eventCode: %d; characters: %s; shift: %d, type: %d", keyCode, eventCode, characters, shift, type); if (!eventCode) return; if (shift) lorie_key_event(backend, (uint8_t) WL_KEYBOARD_KEY_STATE_PRESSED, (uint16_t) 42); // Send KEY_LEFTSHIFT // For some reason Android do not send ACTION_DOWN for non-English characters if (characters) lorie_key_event(backend, (uint8_t) WL_KEYBOARD_KEY_STATE_PRESSED, (uint16_t) eventCode); lorie_key_event(backend, (uint8_t) type, (uint16_t) eventCode); if (shift) lorie_key_event(backend, (uint8_t) WL_KEYBOARD_KEY_STATE_RELEASED, (uint16_t) 42); // Send KEY_LEFTSHIFT if (characters_ != NULL) (*env)->ReleaseStringUTFChars(env, characters_, characters); } //////////////////////////////////////////////////////////////////////// void backend_init (struct callbacks *callbacks) { backend = calloc (1, sizeof(struct backend_android)); if (backend == NULL) { LOGE("Can not allocate backend_android"); return; } backend->callbacks = *callbacks; if (socketpair(PF_LOCAL, SOCK_STREAM, 0, backend->fds)) { } // Setup EGL EGLint num_configs_returned; backend->dpy = eglGetDisplay (EGL_DEFAULT_DISPLAY); checkEGLError(); eglInitialize (backend->dpy, NULL, NULL); checkEGLError(); eglBindAPI (EGL_OPENGL_ES_API); eglChooseConfig (backend->dpy, sfcattr, &backend->config, 1, &num_configs_returned); checkEGLError(); backend->ctx = eglCreateContext (backend->dpy, backend->config, EGL_NO_CONTEXT, ctxattr); checkEGLError(); eglMakeCurrent(backend->dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, backend->ctx); checkEGLError(); if (backend->xkb_context == NULL) { backend->xkb_context = xkb_context_new(0); if (backend->xkb_context == NULL) { LOGE("failed to create XKB context\n"); return; } } backend->xkb_names.rules = strdup("evdev"); backend->xkb_names.model = strdup("pc105"); backend->xkb_names.layout = strdup("us"); backend->xkb_keymap = xkb_keymap_new_from_names(backend->xkb_context, &backend->xkb_names, 0); if (backend->xkb_keymap == NULL) { LOGE("failed to compile global XKB keymap\n"); LOGE(" tried rules %s, model %s, layout %s, variant %s, " "options %s\n", backend->xkb_names.rules, backend->xkb_names.model, backend->xkb_names.layout, backend->xkb_names.variant, backend->xkb_names.options); return; } } EGLDisplay __unused backend_get_egl_display (void) { return backend->dpy; } void backend_swap_buffers (void) { eglSwapBuffers(backend->dpy, backend->sfc); checkEGLError(); } void backend_dispatch_nonblocking (void) { lorie_event ev; while (has_data(backend->fds[1]) >= (int) sizeof(lorie_event)) { read(backend->fds[1], &ev, sizeof(lorie_event)); switch (ev.type) { case ACTION_KEY: if (backend->callbacks.key) backend->callbacks.key(ev.key.key, ev.key.state); break; case ACTION_POINTER: if (backend->callbacks.mouse_motion) backend->callbacks.mouse_motion(ev.pointer.x, ev.pointer.y); if (ev.pointer.state != WL_POINTER_MOTION) { if (backend->callbacks.mouse_button) backend->callbacks.mouse_button(ev.pointer.button, ev.pointer.state); } break; case ACTION_WIN_CHANGE: if (ev.window_change.win != backend->win) { if (backend->sfc) eglDestroySurface(backend->dpy, backend->sfc); backend->sfc = eglCreateWindowSurface(backend->dpy, backend->config, ev.window_change.win, NULL); checkEGLError(); backend->win = ev.window_change.win; } checkEGLError(); eglMakeCurrent(backend->dpy, backend->sfc, backend->sfc, backend->ctx); checkEGLError(); if (backend->callbacks.resize) backend->callbacks.resize(ev.window_change.width, ev.window_change.height, ev.window_change.mmWidth, ev.window_change.mmHeight); break; case ACTION_LAYOUT_CHANGE: xkb_keymap_unref(backend->xkb_keymap); backend->xkb_names.layout = ev.layout_change.layout; backend->xkb_keymap = xkb_keymap_new_from_names(backend->xkb_context, &backend->xkb_names, 0); if (backend->xkb_keymap == NULL) { LOGE("failed to compile global XKB keymap\n"); LOGE(" tried rules %s, model %s, layout %s, variant %s, " "options %s\n", backend->xkb_names.rules, backend->xkb_names.model, backend->xkb_names.layout, backend->xkb_names.variant, backend->xkb_names.options); return; } if (backend->callbacks.keymap) backend->callbacks.keymap(); #if 0 #ifdef __ANDROID__ case ACTION_JNI_WIN_CHANGE: { JavaVM* jvm = ev.jni_window_change.jvm; JNIEnv* env; (*jvm)->AttachCurrentThread(jvm, (void**)&env, NULL); if (!jvm || !env) { weston_log("Failed getting JNIEnv\n"); return -1; } b->edpy = eglGetDisplay(EGL_DEFAULT_DISPLAY); b->ewin = ANativeWindow_fromSurface(env, ev.jni_window_change.surface); if (b->ewin == NULL) { weston_log("Error getting EGLNativeWindowType from Java Surface object\n"); return -1; } struct weston_mode mode = b->output->mode; if (mode.width == ev.jni_window_change.width && mode.height == ev.jni_window_change.height) break; mode.width = ev.jni_window_change.width; mode.height = ev.jni_window_change.height; if (weston_output_mode_set_native(&b->output->base, &mode, b->output->scale) < 0) weston_log("Mode switch failed\n"); break; } #endif #endif default:break; } } } void backend_wait_for_events (int wayland_fd) { if (!backend) return; struct pollfd fds[2] = {{backend->fds[1], POLLIN}, {wayland_fd, POLLIN}}; poll (fds, 2, -1); } void backend_get_keymap (int *fd, int *size) { LOGI("Locale: %s", backend->xkb_names.layout); char *string = xkb_keymap_get_as_string (backend->xkb_keymap, XKB_KEYMAP_FORMAT_TEXT_V1); *size = strlen (string) + 1; char *xdg_runtime_dir = "/data/data/com.termux/files/usr/tmp"; *fd = open (xdg_runtime_dir, O_TMPFILE|O_RDWR|O_EXCL, 0600); ftruncate (*fd, *size); char *map = mmap (NULL, *size, PROT_READ|PROT_WRITE, MAP_SHARED, *fd, 0); strcpy (map, string); munmap (map, *size); free (string); } long backend_get_timestamp (void) { struct timespec t; clock_gettime (CLOCK_MONOTONIC, &t); return t.tv_sec * 1000 + t.tv_nsec / 1000000; } void backend_get_dimensions(uint32_t *width, uint32_t *height) { if (width) *width = 480; if (height) *height = 800; } #pragma clang diagnostic pop