Cursor positioning fix. Starter is moved to main apk. Implemented shell-loader which loads started dynamically from main apk to avoid version mismatches between main apk and starter.

This commit is contained in:
Twaik Yont 2023-02-17 13:22:08 +02:00
parent aa53a085d5
commit 8e38aa670a
41 changed files with 215 additions and 145 deletions

View File

@ -62,6 +62,15 @@ android {
jniLibs.srcDirs 'src/main/jni/prebuilt'
}
}
packagingOptions {
jniLibs {
// This will allow us to use shared libraries inside *.apk, without unpacking
// @agnostic-apollo is the best
// https://github.com/termux/termux-x11/commit/6cdfb75c4451eef63f114a93baef5ba73b7cfd8c#commitcomment-77856313
useLegacyPackaging false
}
}
}
@ -85,4 +94,5 @@ dependencies {
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
implementation 'androidx.annotation:annotation:1.5.0'
implementation 'androidx.drawerlayout:drawerlayout:1.1.1'
compileOnly project(':app:stub')
}

View File

@ -12,10 +12,7 @@ import android.content.Context;
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;
import android.os.Build;
import android.os.Handler;
@ -30,10 +27,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.NotificationCompat;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.InputDevice;
import android.view.KeyEvent;
@ -42,17 +36,11 @@ 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;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
@SuppressWarnings({"ConstantConditions", "SameParameterValue", "SdCardPath"})
@SuppressLint({"ClickableViewAccessibility", "StaticFieldLeak"})
@ -98,33 +86,6 @@ public class LorieService extends Service implements View.OnApplyWindowInsetsLis
if (isServiceRunningInForeground(this, LorieService.class)) return;
compositor = createLorieThread();
File outDir = new File(getApplicationInfo().dataDir, "files");
File out = new File(outDir, "en_us.xkbmap");
File apk = null;
try {
PackageManager pm = getPackageManager();
String apkPath = pm.getApplicationInfo(getPackageName(), 0).publicSourceDir;
apk = new File(apkPath);
} catch (Throwable ignored) {}
if (!out.exists() || (apk != null && apk.lastModified() > out.lastModified())) {
//noinspection ResultOfMethodCallIgnored
outDir.mkdirs();
try (InputStream fin = getAssets().open("en_us.xkbmap")) {
byte[] buffer = new byte[8192];
int count;
try (FileOutputStream fout = new FileOutputStream(out)) {
while ((count = fin.read(buffer)) != -1)
fout.write(buffer, 0, count);
fout.flush();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
if (compositor == 0) {
Log.e("LorieService", "compositor thread was not created");
return;
@ -450,21 +411,7 @@ public class LorieService extends Service implements View.OnApplyWindowInsetsLis
@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);
mmHeight = (int) Math.round((height * 25.4) / dm.ydpi);
} else {
mmWidth = (int) Math.round((width * 25.4) / dm.ydpi);
mmHeight = (int) Math.round((height * 25.4) / dm.xdpi);
}
svc.windowChanged(holder.getSurface(), r.right, r.bottom, mmWidth, mmHeight);
svc.windowChanged(holder.getSurface(), width, height);
}
@Override public void surfaceCreated(SurfaceHolder holder) {}
@ -538,8 +485,7 @@ public class LorieService extends Service implements View.OnApplyWindowInsetsLis
act.runOnUiThread(()-> {
SurfaceView v = act.findViewById(R.id.cursorView);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(w, h);
params.leftMargin = x;
params.topMargin = y;
params.setMargins(x, y, 0, 0);
v.setLayoutParams(params);
v.setVisibility(View.VISIBLE);
@ -547,7 +493,7 @@ public class LorieService extends Service implements View.OnApplyWindowInsetsLis
}
private native void cursorChanged(Surface surface);
private native void windowChanged(Surface surface, int width, int height, int mmWidth, int mmHeight);
private native void windowChanged(Surface surface, int width, int height);
private native void touchDown(int id, float x, float y);
private native void touchMotion(int id, float x, float y);
private native void touchUp(int id);

View File

@ -228,16 +228,9 @@ public class Starter {
private native int openLogFD();
private static native void exec(String path, String[] argv);
@SuppressWarnings("FieldMayBeFinal")
private static Handler handler;
private static Handler handler = new Handler();
static {
Looper.prepareMainLooper();
handler = new Handler();
String libPath = System.getenv("CLASSPATH") +
"!/lib/" + Build.SUPPORTED_ABIS[0] +
"/libx11-starter.so";
System.err.println("Loading shared library: " + libPath);
System.load(libPath);
System.loadLibrary("x11-starter");
}
}

View File

@ -1,3 +1,3 @@
ROOT_PATH := $(call my-dir)
include $(ROOT_PATH)/lorie/Android.mk
include $(ROOT_PATH)/../../../../common/src/main/jni/Android.mk
include $(ROOT_PATH)/starter/Android.mk

View File

@ -5,7 +5,7 @@
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include "ashmem.h"
#include <shm.h>
#include <csignal>
#include <android/native_window_jni.h>
@ -13,6 +13,8 @@
#include <dirent.h>
#include <android/log.h>
#define DEFAULT_DPI 96
#pragma ide diagnostic ignored "hicpp-signed-bitwise"
#define unused __attribute__((__unused__))
#define always_inline __attribute__((always_inline)) inline
@ -55,9 +57,27 @@ lorie_compositor::lorie_compositor(jobject thiz): lorie_compositor() {
});
}
__asm__ (
" .global blob\n"
" .global blob_size\n"
" .section .rodata\n"
" blob:\n"
" .incbin \"en_us.xkbmap\"\n"
" 1:\n"
" blob_size:\n"
" .int 1b - blob"
);
extern jbyte blob[];
extern int blob_size;
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 = {};
int keymap_fd = os_create_anonymous_file(blob_size);
void *dest = mmap(nullptr, blob_size, PROT_READ | PROT_WRITE, MAP_SHARED, keymap_fd, 0);
memcpy(dest, blob, blob_size);
munmap(dest, blob_size);
struct stat s = {};
fstat(keymap_fd, &s);
*size = s.st_size; // NOLINT(cppcoreguidelines-narrowing-conversions)
*fd = keymap_fd;
@ -200,9 +220,9 @@ JNI_DECLARE(LorieService, cursorChanged)(JNIEnv *env, jobject thiz, jobject surf
}
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) {
JNI_DECLARE(LorieService, windowChanged)(JNIEnv *env, jobject thiz, jobject surface, jint w, jint h) {
EGLNativeWindowType win = surface?ANativeWindow_fromSurface(env, surface):nullptr;
queue<&lorie_compositor::output_resize>(env, thiz, win, width, height, mm_width, mm_height);
queue<&lorie_compositor::output_resize>(env, thiz, win, w, h, int(w*25.4/DEFAULT_DPI), int(h*25.4/DEFAULT_DPI));
}
extern "C" JNIEXPORT void JNICALL

View File

@ -0,0 +1,22 @@
#include "ashmem.h"
static inline int
os_create_anonymous_file(size_t size) {
int fd, ret;
long flags;
fd = open("/dev/ashmem", O_RDWR | O_CLOEXEC);
if (fd < 0)
return fd;
ret = ioctl(fd, ASHMEM_SET_SIZE, size);
if (ret < 0)
goto err;
flags = fcntl(fd, F_GETFD);
if (flags == -1)
goto err;
if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1)
goto err;
return fd;
err:
close(fd);
return ret;
}

View File

@ -5,5 +5,3 @@ LOCAL_MODULE := x11-starter
LOCAL_SRC_FILES := starter.c
LOCAL_LDLIBS := -llog -ldl
include $(BUILD_SHARED_LIBRARY)
include $(LOCAL_PATH)/../../../../common/src/main/jni/Android.mk

View File

View File

@ -5,33 +5,38 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
<FrameLayout
android:id="@+id/frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:layoutDirection="ltr"
android:id="@+id/frame">
android:paddingLeft="0dp"
android:paddingTop="0dp"
android:paddingRight="0dp"
android:paddingBottom="0dp">
<SurfaceView
android:id="@+id/lorieView"
android:visibility="visible"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:layout_height="match_parent"
android:visibility="visible" />
<SurfaceView
android:id="@+id/cursorView"
android:visibility="invisible"
android:layout_gravity="left|top"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
android:layout_height="wrap_content"
android:layout_gravity="left|top"
android:visibility="invisible" />
<LinearLayout
android:id="@+id/stub"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal|center_vertical"
android:background="@android:color/transparent"
android:visibility="visible"
android:orientation="vertical">
android:gravity="center_horizontal|center_vertical"
android:orientation="vertical"
android:visibility="visible">
<ImageView
android:id="@+id/x11_image"
@ -74,10 +79,10 @@
<Button
android:id="@+id/getting_started_button"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/getting_started_button_text" />
android:text="@string/getting_started_button_text"
android:visibility="gone" />
</LinearLayout>
</FrameLayout>

View File

@ -16,16 +16,16 @@ mkdir -p $PREFIX/bin/
mkdir -p $PREFIX/libexec/termux-x11
cp termux-x11 $PREFIX/bin/
cp termux-startx11 $PREFIX/libexec/termux-x11
cp starter/build/outputs/apk/debug/starter-debug.apk \
$PREFIX/libexec/termux-x11/starter.apk
cp termux-startx11 $PREFIX/libexec/termux-x11/
cp shell-loader/build/outputs/apk/debug/shell-loader-debug.apk \
$PREFIX/libexec/termux-x11/loader.apk
mkdir -p $CONTROL_DIR
cat <<EOF > $CONTROL_DIR/control
Package: termux-x11
Architecture: all
Maintainer: Twaik Yont @twaik
Version: 1.02.06
Version: 1.02.07
Homepage: https://github.com/termux/termux-x11
Depends: xwayland
Description: Companion package for termux-x11 app

View File

@ -1,4 +1,5 @@
include ':starter:stub'
include ':starter'
include ':shell-loader:stub'
include ':shell-loader'
include ':common'
include ':app:stub'
include ':app'

0
starter/.gitignore → shell-loader/.gitignore vendored Normal file → Executable file
View File

28
starter/build.gradle → shell-loader/build.gradle Normal file → Executable file
View File

@ -15,25 +15,12 @@ android {
versionName "0.1"
}
externalNativeBuild {
ndkBuild {
path "src/main/jni/Android.mk"
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
packagingOptions {
jniLibs {
// This will allow us to use shared libraries inside *.apk, without unpacking
// @agnostic-apollo is the best
// https://github.com/termux/termux-x11/commit/6cdfb75c4451eef63f114a93baef5ba73b7cfd8c#commitcomment-77856313
useLegacyPackaging false
signingConfigs {
debug {
storeFile file('../app/testkey_untrusted.jks')
keyAlias 'alias'
storePassword 'xrj45yWGLbsO7W0v'
keyPassword 'xrj45yWGLbsO7W0v'
}
}
@ -45,6 +32,5 @@ android {
}
dependencies {
implementation project(':common')
compileOnly project(':starter:stub')
compileOnly project(':shell-loader:stub')
}

View File

@ -0,0 +1 @@
<manifest package="com.termux.x11.starter" />

View File

@ -0,0 +1,70 @@
package com.termux.x11;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Looper;
import android.util.Log;
import dalvik.system.PathClassLoader;
@SuppressLint("UnsafeDynamicallyLoadedCode")
public class Loader {
private final static String targetPackageName = "com.termux.x11";
private final static String targetClassName = "com.termux.x11.starter.Starter";
private final static String logTag = "Termux:X11 loader";
/**
* Command-line entry point.
* It is pretty simple.
* 1. We check if application is installed.
* 2. We check if target apk has the same signature as loader's apk.
* It is needed for to prevent running code of potentially replaced malicious apk.
* 3. We load target apk code with `PathClassLoader` and start target's main function.
*
* This way we can make this loader version-agnostic and keep it secure.
* All application logic is located in target apk.
*
* @param args The command-line arguments
*/
@SuppressLint("WrongConstant")
public static void main(String[] args) {
Looper.prepareMainLooper();
Log.i(logTag, "started");
Context ctx = android.app.ActivityThread.systemMain().getSystemContext();
PackageManager pm = ctx.getPackageManager();
try {
String selfPath = System.getProperty("java.class.path");
PackageInfo selfInfo = pm.getPackageArchiveInfo(selfPath, PackageManager.GET_SIGNATURES);
@SuppressLint("PackageManagerGetSignatures")
PackageInfo targetInfo = pm.getPackageInfo("com.termux.x11", PackageManager.GET_SIGNATURES);
if (selfInfo.signatures.length != targetInfo.signatures.length
|| selfInfo.signatures[0].hashCode() != targetInfo.signatures[0].hashCode()) {
System.err.println("Signatures of this loader and target application " + targetPackageName + " do not match.");
System.err.println("Please, reinstall both termux-x11 package and Termux:X11 application from the same source");
System.exit(134);
}
ApplicationInfo target = pm.getApplicationInfo(targetPackageName, 0);
Log.i(logTag, "loading " + target.sourceDir + " of " + targetPackageName + " application");
String librarySearchPath = target.sourceDir + "!/lib/" + Build.SUPPORTED_ABIS[0] + "/";
PathClassLoader classLoader = new PathClassLoader(target.sourceDir, librarySearchPath,
ClassLoader.getSystemClassLoader());
try {
Class<?> targetClass = Class.forName(targetClassName, true, classLoader);
Log.i(logTag, "starting " + targetClassName + "::main();");
targetClass.getMethod("main", String[].class).invoke(null, (Object) args);
} catch (Exception e) {
throw new RuntimeException(e);
}
} catch (PackageManager.NameNotFoundException e) {
throw new RuntimeException(e);
}
System.exit(0);
}
}

28
shell-loader/stub/build.gradle Executable file
View File

@ -0,0 +1,28 @@
plugins {
id('com.android.library')
}
android {
compileSdkVersion 30
defaultConfig {
minSdkVersion 24
//noinspection ExpiredTargetSdkVersion
targetSdkVersion 28
}
buildFeatures {
buildConfig false
}
buildTypes {
release {
minifyEnabled false
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation 'androidx.annotation:annotation:1.5.0'
}

View File

@ -0,0 +1 @@
<manifest package="com.termux.x11.stub" />

View File

@ -0,0 +1,11 @@
package android.app;
public class ActivityThread {
public static ActivityThread systemMain() {
throw new RuntimeException("STUB");
}
public ContextImpl getSystemContext() {
throw new RuntimeException("STUB");
}
}

View File

@ -0,0 +1,6 @@
package android.app;
import android.content.Context;
public abstract class ContextImpl extends Context {
}

View File

@ -1,21 +0,0 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@ -1,7 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.termux.x11.starter">
<application
android:label="TermuxX11Starter"
android:supportsRtl="true" />
</manifest>

View File

@ -1,4 +1,4 @@
#!/data/data/com.termux/files/usr/bin/sh
export CLASSPATH=/data/data/com.termux/files/usr/libexec/termux-x11/starter.apk
export CLASSPATH=/data/data/com.termux/files/usr/libexec/termux-x11/loader.apk
unset LD_LIBRARY_PATH LD_PRELOAD
exec /system/bin/app_process / com.termux.x11.starter.Starter "$@"
exec /system/bin/app_process / com.termux.x11.Loader "$@"