termux-x11/app/src/main/jni/lorie/backend-android.c

468 lines
14 KiB
C

#pragma clang diagnostic push
#pragma ide diagnostic ignored "readability-isolate-declaration"
#pragma ide diagnostic ignored "cppcoreguidelines-avoid-magic-numbers"
#include <unistd.h>
#include <pthread.h>
#include <poll.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <wayland-server.h>
#include <xkbcommon/xkbcommon.h>
#include <jni.h>
#include <android/native_window_jni.h>
#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