mirror of
https://github.com/vectras-team/termux-x11.git
synced 2025-02-17 13:21:44 +00:00
Cursor is not visible here. Replacing rendering with GL with blitting to Surface.
This commit is contained in:
parent
a868f37c8a
commit
3794838b76
@ -13,6 +13,7 @@ import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.net.Uri;
|
||||
@ -41,7 +42,10 @@ import android.view.Surface;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import com.termux.x11.utils.KeyboardUtils;
|
||||
|
||||
@ -52,7 +56,7 @@ import java.io.InputStream;
|
||||
|
||||
@SuppressWarnings({"ConstantConditions", "SameParameterValue", "SdCardPath"})
|
||||
@SuppressLint({"ClickableViewAccessibility", "StaticFieldLeak"})
|
||||
public class LorieService extends Service {
|
||||
public class LorieService extends Service implements View.OnApplyWindowInsetsListener {
|
||||
static final String LAUNCHED_BY_COMPATION = "com.termux.x11.launched_by_companion";
|
||||
static final String ACTION_STOP_SERVICE = "com.termux.x11.service_stop";
|
||||
static final String ACTION_START_FROM_ACTIVITY = "com.termux.x11.start_from_activity";
|
||||
@ -205,13 +209,12 @@ public class LorieService extends Service {
|
||||
int mode = Integer.parseInt(preferences.getString("touchMode", "3"));
|
||||
instance.mTP.setMode(mode);
|
||||
|
||||
if (preferences.getBoolean("showAdditionalKbd", true)) {
|
||||
act.getWindow().getDecorView().setOnApplyWindowInsetsListener(act);
|
||||
act.getWindow().getDecorView().setOnApplyWindowInsetsListener(act);
|
||||
|
||||
if (preferences.getBoolean("showAdditionalKbd", true))
|
||||
act.kbd.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
act.getWindow().getDecorView().setOnApplyWindowInsetsListener(null);
|
||||
else
|
||||
act.kbd.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
Log.e("LorieService", "Preferences changed");
|
||||
}
|
||||
@ -295,7 +298,7 @@ public class LorieService extends Service {
|
||||
return instance;
|
||||
}
|
||||
|
||||
void setListeners(@NonNull SurfaceView view) {
|
||||
void setListeners(@NonNull SurfaceView view, @NonNull SurfaceView cursor) {
|
||||
Context a = view.getRootView().findViewById(android.R.id.content).getContext();
|
||||
if (!(a instanceof MainActivity)) {
|
||||
Log.e("LorieService", "Context is not an activity!!!");
|
||||
@ -308,7 +311,7 @@ public class LorieService extends Service {
|
||||
view.requestFocus();
|
||||
|
||||
listener.svc = this;
|
||||
listener.setAsListenerTo(view);
|
||||
listener.setAsListenerTo(view, cursor);
|
||||
|
||||
mTP = new TouchParser(view, listener);
|
||||
onPreferencesChanged();
|
||||
@ -328,13 +331,24 @@ public class LorieService extends Service {
|
||||
LorieService svc;
|
||||
|
||||
@SuppressLint("WrongConstant")
|
||||
private void setAsListenerTo(SurfaceView view) {
|
||||
private void setAsListenerTo(SurfaceView view, SurfaceView cursor) {
|
||||
view.getHolder().addCallback(this);
|
||||
view.setOnTouchListener(this);
|
||||
view.setOnHoverListener(this);
|
||||
view.setOnGenericMotionListener(this);
|
||||
view.setOnKeyListener(this);
|
||||
surfaceChanged(view.getHolder(), PixelFormat.UNKNOWN, view.getWidth(), view.getHeight());
|
||||
|
||||
cursor.getHolder().addCallback(new SurfaceHolder.Callback() {
|
||||
@Override public void surfaceCreated(@NonNull SurfaceHolder holder) {}
|
||||
@Override
|
||||
public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
|
||||
svc.cursorChanged(holder.getSurface());
|
||||
}
|
||||
@Override public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
|
||||
svc.cursorChanged(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void onPointerButton(int button, int state) {
|
||||
@ -436,8 +450,10 @@ public class LorieService extends Service {
|
||||
@Override
|
||||
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
||||
DisplayMetrics dm = new DisplayMetrics();
|
||||
Rect r = new Rect();
|
||||
int mmWidth, mmHeight;
|
||||
act.getWindowManager().getDefaultDisplay().getMetrics(dm);
|
||||
act.getWindow().getDecorView().getWindowVisibleDisplayFrame(r);
|
||||
|
||||
if (act.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||
mmWidth = (int) Math.round((width * 25.4) / dm.xdpi);
|
||||
@ -447,11 +463,14 @@ public class LorieService extends Service {
|
||||
mmHeight = (int) Math.round((height * 25.4) / dm.xdpi);
|
||||
}
|
||||
|
||||
svc.windowChanged(holder.getSurface(), width, height, mmWidth, mmHeight);
|
||||
svc.windowChanged(holder.getSurface(), r.right, r.bottom, mmWidth, mmHeight);
|
||||
}
|
||||
|
||||
@Override public void surfaceCreated(SurfaceHolder holder) {}
|
||||
@Override public void surfaceDestroyed(SurfaceHolder holder) {}
|
||||
@SuppressLint("WrongConstant")
|
||||
@Override public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
surfaceChanged(holder, PixelFormat.UNKNOWN, 0, 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -490,12 +509,47 @@ public class LorieService extends Service {
|
||||
};
|
||||
}
|
||||
|
||||
@SuppressLint("WrongConstant")
|
||||
@Override
|
||||
public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
|
||||
SurfaceView c = v.getRootView().findViewById(R.id.lorieView);
|
||||
SurfaceHolder h = (c != null) ? c.getHolder() : null;
|
||||
if (h != null)
|
||||
listener.surfaceChanged(h, PixelFormat.UNKNOWN, 0, 0);
|
||||
return insets;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
// It is used in native code
|
||||
void setRendererVisibility(boolean visible) {
|
||||
act.runOnUiThread(()-> act.findViewById(visible ? R.id.lorieView : R.id.stub).bringToFront());
|
||||
act.runOnUiThread(()-> act.findViewById(R.id.stub).setVisibility(visible?View.GONE:View.VISIBLE));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
// It is used in native code
|
||||
void setCursorVisibility(boolean visible) {
|
||||
act.runOnUiThread(()-> act.findViewById(R.id.cursorView).setVisibility(visible?View.VISIBLE:View.GONE));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
// It is used in native code
|
||||
void setCursorRect(int x, int y, int w, int h) {
|
||||
act.runOnUiThread(()-> {
|
||||
Log.d("POSITIONER", "pointing cursor: " + x + " " + y + " " + w + " " + h);
|
||||
SurfaceView v = act.findViewById(R.id.cursorView);
|
||||
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(w*10, h*10);
|
||||
params.leftMargin = x;
|
||||
params.topMargin = y;
|
||||
|
||||
v.setLayoutParams(params);
|
||||
v.setVisibility(View.VISIBLE);
|
||||
v.setZOrderOnTop(true);
|
||||
v.bringToFront();
|
||||
FrameLayout frm = act.findViewById(R.id.frame);
|
||||
});
|
||||
}
|
||||
|
||||
private native void cursorChanged(Surface surface);
|
||||
private native void windowChanged(Surface surface, int width, int height, int mmWidth, int mmHeight);
|
||||
private native void touchDown(int id, float x, float y);
|
||||
private native void touchMotion(int id, float x, float y);
|
||||
|
@ -11,6 +11,7 @@ import android.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.PointerIcon;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
@ -131,8 +132,9 @@ public class MainActivity extends AppCompatActivity implements View.OnApplyWindo
|
||||
|
||||
public void onLorieServiceStart(LorieService instance) {
|
||||
SurfaceView lorieView = findViewById(R.id.lorieView);
|
||||
SurfaceView cursorView = findViewById(R.id.cursorView);
|
||||
|
||||
instance.setListeners(lorieView);
|
||||
instance.setListeners(lorieView, cursorView);
|
||||
kbd.reload(keys, lorieView, LorieService.getOnKeyListener());
|
||||
}
|
||||
|
||||
@ -187,13 +189,15 @@ public class MainActivity extends AppCompatActivity implements View.OnApplyWindo
|
||||
|
||||
@Override
|
||||
public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
|
||||
if (kbd != null) {
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(LorieService.getInstance());
|
||||
if (preferences.getBoolean("showAdditionalKbd", true) && kbd != null) {
|
||||
Rect r = new Rect();
|
||||
getWindow().getDecorView().getWindowVisibleDisplayFrame(r);
|
||||
boolean isSoftKbdVisible = Objects.requireNonNull(ViewCompat.getRootWindowInsets(kbd)).isVisible(WindowInsetsCompat.Type.ime());
|
||||
kbd.setVisibility(isSoftKbdVisible ? View.VISIBLE : View.GONE);
|
||||
kbd.setY(r.bottom - kbd.getHeight());
|
||||
}
|
||||
return insets;
|
||||
|
||||
return LorieService.getInstance().onApplyWindowInsets(v, insets);
|
||||
}
|
||||
}
|
||||
|
@ -5,12 +5,10 @@ include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := lorie
|
||||
LOCAL_SRC_FILES := \
|
||||
compositor.cpp \
|
||||
lorie_egl_helper.cpp \
|
||||
lorie_message_queue.cpp \
|
||||
renderer.cpp \
|
||||
utils/log.cpp \
|
||||
android.cpp \
|
||||
\
|
||||
backend/android/android_app.cpp \
|
||||
backend/android/utils.c \
|
||||
lorie_wayland_server.cpp \
|
||||
$(wildcard $(WAYLAND_GENERATED)/*.cpp)
|
||||
|
347
app/src/main/jni/lorie/android.cpp
Normal file
347
app/src/main/jni/lorie/android.cpp
Normal file
@ -0,0 +1,347 @@
|
||||
#include <thread>
|
||||
#include "lorie_compositor.hpp"
|
||||
#include <sys/mman.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include "ashmem.h"
|
||||
#include <csignal>
|
||||
|
||||
#include <android/native_window_jni.h>
|
||||
#include <sys/socket.h>
|
||||
#include <dirent.h>
|
||||
#include <android/log.h>
|
||||
|
||||
#pragma ide diagnostic ignored "hicpp-signed-bitwise"
|
||||
#define unused __attribute__((__unused__))
|
||||
#define always_inline __attribute__((always_inline)) inline
|
||||
|
||||
JavaVM *vm{};
|
||||
|
||||
jfieldID lorie_compositor::compositor_field_id{};
|
||||
|
||||
lorie_compositor::lorie_compositor(jobject thiz): lorie_compositor() {
|
||||
this->thiz = thiz;
|
||||
self = std::thread([=, this]{
|
||||
vm->AttachCurrentThread(&env, nullptr);
|
||||
compositor_field_id = env->GetFieldID(env->GetObjectClass(thiz), "compositor", "J");
|
||||
set_renderer_visibility_id = env->GetMethodID(env->GetObjectClass(thiz), "setRendererVisibility", "(Z)V");
|
||||
set_cursor_visibility_id = env->GetMethodID(env->GetObjectClass(thiz), "setCursorVisibility", "(Z)V");
|
||||
set_cursor_rect_id =env->GetMethodID( env->GetObjectClass(thiz), "setCursorRect", "(IIII)V");
|
||||
|
||||
set_renderer_visibility = [=](bool visible) {
|
||||
env->CallVoidMethod(thiz, set_renderer_visibility_id, visible);
|
||||
};
|
||||
|
||||
set_cursor_visibility = [=](bool visible) {
|
||||
env->CallVoidMethod(thiz, set_cursor_visibility_id, visible);
|
||||
if (!visible)
|
||||
set_cursor_position = [=](int, int) {};
|
||||
else
|
||||
set_cursor_position = [=](int x, int y) {
|
||||
auto b = cursor.sfc ? any_cast<surface_data*>(cursor.sfc->user_data())->buffer : nullptr;
|
||||
int sx = x - cursor.hotspot_x;
|
||||
int sy = y - cursor.hotspot_y;
|
||||
int w = b ? b->shm_width() : 0;
|
||||
int h = b ? b->shm_width() : 0;
|
||||
env->CallVoidMethod(thiz, set_cursor_rect_id, sx, sy, w, h);
|
||||
};
|
||||
};
|
||||
|
||||
run();
|
||||
|
||||
vm->DetachCurrentThread();
|
||||
env = nullptr;
|
||||
});
|
||||
}
|
||||
|
||||
void lorie_compositor::get_keymap(int *fd, int *size) { // NOLINT(readability-convert-member-functions-to-static)
|
||||
int keymap_fd = open("/data/data/com.termux.x11/files/en_us.xkbmap", O_RDONLY);
|
||||
struct stat s = {};
|
||||
fstat(keymap_fd, &s);
|
||||
*size = s.st_size; // NOLINT(cppcoreguidelines-narrowing-conversions)
|
||||
*fd = keymap_fd;
|
||||
}
|
||||
|
||||
// For some reason both static_cast and reinterpret_cast returning 0 when casting b.bits.
|
||||
static always_inline uint32_t* cast(void* p) { union { void* a; uint32_t* b; } c {p}; return c.b; } // NOLINT(cppcoreguidelines-pro-type-member-init)
|
||||
|
||||
static always_inline void blit_exact(EGLNativeWindowType win, const uint32_t* src, int width, int height) {
|
||||
if (width == 0 || height == 0) {
|
||||
width = ANativeWindow_getWidth(win);
|
||||
height = ANativeWindow_getHeight(win);
|
||||
}
|
||||
ARect bounds{ 0, 0, width, height };
|
||||
ANativeWindow_Buffer b{};
|
||||
|
||||
ANativeWindow_acquire(win);
|
||||
auto ret = ANativeWindow_setBuffersGeometry(win, width, height, AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM);
|
||||
if (ret != 0) {
|
||||
LOGE("Failed to set buffers geometry (%d)", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = ANativeWindow_lock(win, &b, &bounds);
|
||||
if (ret != 0) {
|
||||
LOGE("Failed to lock");
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t* dst = cast(b.bits);
|
||||
if (src) {
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
//uint32_t* d = &dst[b.stride*i + j];
|
||||
uint32_t s = src[width * i + j];
|
||||
// Cast BGRA to RGBA
|
||||
dst[b.stride * i + j] = ((s & 0x0000FF) << 16) | (s & 0x00FF00) | ((s & 0xFF0000) >> 16);
|
||||
}
|
||||
}
|
||||
} else
|
||||
memset(dst, 0, b.stride*b.height);
|
||||
|
||||
ret = ANativeWindow_unlockAndPost(win);
|
||||
if (ret != 0) {
|
||||
LOGE("Failed to post");
|
||||
return;
|
||||
}
|
||||
|
||||
ANativeWindow_release(win);
|
||||
}
|
||||
|
||||
void lorie_compositor::blit(EGLNativeWindowType win, wayland::surface_t* sfc) {
|
||||
if (!win)
|
||||
return;
|
||||
|
||||
auto buffer = sfc ? any_cast<surface_data*>(sfc->user_data())->buffer : nullptr;
|
||||
if (buffer)
|
||||
blit_exact(win, cast(buffer->shm_data()), buffer->shm_width(), buffer->shm_height());
|
||||
else
|
||||
blit_exact(win, nullptr, 0, 0);
|
||||
}
|
||||
|
||||
template<class F, F f, auto defaultValue = 0> struct wrapper_impl;
|
||||
template<class R, class C, class... A, R(C::*f)(A...), auto defaultValue>
|
||||
struct wrapper_impl<R(C::*)(A...), f, defaultValue> {
|
||||
// Be careful and do passing jobjects here!!!
|
||||
[[maybe_unused]] static always_inline R execute(JNIEnv* env, jobject obj, A... args) {
|
||||
auto native = lorie_compositor::compositor_field_id ? reinterpret_cast<C*>(env->GetLongField(obj, lorie_compositor::compositor_field_id)) : nullptr;
|
||||
if (native != nullptr)
|
||||
return (native->*f)(args...);
|
||||
return static_cast<R>(defaultValue);
|
||||
}
|
||||
|
||||
[[maybe_unused]] static always_inline void queue(JNIEnv* env, jobject obj, A... args) {
|
||||
auto native = lorie_compositor::compositor_field_id ? reinterpret_cast<C*>(env->GetLongField(obj, lorie_compositor::compositor_field_id)) : nullptr;
|
||||
if (native != nullptr)
|
||||
native->post([=]{ (native->*f)(args...); });
|
||||
}
|
||||
};
|
||||
template<auto f, auto defaultValue = 0>
|
||||
auto execute = wrapper_impl<decltype(f), f, defaultValue>::execute;
|
||||
template<auto f, auto defaultValue = 0>
|
||||
auto queue = wrapper_impl<decltype(f), f, defaultValue>::queue;
|
||||
|
||||
[[maybe_unused]] static always_inline lorie_compositor* get(JNIEnv* env, jobject obj) {
|
||||
return lorie_compositor::compositor_field_id ?
|
||||
reinterpret_cast<lorie_compositor*>(env->GetLongField(obj, lorie_compositor::compositor_field_id)) :
|
||||
nullptr;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
#define JNI_DECLARE_INNER(package, classname, methodname ) \
|
||||
Java_ ## package ## _ ## classname ## _ ## methodname
|
||||
#define JNI_DECLARE(classname, methodname) \
|
||||
JNI_DECLARE_INNER(com_termux_x11, classname, methodname)
|
||||
|
||||
extern "C" JNIEXPORT jlong JNICALL
|
||||
JNI_DECLARE(LorieService, createLorieThread)(JNIEnv *env, jobject thiz) {
|
||||
#if 0
|
||||
// It is needed to redirect stderr to logcat
|
||||
setenv("WAYLAND_DEBUG", "1", 1);
|
||||
new std::thread([]{
|
||||
FILE *fp;
|
||||
int p[2];
|
||||
size_t read, len;
|
||||
char* line = nullptr;
|
||||
pipe(p);
|
||||
fp = fdopen(p[0], "r");
|
||||
|
||||
dup2(p[1], 2);
|
||||
while ((read = getline(&line, &len, fp)) != -1) {
|
||||
__android_log_write(ANDROID_LOG_VERBOSE, "WAYLAND_STDERR", line);
|
||||
}
|
||||
});
|
||||
#endif
|
||||
return (jlong) new lorie_compositor(env->NewGlobalRef(thiz));
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
JNI_DECLARE(LorieService, passWaylandFD)(JNIEnv *env, jobject thiz, jint fd) {
|
||||
LOGI("JNI: got fd %d", fd);
|
||||
execute<&lorie_compositor::add_socket_fd>(env, thiz, fd);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
JNI_DECLARE(LorieService, terminate)(JNIEnv *env, jobject obj) {
|
||||
auto b = reinterpret_cast<lorie_compositor*>(env->GetLongField(obj, lorie_compositor::compositor_field_id));
|
||||
b->terminate();
|
||||
b->self.join();
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
JNI_DECLARE(LorieService, cursorChanged)(JNIEnv *env, jobject thiz, jobject surface) {
|
||||
EGLNativeWindowType win = surface?ANativeWindow_fromSurface(env, surface):nullptr;
|
||||
auto c = get(env, thiz);
|
||||
c->post([=]{ c->cursor.win = win; });
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
JNI_DECLARE(LorieService, windowChanged)(JNIEnv *env, jobject thiz, jobject surface, jint width, jint height, jint mm_width, jint mm_height) {
|
||||
EGLNativeWindowType win = surface?ANativeWindow_fromSurface(env, surface):nullptr;
|
||||
queue<&lorie_compositor::output_resize>(env, thiz, win, width, height, mm_width, mm_height);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
JNI_DECLARE(LorieService, touchDown)(JNIEnv *env, jobject thiz, jint id, jfloat x, jfloat y) {
|
||||
queue<&lorie_compositor::touch_down>(env, thiz, static_cast<uint32_t>(id), static_cast<uint32_t>(x), static_cast<uint32_t>(y));
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
JNI_DECLARE(LorieService, touchMotion)(JNIEnv *env, jobject thiz, jint id, jfloat x, jfloat y) {
|
||||
queue<&lorie_compositor::touch_motion>(env, thiz, static_cast<uint32_t>(id), static_cast<uint32_t>(x), static_cast<uint32_t>(y));
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
JNI_DECLARE(LorieService, touchUp)(JNIEnv *env, jobject thiz, jint id) {
|
||||
queue<&lorie_compositor::touch_up>(env, thiz, id);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
JNI_DECLARE(LorieService, touchFrame)(JNIEnv *env, jobject thiz) {
|
||||
queue<&lorie_compositor::touch_frame>(env, thiz);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
JNI_DECLARE(LorieService, pointerMotion)(JNIEnv *env, jobject thiz, jint x, jint y) {
|
||||
queue<&lorie_compositor::pointer_motion>(env, thiz, x, y);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
JNI_DECLARE(LorieService, pointerScroll)(JNIEnv *env, jobject thiz, jint axis, jfloat value) {
|
||||
queue<&lorie_compositor::pointer_scroll>(env, thiz, axis, value);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
JNI_DECLARE(LorieService, pointerButton)(JNIEnv *env, jobject thiz, jint button, jint type) {
|
||||
queue<&lorie_compositor::pointer_button>(env, thiz, uint32_t(button), uint32_t(type));
|
||||
}
|
||||
|
||||
extern "C" void get_character_data(char** layout, int *shift, int *ec, char *ch);
|
||||
extern "C" void android_keycode_get_eventcode(int kc, int *ec, int *shift);
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
JNI_DECLARE(LorieService, keyboardKey)(JNIEnv *env, jobject thiz,
|
||||
jint type, jint key_code, jint jshift, jstring characters_) {
|
||||
char *characters = nullptr;
|
||||
|
||||
int event_code = 0;
|
||||
int shift = jshift;
|
||||
if (characters_ != nullptr) characters = (char*) env->GetStringUTFChars(characters_, nullptr);
|
||||
if (key_code && !characters) {
|
||||
android_keycode_get_eventcode(key_code, &event_code, &shift);
|
||||
LOGE("kc: %d ec: %d", key_code, event_code);
|
||||
}
|
||||
if (!key_code && characters) {
|
||||
char *layout = nullptr;
|
||||
get_character_data(&layout, &shift, &event_code, characters);
|
||||
}
|
||||
LOGE("Keyboard input: keyCode: %d; eventCode: %d; characters: %s; shift: %d, type: %d", key_code, event_code, characters, shift, type);
|
||||
|
||||
if (shift || jshift)
|
||||
queue<&lorie_compositor::keyboard_key>(env, thiz, 42, wayland::keyboard_key_state::pressed);
|
||||
|
||||
// For some reason Android do not send ACTION_DOWN for non-English characters
|
||||
if (characters)
|
||||
queue<&lorie_compositor::keyboard_key>(env, thiz, event_code, wayland::keyboard_key_state::pressed);
|
||||
|
||||
queue<&lorie_compositor::keyboard_key>(env, thiz, event_code, wayland::keyboard_key_state(type));
|
||||
|
||||
if (shift || jshift)
|
||||
queue<&lorie_compositor::keyboard_key>(env, thiz, 42, wayland::keyboard_key_state::released);
|
||||
|
||||
if (characters_ != nullptr) env->ReleaseStringUTFChars(characters_, characters);
|
||||
}
|
||||
|
||||
static bool sameUid(int pid) {
|
||||
char path[32] = {0};
|
||||
struct stat s = {0};
|
||||
sprintf(path, "/proc/%d", pid);
|
||||
stat(path, &s);
|
||||
return s.st_uid == getuid();
|
||||
}
|
||||
|
||||
static void killAllLogcats() {
|
||||
DIR* proc;
|
||||
struct dirent* dir_elem;
|
||||
char path[64] = {0}, link[64] = {0};
|
||||
pid_t pid, self = getpid();
|
||||
if ((proc = opendir("/proc")) == nullptr) {
|
||||
LOGE("opendir: %s", strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
while((dir_elem = readdir(proc)) != nullptr) {
|
||||
if (!(pid = (pid_t) atoi (dir_elem->d_name)) || pid == self || !sameUid(pid)) // NOLINT(cert-err34-c)
|
||||
continue;
|
||||
|
||||
memset(path, 0, sizeof(path));
|
||||
memset(link, 0, sizeof(link));
|
||||
sprintf(path, "/proc/%d/exe", pid);
|
||||
if (readlink(path, link, sizeof(link)) < 0) {
|
||||
LOGE("readlink %s: %s", path, strerror(errno));
|
||||
continue;
|
||||
}
|
||||
if (strstr(link, "/logcat") != nullptr) {
|
||||
if (kill(pid, SIGKILL) < 0) {
|
||||
LOGE("kill %d (%s): %s", pid, link, strerror);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void fork(const std::function<void()>& f) {
|
||||
switch(fork()) {
|
||||
case -1: LOGE("fork: %s", strerror(errno)); return;
|
||||
case 0: f(); return;
|
||||
default: return;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_termux_x11_LorieService_startLogcatForFd(unused JNIEnv *env, unused jclass clazz, jint fd) {
|
||||
killAllLogcats();
|
||||
|
||||
LOGI("Starting logcat with output to given fd");
|
||||
fork([]() {
|
||||
execl("/system/bin/logcat", "logcat", "-c", nullptr);
|
||||
LOGE("exec logcat: %s", strerror(errno));
|
||||
});
|
||||
|
||||
fork([fd]() {
|
||||
dup2(fd, 1);
|
||||
dup2(fd, 2);
|
||||
execl("/system/bin/logcat", "logcat", nullptr);
|
||||
LOGE("exec logcat: %s", strerror(errno));
|
||||
});
|
||||
}
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wshadow"
|
||||
extern "C" jint JNI_OnLoad(JavaVM* vm, [[maybe_unused]] void* reserved) {
|
||||
::vm = vm;
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
#pragma clang diagnostic pop
|
@ -1,305 +0,0 @@
|
||||
#include <thread>
|
||||
#include <lorie_compositor.hpp>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <ashmem.h>
|
||||
#include <csignal>
|
||||
|
||||
#include <lorie_egl_helper.hpp>
|
||||
#include <android/native_window_jni.h>
|
||||
#include <sys/socket.h>
|
||||
#include <dirent.h>
|
||||
#include <android/log.h>
|
||||
|
||||
#pragma ide diagnostic ignored "hicpp-signed-bitwise"
|
||||
#define unused __attribute__((__unused__))
|
||||
|
||||
class lorie_backend_android : public lorie_compositor {
|
||||
public:
|
||||
lorie_backend_android(JavaVM *env, jobject obj, jmethodID pId);
|
||||
|
||||
void backend_init() override;
|
||||
|
||||
void swap_buffers() override;
|
||||
|
||||
void get_keymap(int *fd, int *size) override;
|
||||
|
||||
void window_change_callback(EGLNativeWindowType win, uint32_t width, uint32_t height,
|
||||
uint32_t physical_width, uint32_t physical_height);
|
||||
|
||||
void passfd(int fd);
|
||||
|
||||
void on_egl_init();
|
||||
|
||||
LorieEGLHelper helper;
|
||||
|
||||
int keymap_fd = -1;
|
||||
std::thread self;
|
||||
|
||||
JavaVM *vm{};
|
||||
JNIEnv *env{};
|
||||
jobject thiz{};
|
||||
static jfieldID compositor_field_id;
|
||||
jmethodID set_renderer_visibility_id{};
|
||||
};
|
||||
|
||||
jfieldID lorie_backend_android::compositor_field_id{};
|
||||
|
||||
lorie_backend_android::lorie_backend_android(JavaVM *vm, jobject obj, jmethodID mid)
|
||||
: vm(vm), thiz(obj), set_renderer_visibility_id(mid), self(&lorie_compositor::start, this) {}
|
||||
|
||||
void lorie_backend_android::on_egl_init() {
|
||||
renderer.on_surface_create();
|
||||
}
|
||||
|
||||
void lorie_backend_android::backend_init() {
|
||||
if (!helper.init(EGL_DEFAULT_DISPLAY)) {
|
||||
LOGE("Failed to initialize EGL context");
|
||||
}
|
||||
|
||||
helper.onInit = [this](){ on_egl_init(); };
|
||||
|
||||
renderer.set_renderer_visibility = [=](bool visible) {
|
||||
vm->AttachCurrentThread(&env, nullptr);
|
||||
env->CallVoidMethod(thiz, set_renderer_visibility_id, visible);
|
||||
vm->DetachCurrentThread();
|
||||
};
|
||||
}
|
||||
|
||||
void lorie_backend_android::swap_buffers () {
|
||||
helper.swap();
|
||||
}
|
||||
|
||||
void lorie_backend_android::get_keymap(int *fd, int *size) {
|
||||
if (keymap_fd != -1)
|
||||
close(keymap_fd);
|
||||
|
||||
keymap_fd = open("/data/data/com.termux.x11/files/en_us.xkbmap", O_RDONLY);
|
||||
struct stat s = {};
|
||||
fstat(keymap_fd, &s);
|
||||
*size = s.st_size;
|
||||
*fd = keymap_fd;
|
||||
}
|
||||
|
||||
void lorie_backend_android::window_change_callback(EGLNativeWindowType win, uint32_t width, uint32_t height, uint32_t physical_width, uint32_t physical_height) {
|
||||
LOGV("JNI: window is changed: %p %dx%d (%dmm x %dmm)", win, width, height, physical_width, physical_height);
|
||||
helper.setWindow(win);
|
||||
post([=, this]() {
|
||||
output_resize(width, height, physical_width, physical_height);
|
||||
});
|
||||
}
|
||||
|
||||
void lorie_backend_android::passfd(int fd) {
|
||||
LOGI("JNI: got fd %d", fd);
|
||||
dpy.add_socket_fd(fd);
|
||||
}
|
||||
|
||||
template<class F, F f, auto defaultValue = 0> struct wrapper_impl;
|
||||
template<class R, class C, class... A, R(C::*f)(A...), auto defaultValue>
|
||||
struct wrapper_impl<R(C::*)(A...), f, defaultValue> {
|
||||
// Be careful and do passing jobjects here!!!
|
||||
static inline R execute(JNIEnv* env, jobject obj, A... args) {
|
||||
auto native = reinterpret_cast<C*>(env->GetLongField(obj, lorie_backend_android::compositor_field_id));
|
||||
if (native != nullptr)
|
||||
return (native->*f)(args...);
|
||||
return static_cast<R>(defaultValue);
|
||||
}
|
||||
static inline void queue(JNIEnv* env, jobject obj, A... args) {
|
||||
auto native = reinterpret_cast<C*>(env->GetLongField(obj, lorie_backend_android::compositor_field_id));
|
||||
if (native != nullptr)
|
||||
native->post([=]{ (native->*f)(args...); });
|
||||
}
|
||||
};
|
||||
template<auto f, auto defaultValue = 0>
|
||||
auto execute = wrapper_impl<decltype(f), f, defaultValue>::execute;
|
||||
template<auto f, auto defaultValue = 0>
|
||||
auto queue = wrapper_impl<decltype(f), f, defaultValue>::queue;
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
#define JNI_DECLARE_INNER(package, classname, methodname ) \
|
||||
Java_ ## package ## _ ## classname ## _ ## methodname
|
||||
#define JNI_DECLARE(classname, methodname) \
|
||||
JNI_DECLARE_INNER(com_termux_x11, classname, methodname)
|
||||
|
||||
extern "C" JNIEXPORT jlong JNICALL
|
||||
JNI_DECLARE(LorieService, createLorieThread)(JNIEnv *env, jobject thiz) {
|
||||
#if 0
|
||||
// It is needed to redirect stderr to logcat
|
||||
setenv("WAYLAND_DEBUG", "1", 1);
|
||||
new std::thread([]{
|
||||
FILE *fp;
|
||||
int p[2];
|
||||
size_t read, len;
|
||||
char* line = nullptr;
|
||||
pipe(p);
|
||||
fp = fdopen(p[0], "r");
|
||||
|
||||
dup2(p[1], 2);
|
||||
while ((read = getline(&line, &len, fp)) != -1) {
|
||||
__android_log_write(ANDROID_LOG_VERBOSE, "WAYLAND_STDERR", line);
|
||||
}
|
||||
});
|
||||
#endif
|
||||
JavaVM* vm{};
|
||||
env->GetJavaVM(&vm);
|
||||
lorie_backend_android::compositor_field_id = env->GetFieldID(env->GetObjectClass(thiz), "compositor", "J");
|
||||
jmethodID set_renderer_visibility_id = env->GetMethodID(env->GetObjectClass(thiz), "setRendererVisibility", "(Z)V");
|
||||
return (jlong) new lorie_backend_android(vm, env->NewGlobalRef(thiz),
|
||||
set_renderer_visibility_id);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
JNI_DECLARE(LorieService, passWaylandFD)(JNIEnv *env, jobject thiz, jint fd) {
|
||||
execute<&lorie_backend_android::passfd>(env, thiz, fd);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
JNI_DECLARE(LorieService, terminate)(JNIEnv *env, jobject obj) {
|
||||
auto b = reinterpret_cast<lorie_backend_android*>(env->GetLongField(obj, lorie_backend_android::compositor_field_id));
|
||||
b->terminate();
|
||||
b->self.join();
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
JNI_DECLARE(LorieService, windowChanged)(JNIEnv *env, jobject thiz, jobject jsurface, jint width, jint height, jint mm_width, jint mm_height) {
|
||||
EGLNativeWindowType win = ANativeWindow_fromSurface(env, jsurface);
|
||||
queue<&lorie_backend_android::window_change_callback>(env, thiz, win, width, height, mm_width, mm_height);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
JNI_DECLARE(LorieService, touchDown)(JNIEnv *env, jobject thiz, jint id, jfloat x, jfloat y) {
|
||||
queue<&lorie_backend_android::touch_down>(env, thiz, static_cast<uint32_t>(id), static_cast<uint32_t>(x), static_cast<uint32_t>(y));
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
JNI_DECLARE(LorieService, touchMotion)(JNIEnv *env, jobject thiz, jint id, jfloat x, jfloat y) {
|
||||
queue<&lorie_backend_android::touch_motion>(env, thiz, static_cast<uint32_t>(id), static_cast<uint32_t>(x), static_cast<uint32_t>(y));
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
JNI_DECLARE(LorieService, touchUp)(JNIEnv *env, jobject thiz, jint id) {
|
||||
queue<&lorie_backend_android::touch_up>(env, thiz, id);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
JNI_DECLARE(LorieService, touchFrame)(JNIEnv *env, jobject thiz) {
|
||||
queue<&lorie_backend_android::touch_frame>(env, thiz);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
JNI_DECLARE(LorieService, pointerMotion)(JNIEnv *env, jobject thiz, jint x, jint y) {
|
||||
queue<&lorie_backend_android::pointer_motion>(env, thiz, static_cast<uint32_t>(x), static_cast<uint32_t>(y));
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
JNI_DECLARE(LorieService, pointerScroll)(JNIEnv *env, jobject thiz, jint axis, jfloat value) {
|
||||
queue<&lorie_backend_android::pointer_scroll>(env, thiz, static_cast<uint32_t>(axis), value);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
JNI_DECLARE(LorieService, pointerButton)(JNIEnv *env, jobject thiz, jint button, jint type) {
|
||||
queue<&lorie_backend_android::pointer_button>(env, thiz, static_cast<uint32_t>(button), static_cast<uint32_t>(type));
|
||||
}
|
||||
|
||||
extern "C" void get_character_data(char** layout, int *shift, int *ec, char *ch);
|
||||
extern "C" void android_keycode_get_eventcode(int kc, int *ec, int *shift);
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
JNI_DECLARE(LorieService, keyboardKey)(JNIEnv *env, jobject thiz,
|
||||
jint type, jint key_code, jint jshift, jstring characters_) {
|
||||
char *characters = nullptr;
|
||||
|
||||
int event_code = 0;
|
||||
int shift = jshift;
|
||||
if (characters_ != nullptr) characters = (char*) env->GetStringUTFChars(characters_, nullptr);
|
||||
if (key_code && !characters) {
|
||||
android_keycode_get_eventcode(key_code, &event_code, &shift);
|
||||
LOGE("kc: %d ec: %d", key_code, event_code);
|
||||
}
|
||||
if (!key_code && characters) {
|
||||
char *layout = nullptr;
|
||||
get_character_data(&layout, &shift, &event_code, characters);
|
||||
}
|
||||
LOGE("Keyboard input: keyCode: %d; eventCode: %d; characters: %s; shift: %d, type: %d", key_code, event_code, characters, shift, type);
|
||||
|
||||
if (shift || jshift)
|
||||
queue<&lorie_backend_android::keyboard_key>(env, thiz, 42, wayland::keyboard_key_state::pressed);
|
||||
|
||||
// For some reason Android do not send ACTION_DOWN for non-English characters
|
||||
if (characters)
|
||||
queue<&lorie_backend_android::keyboard_key>(env, thiz, event_code, wayland::keyboard_key_state::pressed);
|
||||
|
||||
queue<&lorie_backend_android::keyboard_key>(env, thiz, event_code, wayland::keyboard_key_state(type));
|
||||
|
||||
if (shift || jshift)
|
||||
queue<&lorie_backend_android::keyboard_key>(env, thiz, 42, wayland::keyboard_key_state::released);
|
||||
|
||||
if (characters_ != nullptr) env->ReleaseStringUTFChars(characters_, characters);
|
||||
}
|
||||
|
||||
static bool sameUid(int pid) {
|
||||
char path[32] = {0};
|
||||
struct stat s = {0};
|
||||
sprintf(path, "/proc/%d", pid);
|
||||
stat(path, &s);
|
||||
return s.st_uid == getuid();
|
||||
}
|
||||
|
||||
static void killAllLogcats() {
|
||||
DIR* proc;
|
||||
struct dirent* dir_elem;
|
||||
char path[64] = {0}, link[64] = {0};
|
||||
pid_t pid, self = getpid();
|
||||
if ((proc = opendir("/proc")) == nullptr) {
|
||||
LOGE("opendir: %s", strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
while((dir_elem = readdir(proc)) != nullptr) {
|
||||
if (!(pid = (pid_t) atoi (dir_elem->d_name)) || pid == self || !sameUid(pid)) // NOLINT(cert-err34-c)
|
||||
continue;
|
||||
|
||||
memset(path, 0, sizeof(path));
|
||||
memset(link, 0, sizeof(link));
|
||||
sprintf(path, "/proc/%d/exe", pid);
|
||||
if (readlink(path, link, sizeof(link)) < 0) {
|
||||
LOGE("readlink %s: %s", path, strerror(errno));
|
||||
continue;
|
||||
}
|
||||
if (strstr(link, "/logcat") != nullptr) {
|
||||
if (kill(pid, SIGKILL) < 0) {
|
||||
LOGE("kill %d (%s): %s", pid, link, strerror);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void fork(const std::function<void()>& f) {
|
||||
switch(fork()) {
|
||||
case -1: LOGE("fork: %s", strerror(errno)); return;
|
||||
case 0: f(); return;
|
||||
default: return;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_termux_x11_LorieService_startLogcatForFd(unused JNIEnv *env, unused jclass clazz, jint fd) {
|
||||
killAllLogcats();
|
||||
|
||||
LOGI("Starting logcat with output to given fd");
|
||||
fork([]() {
|
||||
execl("/system/bin/logcat", "logcat", "-c", nullptr);
|
||||
LOGE("exec logcat: %s", strerror(errno));
|
||||
});
|
||||
|
||||
fork([fd]() {
|
||||
dup2(fd, 1);
|
||||
dup2(fd, 2);
|
||||
execl("/system/bin/logcat", "logcat", nullptr);
|
||||
LOGE("exec logcat: %s", strerror(errno));
|
||||
});
|
||||
}
|
@ -7,18 +7,27 @@
|
||||
|
||||
using namespace wayland;
|
||||
|
||||
lorie_compositor::lorie_compositor() :
|
||||
display(dpy),
|
||||
renderer(*this) {
|
||||
dpy.on_client = [=](client_t* client) {
|
||||
lorie_compositor::lorie_compositor() {
|
||||
on_client = [=](client_t* client) {
|
||||
client->user_data() = new client_data;
|
||||
client->on_destroy = [=] {
|
||||
if (toplevel && toplevel->client() == client)
|
||||
renderer.set_toplevel(nullptr);
|
||||
bool request_redraw{};
|
||||
|
||||
if (cursor && cursor->client() == client)
|
||||
renderer.set_cursor(nullptr, 0, 0);
|
||||
if (screen.sfc && screen.sfc->client() == client) {
|
||||
screen.sfc = nullptr;
|
||||
request_redraw = true;
|
||||
}
|
||||
|
||||
if (cursor.sfc && cursor.sfc->client() == client) {
|
||||
screen.sfc = nullptr;
|
||||
request_redraw = true;
|
||||
}
|
||||
|
||||
if (request_redraw)
|
||||
redraw(true);
|
||||
|
||||
if(!screen.sfc)
|
||||
set_renderer_visibility(false);
|
||||
LOGI("Client destroyed");
|
||||
};
|
||||
};
|
||||
@ -32,8 +41,6 @@ renderer(*this) {
|
||||
auto data = new surface_data;
|
||||
surface->user_data() = data;
|
||||
surface->on_attach = [=](buffer_t* b, int32_t, int32_t) {
|
||||
if (data->buffer)
|
||||
data->buffer->release();
|
||||
data->buffer = b;
|
||||
};
|
||||
surface->on_damage = [=](int32_t, int32_t, int32_t, int32_t) {
|
||||
@ -43,7 +50,8 @@ renderer(*this) {
|
||||
data->frame_callback = cb;
|
||||
};
|
||||
surface->on_commit = [=] {
|
||||
renderer.request_redraw();
|
||||
redraw();
|
||||
data->buffer->release();
|
||||
data->frame_callback->done(resource_t::timestamp());
|
||||
};
|
||||
surface->on__destroy = [=] {
|
||||
@ -57,14 +65,17 @@ renderer(*this) {
|
||||
|
||||
auto data = any_cast<client_data*>(client->user_data());
|
||||
|
||||
seat->on_get_pointer = [=](pointer_t* pointer) {
|
||||
seat->on_get_pointer = [=, this](pointer_t* pointer) {
|
||||
LOGV("Client requested seat pointer");
|
||||
data->pointer = pointer;
|
||||
if (toplevel)
|
||||
pointer->enter(next_serial(), toplevel, 0, 0);
|
||||
if (screen.sfc)
|
||||
pointer->enter(next_serial(), screen.sfc, 0, 0);
|
||||
|
||||
pointer->on_set_cursor = [=](uint32_t, wayland::surface_t* sfc, int32_t x, int32_t y) {
|
||||
renderer.set_cursor(sfc, (uint32_t) x, (uint32_t) y);
|
||||
cursor.sfc = sfc;
|
||||
cursor.hotspot_x = x;
|
||||
cursor.hotspot_y = y;
|
||||
|
||||
if (sfc)
|
||||
any_cast<surface_data*>(sfc->user_data())->damaged = true;
|
||||
};
|
||||
@ -89,8 +100,8 @@ renderer(*this) {
|
||||
wl_array keys{};
|
||||
wl_array_init(&keys);
|
||||
|
||||
if (toplevel)
|
||||
kbd->enter(next_serial(), toplevel, &keys);
|
||||
if (screen.sfc)
|
||||
kbd->enter(next_serial(), screen.sfc, &keys);
|
||||
|
||||
kbd->on_release = [=] {
|
||||
kbd->destroy();
|
||||
@ -119,178 +130,178 @@ renderer(*this) {
|
||||
shell->on_set_toplevel = [=] () {
|
||||
wl_array keys{};
|
||||
wl_array_init(&keys);
|
||||
renderer.set_toplevel(sfc);
|
||||
screen.sfc = sfc;
|
||||
redraw();
|
||||
set_renderer_visibility(sfc != nullptr);
|
||||
|
||||
if (data->pointer)
|
||||
data->pointer->enter(next_serial(),sfc, 0, 0);
|
||||
|
||||
if(data->kbd)
|
||||
data->kbd->enter(next_serial(), sfc, &keys);
|
||||
|
||||
shell->configure(shell_surface_resize::none, renderer.width, renderer.height);
|
||||
auto buffer = sfc ? any_cast<surface_data*>(sfc->user_data())->buffer : nullptr;
|
||||
if (buffer)
|
||||
shell->configure(shell_surface_resize::none, buffer->shm_width(), buffer->shm_height());
|
||||
};
|
||||
};
|
||||
};
|
||||
global_xdg_wm_base.on_bind = [=, this](client_t* client, xdg_wm_base_t* wm_base) {
|
||||
wm_base->on_get_xdg_surface = [=, this](xdg_surface_t* xdg_surface, surface_t* sfc) {
|
||||
xdg_surface->on_get_toplevel = [=, this](xdg_toplevel_t*) {
|
||||
auto data = any_cast<client_data*>(toplevel->client()->user_data());
|
||||
auto data = any_cast<client_data*>(screen.sfc->client()->user_data());
|
||||
wl_array keys{};
|
||||
wl_array_init(&keys);
|
||||
renderer.set_toplevel(sfc);
|
||||
screen.sfc = sfc;
|
||||
if (data->pointer)
|
||||
data->pointer->enter(next_serial(),sfc, 0, 0);
|
||||
if (data->kbd)
|
||||
data->kbd->enter(next_serial(), sfc, &keys);
|
||||
redraw();
|
||||
};
|
||||
};
|
||||
wm_base->on__destroy = [=]() { wm_base->destroy(); };
|
||||
};
|
||||
|
||||
LogInit();
|
||||
LOGV("Starting compositor");
|
||||
wl_display_init_shm (*this);
|
||||
add_fd_listener(queue.get_fd(), WL_EVENT_READABLE, [&](int, uint){ queue.run(); return 0; });
|
||||
}
|
||||
|
||||
int proc(int fd, uint32_t mask, void *data) {
|
||||
lorie_compositor *b = static_cast<lorie_compositor*>(data);
|
||||
if (b == nullptr) {LOGF("b == nullptr"); return 0;}
|
||||
void lorie_compositor::redraw(bool force) {
|
||||
if (screen.win) {
|
||||
auto data = screen.sfc ? any_cast<surface_data *>(screen.sfc->user_data()) : nullptr;
|
||||
bool damaged = (data && data->damaged) || force;
|
||||
if (damaged)
|
||||
blit(screen.win, screen.sfc);
|
||||
}
|
||||
|
||||
b->queue.run();
|
||||
return 0;
|
||||
};
|
||||
|
||||
void lorie_compositor::start() {
|
||||
LogInit();
|
||||
LOGV("Starting compositor");
|
||||
wl_display_add_socket_auto(display);
|
||||
|
||||
wl_event_loop_add_fd(wl_display_get_event_loop(display), queue.get_fd(), WL_EVENT_READABLE, &proc, this);
|
||||
|
||||
wl_display_init_shm (display);
|
||||
|
||||
backend_init();
|
||||
|
||||
wl_display_run(display);
|
||||
if (cursor.win) {
|
||||
auto data = cursor.sfc ? any_cast<surface_data *>(cursor.sfc->user_data()) : nullptr;
|
||||
bool damaged = (data && data->damaged) || force;
|
||||
if (damaged)
|
||||
blit(cursor.win, cursor.sfc);
|
||||
}
|
||||
}
|
||||
|
||||
void lorie_compositor::post(std::function<void()> f) {
|
||||
queue.write(f);
|
||||
queue.write(std::move(f));
|
||||
}
|
||||
|
||||
void lorie_compositor::terminate() {
|
||||
LOGI("JNI: requested termination");
|
||||
if (display != nullptr)
|
||||
wl_display_terminate(display);
|
||||
}
|
||||
|
||||
void lorie_compositor::output_resize(int width, int height, uint32_t physical_width, uint32_t physical_height) {
|
||||
void lorie_compositor::output_resize(EGLNativeWindowType win, int real_width, int real_height, int physical_width, int physical_height) {
|
||||
// Xwayland segfaults without that line
|
||||
if (width == 0 || height == 0 || physical_width == 0 || physical_height == 0) return;
|
||||
renderer.resize(width, height, physical_width, physical_height);
|
||||
post([this]() {
|
||||
renderer.request_redraw();
|
||||
});
|
||||
LOGV("JNI: window is changed: %p %dx%d (%dmm x %dmm)", win, real_width, real_height, physical_width, physical_height);
|
||||
if (real_width == 0 || real_height == 0 || physical_width == 0 || physical_height == 0) return;
|
||||
screen.real_width = real_width;
|
||||
screen.real_height = real_height;
|
||||
screen.physical_width = physical_width;
|
||||
screen.physical_height = physical_height;
|
||||
screen.win = win;
|
||||
|
||||
if (screen.sfc) {
|
||||
auto data = any_cast<client_data*>(screen.sfc->client()->user_data());
|
||||
report_mode(data->output);
|
||||
}
|
||||
}
|
||||
|
||||
void lorie_compositor::report_mode(wayland::output_t* output) const {
|
||||
output->geometry(0, 0, renderer.physical_width, renderer.physical_height, output_subpixel::unknown, "Lorie", "none", output_transform::normal);
|
||||
output->geometry(0, 0, screen.physical_width, screen.physical_height, output_subpixel::unknown, "Lorie", "none", output_transform::normal);
|
||||
output->scale(1.0);
|
||||
output->mode(output_mode::current | output_mode::preferred, renderer.width, renderer.height, 60000);
|
||||
output->mode(output_mode::current | output_mode::preferred, screen.real_width, screen.real_height, 60000);
|
||||
output->done();
|
||||
}
|
||||
|
||||
void lorie_compositor::touch_down(uint32_t id, uint32_t x, uint32_t y) {
|
||||
LOGV("JNI: touch down");
|
||||
if (!toplevel)
|
||||
if (!screen.sfc)
|
||||
return;
|
||||
|
||||
auto data = any_cast<client_data*>(toplevel->client()->user_data());
|
||||
auto data = any_cast<client_data*>(screen.sfc->client()->user_data());
|
||||
|
||||
data->touch->down(next_serial(), resource_t::timestamp(), toplevel, id, x, y);
|
||||
renderer.set_cursor_visibility(false);
|
||||
data->touch->down(next_serial(), resource_t::timestamp(), screen.sfc, id, x, y);
|
||||
set_cursor_visibility(false);
|
||||
}
|
||||
|
||||
void lorie_compositor::touch_motion(uint32_t id, uint32_t x, uint32_t y) {
|
||||
LOGV("JNI: touch motion");
|
||||
if (!toplevel)
|
||||
if (!screen.sfc)
|
||||
return;
|
||||
|
||||
auto data = any_cast<client_data*>(toplevel->client()->user_data());
|
||||
auto data = any_cast<client_data*>(screen.sfc->client()->user_data());
|
||||
|
||||
data->touch->motion(resource_t::timestamp(), id, x, y);
|
||||
renderer.set_cursor_visibility(false);
|
||||
set_cursor_visibility(false);
|
||||
}
|
||||
|
||||
void lorie_compositor::touch_up(uint32_t id) {
|
||||
LOGV("JNI: touch up");
|
||||
if (!toplevel)
|
||||
if (!screen.sfc)
|
||||
return;
|
||||
|
||||
auto data = any_cast<client_data*>(toplevel->client()->user_data());
|
||||
auto data = any_cast<client_data*>(screen.sfc->client()->user_data());
|
||||
|
||||
data->touch->up(next_serial(), resource_t::timestamp(), id);
|
||||
renderer.set_cursor_visibility(false);
|
||||
set_cursor_visibility(false);
|
||||
}
|
||||
|
||||
void lorie_compositor::touch_frame() {
|
||||
LOGV("JNI: touch frame");
|
||||
if (!toplevel)
|
||||
if (!screen.sfc)
|
||||
return;
|
||||
|
||||
auto data = any_cast<client_data*>(toplevel->client()->user_data());
|
||||
auto data = any_cast<client_data*>(screen.sfc->client()->user_data());
|
||||
|
||||
data->touch->frame();
|
||||
renderer.set_cursor_visibility(false);
|
||||
set_cursor_visibility(false);
|
||||
}
|
||||
|
||||
void lorie_compositor::pointer_motion(uint32_t x, uint32_t y) {
|
||||
void lorie_compositor::pointer_motion(int x, int y) {
|
||||
LOGV("JNI: pointer motion %dx%d", x, y);
|
||||
if (!toplevel)
|
||||
if (!screen.sfc)
|
||||
return;
|
||||
|
||||
auto data = any_cast<client_data*>(toplevel->client()->user_data());
|
||||
auto data = any_cast<client_data*>(screen.sfc->client()->user_data());
|
||||
|
||||
data->pointer->motion(resource_t::timestamp(), x, y);
|
||||
data->pointer->motion(resource_t::timestamp(), double(x), double(y));
|
||||
data->pointer->frame();
|
||||
|
||||
renderer.set_cursor_visibility(true);
|
||||
renderer.cursor_move(x, y);
|
||||
set_cursor_visibility(true);
|
||||
set_cursor_position(x, y);
|
||||
}
|
||||
|
||||
void lorie_compositor::pointer_scroll(uint32_t axis, float value) {
|
||||
void lorie_compositor::pointer_scroll(int axis, float value) {
|
||||
LOGV("JNI: pointer scroll %d %f", axis, value);
|
||||
if (!toplevel)
|
||||
if (!screen.sfc)
|
||||
return;
|
||||
|
||||
auto data = any_cast<client_data*>(toplevel->client()->user_data());
|
||||
auto data = any_cast<client_data*>(screen.sfc->client()->user_data());
|
||||
|
||||
data->pointer->axis_discrete(pointer_axis(axis), (value >= 0) ? 1 : -1);
|
||||
data->pointer->axis(resource_t::timestamp(), pointer_axis(axis), value);
|
||||
data->pointer->frame();
|
||||
renderer.set_cursor_visibility(true);
|
||||
set_cursor_visibility(true);
|
||||
}
|
||||
|
||||
void lorie_compositor::pointer_button(uint32_t button, uint32_t state) {
|
||||
void lorie_compositor::pointer_button(int button, int state) {
|
||||
LOGV("JNI: pointer button %d type %d", button, state);
|
||||
if (!toplevel)
|
||||
if (!screen.sfc)
|
||||
return;
|
||||
|
||||
auto data = any_cast<client_data*>(toplevel->client()->user_data());
|
||||
auto data = any_cast<client_data*>(screen.sfc->client()->user_data());
|
||||
|
||||
LOGI("pointer button: %d %d", button, state);
|
||||
data->pointer->button(next_serial(), resource_t::timestamp(), button, pointer_button_state(state));
|
||||
data->pointer->frame();
|
||||
renderer.set_cursor_visibility(true);
|
||||
set_cursor_visibility(true);
|
||||
}
|
||||
|
||||
void lorie_compositor::keyboard_key(uint32_t key, keyboard_key_state state) {
|
||||
if (!toplevel)
|
||||
if (!screen.sfc)
|
||||
return;
|
||||
|
||||
auto data = any_cast<client_data*>(toplevel->client()->user_data());
|
||||
auto data = any_cast<client_data*>(screen.sfc->client()->user_data());
|
||||
data->kbd->key (next_serial(), resource_t::timestamp(), key, keyboard_key_state(state));
|
||||
}
|
||||
|
||||
uint32_t lorie_compositor::next_serial() const {
|
||||
if (display == nullptr) return 0;
|
||||
|
||||
return wl_display_next_serial(display);
|
||||
}
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
@ -2,65 +2,91 @@
|
||||
#include <lorie_wayland_server.hpp>
|
||||
#include <wayland-server-protocol.hpp>
|
||||
#include <xdg-shell-server-protocol.hpp>
|
||||
#include <lorie_renderer.hpp>
|
||||
#include <lorie_message_queue.hpp>
|
||||
#include "log.h"
|
||||
|
||||
class lorie_compositor {
|
||||
#ifdef ANDROID
|
||||
#include <android/native_window.h>
|
||||
#include <jni.h>
|
||||
#include <thread>
|
||||
|
||||
typedef ANativeWindow* EGLNativeWindowType;
|
||||
#else
|
||||
typedef void* EGLNativeWindowType;
|
||||
#endif
|
||||
|
||||
class lorie_compositor: public wayland::display_t {
|
||||
public:
|
||||
lorie_compositor();
|
||||
lorie_compositor();
|
||||
// compositor features
|
||||
void start();
|
||||
void post(std::function<void()> f);
|
||||
void start();
|
||||
void post(std::function<void()> f);
|
||||
void redraw(bool force = false);
|
||||
|
||||
void terminate();
|
||||
void output_resize(int width, int height, uint32_t physical_width, uint32_t physical_height);
|
||||
void report_mode(wayland::output_t* output) const;
|
||||
void output_resize(EGLNativeWindowType win, int real_width, int real_height, int physical_width, int physical_height);
|
||||
void report_mode(wayland::output_t* output) const;
|
||||
|
||||
void touch_down(uint32_t id, uint32_t x, uint32_t y);
|
||||
void touch_motion(uint32_t id, uint32_t x, uint32_t y);
|
||||
void touch_up(uint32_t id);
|
||||
void touch_frame();
|
||||
void pointer_motion(uint32_t x, uint32_t y); // absolute values
|
||||
void pointer_scroll(uint32_t axis, float value);
|
||||
void pointer_button(uint32_t button, uint32_t state);
|
||||
void keyboard_key(uint32_t key, wayland::keyboard_key_state state);
|
||||
void touch_down(uint32_t id, uint32_t x, uint32_t y);
|
||||
void touch_motion(uint32_t id, uint32_t x, uint32_t y);
|
||||
void touch_up(uint32_t id);
|
||||
void touch_frame();
|
||||
void pointer_motion(int x, int y); // absolute values
|
||||
void pointer_scroll(int axis, float value);
|
||||
void pointer_button(int button, int state);
|
||||
void keyboard_key(uint32_t key, wayland::keyboard_key_state state);
|
||||
|
||||
struct client_data {
|
||||
wayland::output_t* output{};
|
||||
wayland::pointer_t* pointer{};
|
||||
wayland::keyboard_t* kbd{};
|
||||
wayland::touch_t* touch{};
|
||||
};
|
||||
struct client_data {
|
||||
wayland::output_t* output{};
|
||||
wayland::pointer_t* pointer{};
|
||||
wayland::keyboard_t* kbd{};
|
||||
wayland::touch_t* touch{};
|
||||
};
|
||||
|
||||
struct surface_data {
|
||||
uint32_t x{}, y{};
|
||||
bool damaged{};
|
||||
wayland::buffer_t *buffer{};
|
||||
wayland::callback_t *frame_callback{};
|
||||
};
|
||||
struct surface_data {
|
||||
uint32_t x{}, y{};
|
||||
bool damaged{};
|
||||
wayland::buffer_t *buffer{};
|
||||
wayland::callback_t *frame_callback{};
|
||||
};
|
||||
|
||||
lorie_renderer renderer;
|
||||
|
||||
wayland::surface_t* toplevel{};
|
||||
wayland::surface_t* cursor{};
|
||||
struct {
|
||||
int real_width, real_height;
|
||||
int physical_width, physical_height;
|
||||
wayland::surface_t* sfc;
|
||||
EGLNativeWindowType win;
|
||||
} screen{};
|
||||
struct {
|
||||
int width, height;
|
||||
int hotspot_x, hotspot_y;
|
||||
int x, y;
|
||||
wayland::surface_t* sfc;
|
||||
EGLNativeWindowType win;
|
||||
} cursor{};
|
||||
static void blit(EGLNativeWindowType win, wayland::surface_t* sfc);
|
||||
std::function<void(bool)> set_renderer_visibility = [](bool){};
|
||||
std::function<void(bool)> set_cursor_visibility = [](bool){};
|
||||
std::function<void(int, int)> set_cursor_position = [](int, int){};
|
||||
|
||||
// backend features
|
||||
virtual void backend_init() = 0;
|
||||
virtual void swap_buffers() = 0;
|
||||
virtual void get_keymap(int *fd, int *size) = 0;
|
||||
virtual ~lorie_compositor() {};
|
||||
void get_keymap(int *fd, int *size);
|
||||
|
||||
//private:
|
||||
wayland::display_t dpy;
|
||||
wayland::global_compositor_t global_compositor{dpy};
|
||||
wayland::global_seat_t global_seat{dpy};
|
||||
wayland::global_output_t global_output{dpy};
|
||||
wayland::global_shell_t global_shell{dpy};
|
||||
wayland::global_xdg_wm_base_t global_xdg_wm_base{dpy};
|
||||
struct wl_display *display = nullptr;
|
||||
wayland::global_compositor_t global_compositor{this};
|
||||
wayland::global_seat_t global_seat{this};
|
||||
wayland::global_output_t global_output{this};
|
||||
wayland::global_shell_t global_shell{this};
|
||||
wayland::global_xdg_wm_base_t global_xdg_wm_base{this};
|
||||
|
||||
lorie_message_queue queue;
|
||||
private:
|
||||
uint32_t next_serial() const;
|
||||
|
||||
#ifdef ANDROID
|
||||
JNIEnv *env{};
|
||||
jobject thiz{};
|
||||
static jfieldID compositor_field_id;
|
||||
jmethodID set_renderer_visibility_id{};
|
||||
jmethodID set_cursor_visibility_id{};
|
||||
jmethodID set_cursor_rect_id{};
|
||||
lorie_compositor(jobject thiz);
|
||||
std::thread self;
|
||||
#endif
|
||||
};
|
||||
|
@ -1,27 +0,0 @@
|
||||
#include <EGL/egl.h>
|
||||
#include <EGL/eglext.h>
|
||||
#include <functional>
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
#define EGL_NO_WINDOW nullptr
|
||||
#else
|
||||
#define EGL_NO_WINDOW 0
|
||||
#endif
|
||||
|
||||
class LorieEGLHelper {
|
||||
public:
|
||||
EGLDisplay dpy = EGL_NO_DISPLAY;
|
||||
EGLConfig cfg;
|
||||
|
||||
EGLSurface sfc = EGL_NO_SURFACE;
|
||||
EGLContext ctx = EGL_NO_CONTEXT;
|
||||
EGLNativeWindowType win = EGL_NO_WINDOW;
|
||||
|
||||
std::function<void()> onInit = nullptr;
|
||||
std::function<void()> onUninit = nullptr;
|
||||
|
||||
bool init(EGLNativeDisplayType display);
|
||||
bool setWindow(EGLNativeWindowType window);
|
||||
void swap();
|
||||
void uninit();
|
||||
};
|
@ -1,73 +0,0 @@
|
||||
#pragma once
|
||||
#include <GLES2/gl2.h>
|
||||
#include <limits.h>
|
||||
|
||||
class lorie_renderer;
|
||||
class lorie_texture {
|
||||
private:
|
||||
lorie_renderer* r = nullptr;
|
||||
bool damaged = false;
|
||||
public:
|
||||
lorie_texture();
|
||||
int width{}, height{};
|
||||
void *data{};
|
||||
void set_data(lorie_renderer* renderer, uint32_t width, uint32_t height, void *data);
|
||||
void damage(int32_t x, int32_t y, int32_t width, int32_t height);
|
||||
void uninit();
|
||||
void reinit();
|
||||
bool valid();
|
||||
private:
|
||||
GLuint id = UINT_MAX;
|
||||
void draw(float x0, float y0, float x1, float y1);
|
||||
|
||||
friend class lorie_renderer;
|
||||
};
|
||||
|
||||
class lorie_compositor;
|
||||
class lorie_renderer {
|
||||
public:
|
||||
lorie_renderer(lorie_compositor& compositor);
|
||||
void request_redraw();
|
||||
void on_surface_create();
|
||||
|
||||
uint32_t width = 1024;
|
||||
uint32_t height = 600;
|
||||
uint32_t physical_width = 270;
|
||||
uint32_t physical_height = 158;
|
||||
|
||||
bool cursor_visible = false;
|
||||
|
||||
uint32_t hotspot_x{}, hotspot_y{};
|
||||
|
||||
void resize(int w, int h, uint32_t pw, uint32_t ph);
|
||||
void cursor_move(uint32_t x, uint32_t y);
|
||||
void set_cursor_visibility(bool visibility);
|
||||
std::function<void(bool)> set_renderer_visibility = [](bool){};
|
||||
private:
|
||||
lorie_compositor& compositor;
|
||||
|
||||
void set_toplevel(wayland::surface_t* surface);
|
||||
void set_cursor(wayland::surface_t* surface, uint32_t hotspot_x, uint32_t hotspot_y);
|
||||
|
||||
struct wl_event_source *idle = NULL;
|
||||
void draw(GLuint id, float x0, float y0, float x1, float y1) const;
|
||||
void draw_cursor();
|
||||
void redraw();
|
||||
GLuint g_texture_program = 0;
|
||||
GLuint gv_pos = 0;
|
||||
GLuint gv_coords = 0;
|
||||
GLuint gv_texture_sampler_handle = 0;
|
||||
|
||||
struct {
|
||||
GLuint id;
|
||||
GLsizei width, height;
|
||||
inline void reset() { width = 0; height = 0; }
|
||||
inline bool valid() { return width != 0 && height != 0; }
|
||||
} toplevel_texture{}, cursor_texture{};
|
||||
|
||||
bool ready = false;
|
||||
bool visible = false;
|
||||
|
||||
friend class lorie_texture;
|
||||
friend class lorie_compositor;
|
||||
};
|
@ -1,181 +0,0 @@
|
||||
#include <log.h>
|
||||
#include <lorie_egl_helper.hpp>
|
||||
#include <GLES2/gl2.h>
|
||||
|
||||
static char* eglGetErrorText(int code) {
|
||||
switch(eglGetError()) {
|
||||
#define E(code, desc) case code: return (char*) desc; break;
|
||||
case EGL_SUCCESS: return nullptr; // "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: return (char*) "Unknown error";
|
||||
}
|
||||
}
|
||||
|
||||
#undef checkEGLError
|
||||
void checkEGLError(int line) {
|
||||
char *error = eglGetErrorText(eglGetError());
|
||||
if (error != nullptr)
|
||||
LOGE("EGL: %s after %d", error, line);
|
||||
}
|
||||
#define checkEGLError() checkEGLError(__LINE__)
|
||||
|
||||
bool LorieEGLHelper::init(EGLNativeDisplayType display) {
|
||||
EGLint majorVersion, minorVersion;
|
||||
|
||||
LOGV("Initializing EGL");
|
||||
dpy = eglGetDisplay(display); checkEGLError();
|
||||
if (dpy == EGL_NO_DISPLAY) {
|
||||
LOGE("LorieEGLHelper::init : eglGetDisplay failed: %s", eglGetErrorText(eglGetError()));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (eglInitialize(dpy, &majorVersion, &minorVersion) != EGL_TRUE) {
|
||||
LOGE("LorieEGLHelper::init : eglInitialize failed: %s", eglGetErrorText(eglGetError()));
|
||||
return false;
|
||||
}
|
||||
|
||||
const EGLint configAttribs[] = {
|
||||
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
|
||||
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
|
||||
EGL_RED_SIZE, 8,
|
||||
EGL_GREEN_SIZE, 8,
|
||||
EGL_BLUE_SIZE, 8,
|
||||
EGL_ALPHA_SIZE, 8,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
EGLint numConfigs;
|
||||
|
||||
if (eglChooseConfig(dpy, configAttribs, &cfg, 1, &numConfigs) != EGL_TRUE) {
|
||||
LOGE("LorieEGLHelper::init : eglChooseConfig failed: %s", eglGetErrorText(eglGetError()));
|
||||
return false;
|
||||
}
|
||||
|
||||
eglBindAPI(EGL_OPENGL_ES_API);
|
||||
|
||||
const EGLint ctxattribs[] = {
|
||||
EGL_CONTEXT_CLIENT_VERSION,
|
||||
2, // Request opengl ES2.0
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
ctx = eglCreateContext(dpy, cfg, NULL, ctxattribs);
|
||||
if (ctx == EGL_NO_CONTEXT) {
|
||||
LOGE("LorieEGLHelper::init : eglCreateContext failed: %s", eglGetErrorText(eglGetError()));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONFIG_KHR) != EGL_TRUE) {
|
||||
LOGE("LorieEGLHelper::init : eglMakeCurrent failed: %s", eglGetErrorText(eglGetError()));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LorieEGLHelper::setWindow(EGLNativeWindowType window) {
|
||||
LOGV("Trying to use window %p", window);
|
||||
if (sfc != EGL_NO_SURFACE) {
|
||||
if (eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT) != EGL_TRUE) {
|
||||
LOGE("LorieEGLHelper::setWindow : eglMakeCurrent (EGL_NO_SURFACE) failed: %s", eglGetErrorText(eglGetError()));
|
||||
return false;
|
||||
}
|
||||
if (eglDestroySurface(dpy, sfc) != EGL_TRUE) {
|
||||
LOGE("LorieEGLHelper::setWindow : eglDestoySurface failed: %s", eglGetErrorText(eglGetError()));
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
sfc = EGL_NO_SURFACE;
|
||||
win = window;
|
||||
|
||||
if (win == EGL_NO_WINDOW) {
|
||||
if (onUninit != nullptr) onUninit();
|
||||
return true;
|
||||
}
|
||||
|
||||
sfc = eglCreateWindowSurface(dpy, cfg, win, NULL);
|
||||
if (sfc == EGL_NO_SURFACE) {
|
||||
LOGE("LorieEGLHelper::setWindow : eglCreateWindowSurface failed: %s", eglGetErrorText(eglGetError()));
|
||||
if (onUninit != nullptr) onUninit();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (eglMakeCurrent(dpy, sfc, sfc, ctx) != EGL_TRUE) {
|
||||
LOGE("LorieEGLHelper::setWindow : eglMakeCurrent failed: %s", eglGetErrorText(eglGetError()));
|
||||
if (onUninit != nullptr) onUninit();
|
||||
return false;
|
||||
}
|
||||
|
||||
glClearColor(0.f, 0.f, 0.f, 0.f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
swap();
|
||||
|
||||
if (onInit != nullptr)
|
||||
onInit();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void LorieEGLHelper::swap() {
|
||||
EGLint b = eglSwapBuffers(dpy, sfc);
|
||||
if (b != EGL_TRUE) {
|
||||
EGLint err = eglGetError();
|
||||
if (err == EGL_BAD_SURFACE) {
|
||||
LOGE("eglSwapBuffers failed because of invalid surface. Regenerating surface.");
|
||||
setWindow(win);
|
||||
} else if (err == EGL_BAD_CONTEXT || err == EGL_CONTEXT_LOST) {
|
||||
LOGE("eglSwapBuffers failed because of invalid context. Regenerating context.");
|
||||
const EGLint ctxattribs[] = {
|
||||
EGL_CONTEXT_CLIENT_VERSION,
|
||||
2, // Request opengl ES2.0
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
ctx = eglCreateContext(dpy, cfg, NULL, ctxattribs);
|
||||
if (ctx == EGL_NO_CONTEXT) {
|
||||
LOGE("LorieEGLHelper::init : eglCreateContext failed: %s", eglGetErrorText(eglGetError()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LorieEGLHelper::uninit() {
|
||||
if (eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT) != EGL_TRUE) {
|
||||
LOGE("LorieEGLHelper::uninit : eglMakeCurrent failed: %s", eglGetErrorText(eglGetError()));
|
||||
return;
|
||||
}
|
||||
if (eglDestroyContext(dpy, ctx) != EGL_TRUE) {
|
||||
LOGE("LorieEGLHelper::uninit : eglDestroyContext failed: %s", eglGetErrorText(eglGetError()));
|
||||
return;
|
||||
}
|
||||
ctx = EGL_NO_CONTEXT;
|
||||
|
||||
if (eglDestroySurface(dpy, sfc) != EGL_TRUE) {
|
||||
LOGE("LorieEGLHelper::uninit : eglDestroySurface failed: %s", eglGetErrorText(eglGetError()));
|
||||
return;
|
||||
}
|
||||
sfc = EGL_NO_SURFACE;
|
||||
|
||||
if (eglTerminate(dpy) != EGL_TRUE) {
|
||||
LOGE("LorieEGLHelper::uninit : eglTerminate failed: %s", eglGetErrorText(eglGetError()));
|
||||
return;
|
||||
}
|
||||
dpy = EGL_NO_DISPLAY;
|
||||
}
|
@ -61,6 +61,7 @@ void display_t::add_fd_listener(int fd, uint32_t mask, std::function<int(int fd,
|
||||
wl_event_loop* loop = wl_display_get_event_loop(display);
|
||||
auto l = new fd_listener(loop, std::move(listener));
|
||||
l->source = wl_event_loop_add_fd(loop, fd, mask, fd_listener::fire, l);
|
||||
wl_event_loop_add_destroy_listener(loop, l);
|
||||
}
|
||||
|
||||
// It is wl_display_socket_add_fd version which drops server fd if it is faulty.
|
||||
@ -96,7 +97,7 @@ void display_t::add_socket_fd(int fd) {
|
||||
client_fd = accept4(fd, (struct sockaddr *) &name, &length, SOCK_CLOEXEC);
|
||||
if (client_fd < 0) {
|
||||
LOGE("failed to accept: %s\n", strerror(errno));
|
||||
if (errno == EBADF || errno == ENOTSOCK) {
|
||||
if (errno == EBADF || errno == ENOTSOCK || errno == EPERM) {
|
||||
l->destroy(l, nullptr);
|
||||
return 0;
|
||||
}
|
||||
|
@ -56,6 +56,10 @@ namespace wayland {
|
||||
inline void set_log_handler(wl_log_func_t f) {
|
||||
wl_log_set_handler_server(f);
|
||||
}
|
||||
|
||||
inline uint32_t next_serial() {
|
||||
return wl_display_next_serial(*this);
|
||||
};
|
||||
};
|
||||
|
||||
class client_t: public wl_listener {
|
||||
@ -128,10 +132,6 @@ namespace wayland {
|
||||
inline client_t* client() {
|
||||
return m_client;
|
||||
}
|
||||
|
||||
inline uint32_t next_serial() {
|
||||
return wl_display_next_serial(display);
|
||||
};
|
||||
|
||||
static inline resource_t* get(wl_resource* r) {
|
||||
return r == nullptr ? nullptr : static_cast<resource_t*>(wl_resource_get_destroy_listener(r, &resource_destroyed));
|
||||
|
@ -1,266 +0,0 @@
|
||||
#include <lorie_compositor.hpp>
|
||||
#include <GLES2/gl2ext.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
using namespace wayland;
|
||||
|
||||
static const char vertex_shader[] = R"(
|
||||
attribute vec4 position;
|
||||
attribute vec2 texCoords;
|
||||
varying vec2 outTexCoords;
|
||||
void main(void) {
|
||||
outTexCoords = texCoords;
|
||||
gl_Position = position;
|
||||
}
|
||||
)";
|
||||
static const char fragment_shader[] = R"(
|
||||
precision mediump float;
|
||||
varying vec2 outTexCoords;
|
||||
uniform sampler2D texture;
|
||||
void main(void) {
|
||||
gl_FragColor = texture2D(texture, outTexCoords).bgra;
|
||||
}
|
||||
)";
|
||||
|
||||
static GLuint load_shader(GLenum shaderType, const char* pSource) {
|
||||
GLuint shader = glCreateShader(shaderType);
|
||||
if (shader) {
|
||||
glShaderSource(shader, 1, &pSource, nullptr);
|
||||
glCompileShader(shader);
|
||||
GLint compiled = 0;
|
||||
glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
|
||||
if (!compiled) {
|
||||
GLint infoLen = 0;
|
||||
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
|
||||
if (infoLen) {
|
||||
char* buf = (char*) malloc(infoLen);
|
||||
if (buf) {
|
||||
glGetShaderInfoLog(shader, infoLen, nullptr, buf);
|
||||
LOGE("Could not compile shader %d:\n%s\n", shaderType, buf);
|
||||
free(buf);
|
||||
}
|
||||
glDeleteShader(shader);
|
||||
shader = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return shader;
|
||||
}
|
||||
|
||||
static GLuint create_program(const char* p_vertex_source, const char* p_fragment_source) {
|
||||
GLuint vertexShader = load_shader(GL_VERTEX_SHADER, p_vertex_source);
|
||||
if (!vertexShader) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
GLuint pixelShader = load_shader(GL_FRAGMENT_SHADER, p_fragment_source);
|
||||
if (!pixelShader) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
GLuint program = glCreateProgram();
|
||||
if (program) {
|
||||
glAttachShader(program, vertexShader);
|
||||
glAttachShader(program, pixelShader);
|
||||
glLinkProgram(program);
|
||||
GLint linkStatus = GL_FALSE;
|
||||
glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
|
||||
if (linkStatus != GL_TRUE) {
|
||||
GLint bufLength = 0;
|
||||
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength);
|
||||
if (bufLength) {
|
||||
char* buf = (char*) malloc(bufLength);
|
||||
if (buf) {
|
||||
glGetProgramInfoLog(program, bufLength, nullptr, buf);
|
||||
LOGE("Could not link program:\n%s\n", buf);
|
||||
free(buf);
|
||||
}
|
||||
}
|
||||
glDeleteProgram(program);
|
||||
program = 0;
|
||||
}
|
||||
}
|
||||
return program;
|
||||
}
|
||||
|
||||
static inline lorie_compositor::surface_data* data(surface_t* sfc) {
|
||||
return any_cast<lorie_compositor::surface_data*>(sfc->user_data());
|
||||
}
|
||||
|
||||
lorie_renderer::lorie_renderer(lorie_compositor& compositor) : compositor(compositor) {}
|
||||
|
||||
void lorie_renderer::on_surface_create() {
|
||||
LOGV("Initializing renderer (tid %d)", ::gettid());
|
||||
g_texture_program = create_program(vertex_shader, fragment_shader);
|
||||
if (!g_texture_program) {
|
||||
LOGE("GLESv2: Unable to create shader program");
|
||||
return;
|
||||
}
|
||||
gv_pos = (GLuint) glGetAttribLocation(g_texture_program, "position");
|
||||
gv_coords = (GLuint) glGetAttribLocation(g_texture_program, "texCoords");
|
||||
gv_texture_sampler_handle = (GLuint) glGetUniformLocation(g_texture_program, "texture");
|
||||
ready = true;
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glGenTextures(1, &toplevel_texture.id);
|
||||
glGenTextures(1, &cursor_texture.id);
|
||||
|
||||
redraw();
|
||||
}
|
||||
|
||||
void lorie_renderer::request_redraw() {
|
||||
//LOGV("Requesting redraw");
|
||||
if (idle) return;
|
||||
|
||||
idle = wl_event_loop_add_idle(
|
||||
wl_display_get_event_loop(compositor.display),
|
||||
[](void* data) { reinterpret_cast<lorie_renderer*>(data)->redraw(); },
|
||||
this
|
||||
);
|
||||
}
|
||||
|
||||
void lorie_renderer::resize(int w, int h, uint32_t pw, uint32_t ph) {
|
||||
LOGV("Resizing renderer to %dx%d (%dmm x %dmm)", w, h, pw, ph);
|
||||
if (w == width &&
|
||||
h == height &&
|
||||
pw == physical_width &&
|
||||
ph == physical_height) return;
|
||||
width = w;
|
||||
height = h;
|
||||
physical_width = pw;
|
||||
physical_height = ph;
|
||||
|
||||
glViewport(0, 0, w, h);
|
||||
|
||||
if (compositor.toplevel) {
|
||||
auto data = any_cast<lorie_compositor::client_data*>(compositor.toplevel->client()->user_data());
|
||||
compositor.report_mode(data->output);
|
||||
}
|
||||
}
|
||||
|
||||
void lorie_renderer::cursor_move(uint32_t x, uint32_t y) {
|
||||
if (compositor.cursor == nullptr) return;
|
||||
|
||||
data(compositor.cursor)->x = x;
|
||||
data(compositor.cursor)->y = y;
|
||||
request_redraw();
|
||||
}
|
||||
|
||||
void lorie_renderer::set_cursor_visibility(bool visibility) {
|
||||
if (cursor_visible != visibility)
|
||||
cursor_visible = visibility;
|
||||
}
|
||||
|
||||
void lorie_renderer::set_toplevel(surface_t* sfc) {
|
||||
LOGV("Setting surface %p as toplevel", sfc);
|
||||
compositor.toplevel = sfc;
|
||||
request_redraw();
|
||||
}
|
||||
|
||||
void lorie_renderer::set_cursor(surface_t* sfc, uint32_t hs_x, uint32_t hs_y) {
|
||||
LOGV("Setting surface %p as cursor", sfc);
|
||||
compositor.cursor = sfc;
|
||||
this->hotspot_x = hs_x;
|
||||
this->hotspot_y = hs_y;
|
||||
|
||||
request_redraw();
|
||||
}
|
||||
|
||||
void lorie_renderer::draw(GLuint id, float x0, float y0, float x1, float y1) const {
|
||||
float coords[20] = {
|
||||
x0, -y0, 0.f, 0.f, 0.f,
|
||||
x1, -y0, 0.f, 1.f, 0.f,
|
||||
x0, -y1, 0.f, 0.f, 1.f,
|
||||
x1, -y1, 0.f, 1.f, 1.f,
|
||||
};
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glUseProgram(g_texture_program);
|
||||
glBindTexture(GL_TEXTURE_2D, id);
|
||||
|
||||
glVertexAttribPointer(gv_pos, 3, GL_FLOAT, GL_FALSE, 20, coords);
|
||||
glVertexAttribPointer(gv_coords, 2, GL_FLOAT, GL_FALSE, 20, &coords[3]);
|
||||
glEnableVertexAttribArray(gv_pos);
|
||||
glEnableVertexAttribArray(gv_coords);
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
}
|
||||
|
||||
void lorie_renderer::draw_cursor() {
|
||||
if (compositor.cursor == nullptr) return;
|
||||
|
||||
auto toplevel = compositor.toplevel ? data(compositor.toplevel) : nullptr;
|
||||
auto cursor = compositor.cursor ? data(compositor.cursor) : nullptr;
|
||||
|
||||
if (cursor && cursor->damaged && cursor->buffer && cursor->buffer->is_shm()) {
|
||||
GLsizei w = cursor_texture.width = cursor->buffer->shm_width();
|
||||
GLsizei h = cursor_texture.height = cursor->buffer->shm_height();
|
||||
void* d = cursor->buffer->shm_data();
|
||||
glBindTexture(GL_TEXTURE_2D, cursor_texture.id);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, d);
|
||||
}
|
||||
if (!cursor_texture.valid()) return;
|
||||
|
||||
float x, y, w, h;
|
||||
x = 2*(((float)(cursor->x-hotspot_x))/(float)toplevel_texture.width) - 1;
|
||||
y = 2*(((float)(cursor->y-hotspot_y))/(float)toplevel_texture.height) - 1;
|
||||
w = 2*((float)cursor_texture.width)/(float)toplevel_texture.width;
|
||||
h = 2*((float)cursor_texture.height)/(float)toplevel_texture.height;
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
draw(cursor_texture.id, x, y, x + w, y + h);
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
|
||||
void lorie_renderer::redraw() {
|
||||
idle = nullptr;
|
||||
if (!ready) return;
|
||||
|
||||
if (compositor.toplevel && data(compositor.toplevel)) {
|
||||
auto d = data(compositor.toplevel);
|
||||
if (d->damaged && d->buffer && d->buffer->is_shm()) {
|
||||
if (d->buffer->shm_width() != toplevel_texture.width || d->buffer->shm_width() != toplevel_texture.width) {
|
||||
GLsizei w = toplevel_texture.width = d->buffer->shm_width();
|
||||
GLsizei h = toplevel_texture.height = d->buffer->shm_height();
|
||||
void* dd = d->buffer->shm_data();
|
||||
glBindTexture(GL_TEXTURE_2D, toplevel_texture.id);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, dd);
|
||||
} else {
|
||||
glBindTexture(GL_TEXTURE_2D, toplevel_texture.id);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0,
|
||||
toplevel_texture.width, toplevel_texture.height, GL_RGBA, GL_UNSIGNED_BYTE, d->buffer->shm_data());
|
||||
if (glGetError() == GL_INVALID_OPERATION)
|
||||
/* For some reason if our activity goes to background it loses access
|
||||
* to this texture. In this case it should be regenerated. */
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toplevel_texture.width, toplevel_texture.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, d->buffer->shm_data());
|
||||
}
|
||||
}
|
||||
|
||||
draw(toplevel_texture.id, -1.0, -1.0, 1.0, 1.0);
|
||||
|
||||
if (cursor_visible)
|
||||
draw_cursor();
|
||||
if (!visible)
|
||||
set_renderer_visibility(visible = true);
|
||||
} else {
|
||||
if (visible)
|
||||
set_renderer_visibility(visible = false);
|
||||
glClearColor(0.f, 0.f, 0.f, 0.f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
compositor.swap_buffers();
|
||||
}
|
@ -17,9 +17,6 @@ static int enabled = 0;
|
||||
using namespace std;
|
||||
extern "C" {
|
||||
|
||||
#define COLORIZE "\033[0;32m"
|
||||
#define COLOR_RESET "\033[0m"
|
||||
|
||||
extern void *blacklist[];
|
||||
#define skip_blacklisted(f) for (int z=0; blacklist[z]!=NULL; z++) if (blacklist[z]==f) return;
|
||||
|
||||
@ -122,11 +119,11 @@ void LogInit(void) {
|
||||
LogMessageInternal(LOG_ERROR, "Logger mutex init failed\n");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
LOGD("Logging initialized");
|
||||
}
|
||||
|
||||
static int level = -1;
|
||||
static thread_local int level = -1;
|
||||
|
||||
void print_func(void *func, int enter);
|
||||
|
||||
@ -150,13 +147,12 @@ __cyg_profile_func_exit (void *func, void *caller) {
|
||||
}
|
||||
|
||||
void print_func(void *func, int enter) {
|
||||
for (int i=0; i<level; i++) printf(" ");
|
||||
Dl_info info;
|
||||
if (dladdr(func, &info) && info.dli_sname != NULL) {
|
||||
char *demangled = NULL;
|
||||
int status;
|
||||
demangled = abi::__cxa_demangle(info.dli_sname, NULL, 0, &status);
|
||||
LOGP("%s%s %s%s", (level==2)?COLORIZE:"", enter ? ">" : "<", status == 0 ? demangled : info.dli_sname, (level==2)?COLOR_RESET:"");
|
||||
LOGP("%*c%s %s", level, ' ', enter ? ">" : "<", status == 0 ? demangled : info.dli_sname);
|
||||
free(demangled);
|
||||
}
|
||||
}
|
||||
@ -168,7 +164,7 @@ void *blacklist[] = {
|
||||
#if defined(__ANDROID__)
|
||||
(void*) androidLogFn,
|
||||
#endif
|
||||
(void*) LogInit,
|
||||
(void*) LogInit,
|
||||
(void*) LogMessage,
|
||||
(void*) LogMessageInternal,
|
||||
(void*) __cyg_profile_func_enter,
|
||||
|
@ -10,6 +10,11 @@
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/frame">
|
||||
|
||||
<SurfaceView
|
||||
android:id="@+id/cursorView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<SurfaceView
|
||||
android:id="@+id/lorieView"
|
||||
android:visibility="visible"
|
||||
|
Loading…
x
Reference in New Issue
Block a user