diff --git a/app/src/main/aidl/com/termux/x11/IRemoteCmdImterface.aidl b/app/src/main/aidl/com/termux/x11/IRemoteCmdImterface.aidl new file mode 100644 index 000000000..80434500c --- /dev/null +++ b/app/src/main/aidl/com/termux/x11/IRemoteCmdImterface.aidl @@ -0,0 +1,5 @@ +package com.termux.x11; + +interface IRemoteCmdImterface { + void exit(int code, String output); +} \ No newline at end of file diff --git a/app/src/main/java/com/termux/x11/CmdEntryPoint.java b/app/src/main/java/com/termux/x11/CmdEntryPoint.java index a9b780a4a..ddfcf934a 100644 --- a/app/src/main/java/com/termux/x11/CmdEntryPoint.java +++ b/app/src/main/java/com/termux/x11/CmdEntryPoint.java @@ -10,6 +10,7 @@ import android.content.IIntentReceiver; import android.content.IIntentSender; import android.content.Intent; +import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -29,8 +30,9 @@ @Keep @SuppressLint({"StaticFieldLeak", "UnsafeDynamicallyLoadedCode"}) public class CmdEntryPoint extends ICmdEntryInterface.Stub { public static final String ACTION_START = "com.termux.x11.CmdEntryPoint.ACTION_START"; - private static final Handler handler; + static final Handler handler; public static Context ctx; + private final Intent intent = createIntent(); /** * Command-line entry point. @@ -52,7 +54,7 @@ public static void main(String[] args) { } @SuppressLint({"WrongConstant", "PrivateApi"}) - void sendBroadcast() { + private Intent createIntent() { String targetPackage = getenv("TERMUX_X11_OVERRIDE_PACKAGE"); if (targetPackage == null) targetPackage = "com.termux.x11"; @@ -68,6 +70,10 @@ void sendBroadcast() { if (getuid() == 0 || getuid() == 2000) intent.setFlags(0x00400000 /* FLAG_RECEIVER_FROM_SHELL */); + return intent; + } + + static void sendBroadcast(Intent intent) { try { ctx.sendBroadcast(intent); } catch (Exception e) { @@ -118,7 +124,7 @@ void sendBroadcast() { // In this case opened port works like a lock file. private void sendBroadcastDelayed() { if (!connected()) - sendBroadcast(); + sendBroadcast(intent); handler.postDelayed(this::sendBroadcastDelayed, 1000); } diff --git a/app/src/main/java/com/termux/x11/LoriePreferences.java b/app/src/main/java/com/termux/x11/LoriePreferences.java index 54a77fceb..62f68f89e 100644 --- a/app/src/main/java/com/termux/x11/LoriePreferences.java +++ b/app/src/main/java/com/termux/x11/LoriePreferences.java @@ -5,6 +5,7 @@ import static android.content.pm.PackageManager.PERMISSION_DENIED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Build.VERSION.SDK_INT; +import static android.system.Os.getuid; import android.annotation.SuppressLint; import android.content.BroadcastReceiver; @@ -22,6 +23,7 @@ import android.os.Build; import android.os.Bundle; +import androidx.annotation.Keep; import androidx.annotation.NonNull; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; @@ -31,6 +33,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; +import android.os.RemoteException; import android.preference.PreferenceManager; import androidx.annotation.Nullable; @@ -68,6 +71,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Objects; +import java.util.Scanner; import java.util.Set; import java.util.function.Consumer; import java.util.regex.PatternSyntaxException; @@ -493,8 +497,12 @@ public IBinder peekService(Context myContext, Intent service) { @SuppressLint("ApplySharedPref") @Override public void onReceive(Context context, Intent intent) { + Bundle bundle = intent != null ? intent.getBundleExtra(null) : null; + IBinder ibinder = bundle != null ? bundle.getBinder(null) : null; + IRemoteCmdImterface remote = ibinder != null ? IRemoteCmdImterface.Stub.asInterface(ibinder) : null; + try { - if (intent.getExtras() != null) { + if (intent != null && intent.getExtras() != null) { Prefs p = (MainActivity.getInstance() != null) ? new Prefs(MainActivity.getInstance()) : (prefs != null ? prefs : new Prefs(context)); if (intent.getStringExtra("list") != null) { String result = ""; @@ -516,14 +524,14 @@ else if (pref.type == String[].class) { } } - setResultCode(2); - setResultData(result); - + sendResponse(remote, 0, 2, result.substring(0, result.length() - 1)); return; } SharedPreferences.Editor edit = p.get().edit(); for (String key : intent.getExtras().keySet()) { + if (key == null) + continue; String newValue = intent.getStringExtra(key); if (newValue == null) continue; @@ -535,8 +543,7 @@ else if (pref.type == String[].class) { Integer.parseInt(resolution[0]); Integer.parseInt(resolution[1]); } catch (NumberFormatException | PatternSyntaxException ignored) { - setResultCode(1); - setResultData("displayResolutionCustom: Wrong resolution format."); + sendResponse(remote, 1, 1, "displayResolutionCustom: Wrong resolution format."); return; } @@ -547,8 +554,7 @@ else if (pref.type == String[].class) { if (!"true".equals(newValue)) KeyInterceptor.shutdown(false); else if (context.checkSelfPermission(WRITE_SECURE_SETTINGS) != PERMISSION_GRANTED) { - setResultCode(1); - setResultData("Permission denied.\n" + + sendResponse(remote, 1, 1, "Permission denied.\n" + "Android requires WRITE_SECURE_SETTINGS permission to change `enableAccessibilityServiceAutomatically` setting.\n" + "Please, launch this command using ADB:\n" + "adb shell pm grant com.termux.x11 android.permission.WRITE_SECURE_SETTINGS"); @@ -572,8 +578,7 @@ else if (context.checkSelfPermission(WRITE_SECURE_SETTINGS) != PERMISSION_GRANTE try { edit.putInt(key, Integer.parseInt(newValue)); } catch (NumberFormatException | PatternSyntaxException exception) { - setResultCode(4); - setResultData(key + ": failed to parse integer: " + exception); + sendResponse(remote, 1, 4, key + ": failed to parse integer: " + exception); return; } } else if (pref != null && pref.type == String[].class) { @@ -590,12 +595,10 @@ else if (context.checkSelfPermission(WRITE_SECURE_SETTINGS) != PERMISSION_GRANTE break; } - setResultCode(1); - setResultData(key + ": can not be set to \"" + newValue + "\", possible options are " + Arrays.toString(entries) + (_p.entries != _p.values ? " or " + Arrays.toString(values) : "")); + sendResponse(remote, 1, 1, key + ": can not be set to \"" + newValue + "\", possible options are " + Arrays.toString(entries) + (_p.entries != _p.values ? " or " + Arrays.toString(values) : "")); return; } else { - setResultCode(4); - setResultData(key + ": unrecognised option"); + sendResponse(remote, 1, 4, key + ": unrecognised option"); return; } } @@ -610,19 +613,88 @@ else if (context.checkSelfPermission(WRITE_SECURE_SETTINGS) != PERMISSION_GRANTE edit.commit(); } - setResultCode(2); - setResultData("Done"); + sendResponse(remote, 0, 2, "Done"); } catch (Exception e) { - setResultCode(4); StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); - e.printStackTrace(pw); - setResultData(sw.toString()); + sendResponse(remote, 1, 4, sw.toString()); + } + } + + void sendResponse(IRemoteCmdImterface remote, int status, int oldStatus, String text) { + if (remote != null) { + try { + remote.exit(status, text); + } catch (RemoteException ex) { + Log.e("LoriePreferences", "Failed to send response to commandline proxy", ex); + } + } else if (isOrderedBroadcast()) { + setResultCode(oldStatus); + setResultData(text); } } + + // For changing preferences from commandline + private static final IBinder iface = new IRemoteCmdImterface.Stub() { + @Override + public void exit(int code, String output) { + System.out.println(output); + CmdEntryPoint.handler.post(() -> System.exit(code)); + } + }; + + private static void help() { + System.err.print("termux-x11-preference [list] {key:value} [{key2:value2}]..."); + System.exit(0); + } + + @Keep + @SuppressLint("WrongConstant") + public static void main(String[] args) { + android.util.Log.i("LoriePreferences$Receiver", "commit " + BuildConfig.COMMIT); + Bundle bundle = new Bundle(); + bundle.putBinder(null, iface); + + Intent i = new Intent("com.termux.x11.CHANGE_PREFERENCE"); + i.setPackage("com.termux.x11"); + i.putExtra(null, bundle); + if (getuid() == 0 || getuid() == 2000) + i.setFlags(0x00400000 /* FLAG_RECEIVER_FROM_SHELL */); + + if (System.console() == null && System.in != null) { + Scanner scanner = new Scanner(System.in); + String line; + String[] v; + while (scanner.hasNextLine()) { + line = scanner.nextLine(); + if (!line.contains("=")) + help(); + + v = line.split("="); + i.putExtra(v[0], v[1]); + } + } + + for (String a: args) { + if ("list".equals(a)) { + i.putExtra("list", ""); + } else if (a != null && a.contains(":")) { + String[] v = a.split(":"); + i.putExtra(v[0], v[1]); + } else + help(); + } + + CmdEntryPoint.handler.post(() -> CmdEntryPoint.sendBroadcast(i)); + CmdEntryPoint.handler.postDelayed(() -> { + System.err.println("Failed to obtain response from app."); + System.exit(1); + }, 5000); + Looper.loop(); + } } - static Handler handler = new Handler(Looper.getMainLooper()); + static Handler handler = Looper.getMainLooper() != null ? new Handler(Looper.getMainLooper()) : null; public void onClick(View view) { showFragment(new LoriePreferenceFragment("ekbar")); diff --git a/shell-loader/src/main/java/com/termux/x11/Loader.java b/shell-loader/src/main/java/com/termux/x11/Loader.java index 519933cc5..d22d56799 100755 --- a/shell-loader/src/main/java/com/termux/x11/Loader.java +++ b/shell-loader/src/main/java/com/termux/x11/Loader.java @@ -14,6 +14,8 @@ public class Loader { * @param args The command-line arguments */ public static void main(String[] args) { + String cls = System.getenv("TERMUX_X11_LOADER_OVERRIDE_CMDENTRYPOINT_CLASS"); + cls = cls != null ? cls : BuildConfig.CLASS_ID; try { android.content.pm.PackageInfo targetInfo = (android.os.Build.VERSION.SDK_INT <= 32) ? android.app.ActivityThread.getPackageManager().getPackageInfo(BuildConfig.APPLICATION_ID, android.content.pm.PackageManager.GET_SIGNATURES, 0) : @@ -22,7 +24,7 @@ public static void main(String[] args) { assert targetInfo.signatures.length == 1 && BuildConfig.SIGNATURE == targetInfo.signatures[0].hashCode() : BuildConfig.packageSignatureMismatchErrorText; android.util.Log.i(BuildConfig.logTag, "loading " + targetInfo.applicationInfo.sourceDir + "::" + BuildConfig.CLASS_ID + "::main of " + BuildConfig.APPLICATION_ID + " application (commit " + BuildConfig.COMMIT + ")"); - Class targetClass = Class.forName(BuildConfig.CLASS_ID, true, + Class targetClass = Class.forName(cls, true, new dalvik.system.PathClassLoader(targetInfo.applicationInfo.sourceDir, null, ClassLoader.getSystemClassLoader())); targetClass.getMethod("main", String[].class).invoke(null, (Object) args); } catch (AssertionError e) { diff --git a/termux-x11-preference b/termux-x11-preference index 37a6f6cf6..a48c8f1ed 100755 --- a/termux-x11-preference +++ b/termux-x11-preference @@ -1,4 +1,11 @@ #!/data/data/com.termux/files/usr/bin/bash +if [ "$(getprop ro.build.version.sdk)" -ge "34" ]; then + unset LD_LIBRARY_PATH LD_PRELOAD + export CLASSPATH=/data/data/com.termux/files/usr/libexec/termux-x11/loader.apk + export TERMUX_X11_LOADER_OVERRIDE_CMDENTRYPOINT_CLASS=com.termux.x11.LoriePreferences\$Receiver + exec /system/bin/app_process -Xnoimage-dex2oat / com.termux.x11.Loader "$@" +fi + COMMAND=("am" "broadcast" "-a" "com.termux.x11.CHANGE_PREFERENCE" "-p" "com.termux.x11") help() { echo "$0 [list] {key:value} [{key2:value2}]..."