termux-x11/starter/src/main/java/com/termux/x11/starter/Starter.java

310 lines
11 KiB
Java

/*
**
** Copyright 2007, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
package com.termux.x11.starter;
import android.annotation.SuppressLint;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.AndroidException;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Objects;
import com.termux.x11.common.ITermuxX11Internal;
@SuppressLint("UnsafeDynamicallyLoadedCode")
@SuppressWarnings({"unused", "RedundantThrows", "SameParameterValue", "FieldCanBeLocal"})
public class Starter {
@SuppressLint("SdCardPath")
private final String XwaylandPath = "/data/data/com.termux/files/usr/bin/Xwayland";
private final String TermuxX11ComponentName = "com.termux.x11/.TermuxX11StarterReceiver";
private String[] args;
private Service svc;
private ParcelFileDescriptor logFD;
/**
* Command-line entry point.
*
* @param args The command-line arguments
*/
public static void main(String[] args) {
try {
(new Thread(() -> {
try {
(new Starter()).onRun(args);
} catch (Throwable e) {
e.printStackTrace();
}
})).start();
synchronized (Thread.currentThread()) {
Looper.loop();
}
} catch (Throwable e) {
e.printStackTrace();
}
}
Runnable failedToStartActivity = () -> {
System.err.println("Android reported activity started but we did not get any respond");
System.err.println("Looks like we failed to start activity.");
System.err.println("Looks like Termux lacks \"Draw Over Apps\" permission.");
System.err.println("You can grant Termux the \"Draw Over Apps\" permission from its App Info activity:");
System.err.println("\tAndroid Settings -> Apps -> Termux -> Advanced -> Draw over other apps.");
};
public void onRun(String[] args) throws Throwable {
checkXdgRuntimeDir();
prepareLogFD();
if (checkWaylandSocket()) {
System.err.println("termux-x11 is already running");
startXwayland();
} else {
Starter.this.args = args;
svc = new Service();
handler.postDelayed(failedToStartActivity, 2000);
boolean launched = startActivity(svc);
}
}
public void onTransact(int code, Parcel data, Parcel reply, int flags) {
handler.removeCallbacks(failedToStartActivity);
}
private void prepareLogFD() {
logFD = ParcelFileDescriptor.adoptFd(openLogFD());
}
private ParcelFileDescriptor getWaylandFD() throws Throwable {
try {
ParcelFileDescriptor pfd;
int fd = createWaylandSocket();
if (fd == -1)
throw new Exception("Failed to bind a socket");
pfd = ParcelFileDescriptor.adoptFd(fd);
System.err.println(pfd);
return pfd;
} finally {
System.err.println("Lorie requested fd");
}
}
private void startXwayland() {
try {
boolean started = false;
for (int i = 0; i < 200; i++) {
if (checkWaylandSocket()) {
started = true;
Thread.sleep(200);
startXwayland(args);
break;
}
Thread.sleep(100);
}
if (!started)
System.err.println("Failed to connect to Termux:X11. Something went wrong.");
} catch (Exception e) {
e.printStackTrace();
}
exit(0);
}
private void onFinish() {
System.err.println("App sent finishing command");
startXwayland();
}
private boolean startActivity(IBinder token) throws Throwable {
Bundle bundle = new Bundle();
bundle.putBinder("", token);
Intent intent = new Intent();
intent.putExtra("com.termux.x11.starter", bundle);
ComponentName cn = ComponentName.unflattenFromString(TermuxX11ComponentName);
if (cn == null)
throw new IllegalArgumentException("Bad component name: " + TermuxX11ComponentName);
intent.setComponent(cn);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|
Intent.FLAG_ACTIVITY_SINGLE_TOP);
IActivityManager mAm;
try {
mAm = new IActivityManager();
} catch (Throwable e) {
e.printStackTrace();
throw new AndroidException("Can't connect to activity manager; is the system running?");
}
int res;
res = mAm.startActivityAsUser(intent,null, 0, null, 0);
PrintStream out = System.err;
boolean launched = false;
out.println("res = " + res);
switch (res) {
case ActivityManager.START_SUCCESS:
launched = true;
break;
case ActivityManager.START_SWITCHES_CANCELED:
launched = true;
out.println(
"Warning: Activity not started because the "
+ " current activity is being kept for the user.");
break;
case ActivityManager.START_DELIVERED_TO_TOP:
launched = true;
out.println(
"Warning: Activity not started, intent has "
+ "been delivered to currently running "
+ "top-most instance.");
break;
case ActivityManager.START_RETURN_INTENT_TO_CALLER:
launched = true;
out.println(
"Warning: Activity not started because intent "
+ "should be handled by the caller");
break;
case ActivityManager.START_TASK_TO_FRONT:
launched = true;
out.println(
"Warning: Activity not started, its current "
+ "task has been brought to the front");
break;
case ActivityManager.START_INTENT_NOT_RESOLVED:
out.println(
"Error: Activity not started, unable to "
+ "resolve " + intent.toString());
break;
case ActivityManager.START_CLASS_NOT_FOUND:
out.println("Error: Activity class " +
Objects.requireNonNull(intent.getComponent()).toShortString()
+ " does not exist.");
break;
case ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT:
out.println(
"Error: Activity not started, you requested to "
+ "both forward and receive its result");
break;
case ActivityManager.START_PERMISSION_DENIED:
out.println(
"Error: Activity not started, you do not "
+ "have permission to access it.");
break;
case ActivityManager.START_NOT_VOICE_COMPATIBLE:
out.println(
"Error: Activity not started, voice control not allowed for: "
+ intent);
break;
case ActivityManager.START_NOT_CURRENT_USER_ACTIVITY:
out.println(
"Error: Not allowed to start background user activity"
+ " that shouldn't be displayed for all users.");
break;
default:
out.println(
"Error: Activity not started, unknown error code " + res);
break;
}
System.err.println("Activity is" + (launched?"":" not") + " started");
PendingIntent p;
return launched;
}
private void startXwayland(String[] args) throws IOException {
System.err.println("Starting Xwayland");
ExecHelper.exec(XwaylandPath, args);
}
class Service extends ITermuxX11Internal.Stub {
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws RemoteException {
Starter.this.onTransact(code, data, reply, flags);
return super.onTransact(code, data, reply, flags);
}
@Override
public ParcelFileDescriptor getWaylandFD() throws RemoteException {
System.err.println("Got getWaylandFD");
try {
return Starter.this.getWaylandFD();
} catch (Throwable e) {
throw new RemoteException(e.getMessage());
}
}
@Override
public ParcelFileDescriptor getLogFD() throws RemoteException {
System.err.println("Got getLogFD");
System.err.println(logFD);
return logFD;
}
@Override
public void finish() throws RemoteException {
System.err.println("Got finish request");
handler.postDelayed(Starter.this::onFinish, 10);
}
}
private void exit(int status) {
exit(50, status);
}
private void exit(int delay, int status) {
handler.postDelayed(() -> System.exit(status), delay);
}
private native void checkXdgRuntimeDir();
private native int createWaylandSocket();
private native boolean checkWaylandSocket();
private native int openLogFD();
@SuppressWarnings("FieldMayBeFinal")
private static Handler handler;
static {
Looper.prepare();
handler = new Handler();
@SuppressLint("SdCardPath")
final String libPath = "/data/data/com.termux/files/usr/libexec/termux-x11/libstarter.so";
final File libFile = new File(libPath);
if (libFile.exists()) {
Runtime.getRuntime().load(libPath);
} else {
System.err.println(libPath + " does not exist. Please, check termux-x11 package installation.");
System.exit(1);
}
}
}