Let Termux:X11 run without sharedUserId="com.termux" (#31)
* Let Termux:X11 run without sharedUserId="com.termux" Make Termux:X11 start user-defined commands in Termux ($PREFIX/libexec/termux-x11/termux-startx11) Make debug_build.yml build companion package for termux and upload it as an artifact. Update README.md
This commit is contained in:
parent
db575f7887
commit
f9a9ce3167
|
@ -17,6 +17,9 @@ jobs:
|
|||
- name: Build
|
||||
run: |
|
||||
./gradlew assembleDebug
|
||||
- name: Build companion package
|
||||
run: |
|
||||
./build_termux_package
|
||||
- name: Store generated APK file
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
|
|
|
@ -36,3 +36,6 @@ Thumbs.db
|
|||
|
||||
#files containing github tokens
|
||||
github.properties
|
||||
|
||||
*.cxx
|
||||
app/.cxx/*
|
||||
|
|
81
README.md
81
README.md
|
@ -1,25 +1,25 @@
|
|||
|
||||
# Termux:X11
|
||||
|
||||
[![Join the chat at https://gitter.im/termux/termux](https://badges.gitter.im/termux/termux.svg)](https://gitter.im/termux/termux)
|
||||
|
||||
A [Termux](https://termux.com) add-on app providing Android frontend for Xwayland.
|
||||
|
||||
When developing (or packaging), note that this app needs to be signed with the same key as the main Termux app in order to have the permission to execute scripts.
|
||||
|
||||
## About
|
||||
Termux:X11 uses [Wayland](https://wayland.freedesktop.org/) display protocol. a modern replacement and the predecessor of the [X.org](https://www.x.org/wiki) server
|
||||
Termux:X11 uses [Wayland](https://wayland.freedesktop.org/) display protocol. a modern replacement and the predecessor of the [X.org](https://www.x.org/wiki) server.
|
||||
Pay attention that it is not a full-fledged Wayland server and it can not handle Wayland apps except Xwayland.
|
||||
|
||||
## How does it work?
|
||||
the Termux:X11 app writes files through `$PREFIX/tmp` in Termux directory by default and creates wayland sockets through that directory, this takes advantage of `sharedUserId` AndroidManifest attribute
|
||||
The Termux:X11 app's companion package executable creates socket through `$XDG_RUNTIME_DIR` in Termux directory by default.
|
||||
|
||||
the wayland sockets is the way for the graphical applications to communicate with. Termux X11 applications do not have wayland support yet, this kind of setup may not be straightforward and therefore additional packages should be installed in order for X11 applications to be run in Termux:X11
|
||||
The wayland sockets is the way for the graphical applications to communicate with. Termux X11 applications do not have wayland support yet, this kind of setup may not be straightforward and therefore additional packages should be installed in order for X11 applications to be run in Termux:X11
|
||||
|
||||
## Setup Instructions
|
||||
for this one. you must enable the `x11-repo` repository can be done by executing `pkg install x11-repo` command
|
||||
For this one. you must enable the `x11-repo` repository can be done by executing `pkg install x11-repo` command
|
||||
|
||||
for X applications to work, you must install `Xwayland` packages. you can do that by doing
|
||||
For X applications to work, you must install Termux-x11 companion package. You can do that by doing
|
||||
```
|
||||
pkg install xwayland
|
||||
pkg install termux-x11
|
||||
```
|
||||
|
||||
## Running Graphical Applications
|
||||
|
@ -28,7 +28,7 @@ to work with GUI applications, start Termux:X11 first. a toast message saying `S
|
|||
then you can start your desired graphical application by doing:
|
||||
```
|
||||
~ $ export XDG_RUNTIME_DIR=${TMPDIR}
|
||||
~ $ Xwayland :1 >/dev/null &
|
||||
~ $ termux-x11 :1 >/dev/null &
|
||||
~ $ env DISPLAY=:1 xfce4-session
|
||||
```
|
||||
You may replace `xfce4-session` if you use other than Xfce
|
||||
|
@ -42,5 +42,68 @@ You can fix this in your window manager settings (in the case of xfce4 and lxqt
|
|||
|
||||
![image](./img/dpi-scale.png)
|
||||
|
||||
## Using with 3rd party apps
|
||||
It is posssible to use Termux:X11 with 3rd party apps.
|
||||
You should start Termux:X11's activity with providing some additional data.
|
||||
```
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.RemoteException;
|
||||
import com.termux.x11.common.ITermuxX11Internal;
|
||||
...
|
||||
private final String TermuxX11ComponentName = "com.termux.x11/.TermuxX11StarterReceiver";
|
||||
|
||||
private void startTermuxX11() {
|
||||
Service svc = new Service();
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putBinder("", svc);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
class Service extends ITermuxX11Internal.Stub {
|
||||
@Override
|
||||
public ParcelFileDescriptor getWaylandFD() throws RemoteException {
|
||||
/*
|
||||
* nativeObtainWaylandFd() should create wayland-0
|
||||
* socket in your $XDG_RUNTIME_DIR and return it's
|
||||
* fd. You should not "listen()" this socket.
|
||||
*/
|
||||
int fd = nativeObtainWaylandFd();
|
||||
return ParcelFileDescriptor.adoptFd(fd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParcelFileDescriptor getLogFD() throws RemoteException {
|
||||
/*
|
||||
* nativeObtainLogFd() should create file that should
|
||||
* contain log. Pay attention that if you choose tty/pty
|
||||
* or fifo file Android will not allow writing it.
|
||||
* You can use `pipe` system call to create pipe.
|
||||
* Do not forget to change it's mode with `fchmod`.
|
||||
*/
|
||||
int fd = nativeObtainLogFd();
|
||||
return ParcelFileDescriptor.adoptFd(fd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() throws RemoteException {
|
||||
/*
|
||||
* Termux:X11 cals this function to to notify calling
|
||||
* process that init stage was completed
|
||||
* successfully.
|
||||
*/
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# License
|
||||
Released under the [GPLv3 license](https://www.gnu.org/licenses/gpl-3.0.html).
|
||||
|
|
|
@ -5,9 +5,10 @@ android {
|
|||
defaultConfig {
|
||||
applicationId "com.termux.x11"
|
||||
minSdkVersion 24
|
||||
//noinspection OldTargetApi
|
||||
targetSdkVersion 28
|
||||
versionCode 7
|
||||
versionName "1.02.04"
|
||||
versionCode 9
|
||||
versionName "1.02.06"
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
|
@ -30,6 +31,11 @@ android {
|
|||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
ndkBuild {
|
||||
path "src/main/jni/Android.mk"
|
||||
|
@ -47,6 +53,7 @@ dependencies {
|
|||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation 'androidx.appcompat:appcompat:1.3.1'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
|
||||
implementation project(path: ':common')
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test:runner:1.4.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.termux.x11"
|
||||
android:installLocation="internalOnly"
|
||||
android:sharedUserId="com.termux">
|
||||
>
|
||||
|
||||
<uses-permission android:name="com.termux.permission.RUN_COMMAND" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
|
||||
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"
|
||||
|
@ -18,16 +19,11 @@
|
|||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
tools:ignore="GoogleAppIndexingWarning"
|
||||
android:debuggable="true">
|
||||
>
|
||||
<service
|
||||
android:name=".LorieService"
|
||||
android:enabled="true"
|
||||
android:exported="false"/>
|
||||
<service
|
||||
android:name=".LorieTestService"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
android:process="com.termux.x11.separated"/>
|
||||
<activity android:name=".MainActivity"
|
||||
android:theme="@style/NoActionBar"
|
||||
android:launchMode="singleInstance"
|
||||
|
@ -50,8 +46,13 @@
|
|||
android:supportsPictureInPicture="false"
|
||||
android:resizeableActivity="true">
|
||||
</activity>
|
||||
<activity android:name=".TermuxX11StarterReceiver"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true" />
|
||||
<meta-data android:name="com.samsung.android.multidisplay.keep_process_alive" android:value="true"/>
|
||||
<meta-data android:name="android.allow_multiple_resumed_activities" android:value="true" />
|
||||
</application>
|
||||
|
||||
<queries>
|
||||
<package android:name="com.termux" />
|
||||
</queries>
|
||||
</manifest>
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
// ITermuxService.aidl
|
||||
package com.termux.x11;
|
||||
|
||||
import android.view.Surface;
|
||||
|
||||
interface ITermuxService {
|
||||
oneway void windowChanged(in Surface surface, int width, int height);
|
||||
oneway void pointerMotion(int x, int y);
|
||||
oneway void pointerScroll(int axis, float value);
|
||||
oneway void pointerButton(int button, int type);
|
||||
oneway void keyboardKey(int key, int type, int shift, String characters);
|
||||
}
|
Binary file not shown.
|
@ -2,6 +2,7 @@ package com.termux.x11;
|
|||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
|
@ -10,15 +11,22 @@ import android.app.Service;
|
|||
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.PixelFormat;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.PowerManager;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.provider.Settings;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
|
@ -30,13 +38,21 @@ import android.view.SurfaceHolder;
|
|||
import android.view.SurfaceView;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
import android.graphics.PixelFormat;
|
||||
|
||||
|
||||
@SuppressWarnings({"ConstantConditions", "SameParameterValue"})
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
|
||||
@SuppressWarnings({"ConstantConditions", "SameParameterValue", "SdCardPath"})
|
||||
@SuppressLint({"ClickableViewAccessibility", "StaticFieldLeak"})
|
||||
public class LorieService extends Service {
|
||||
|
||||
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";
|
||||
static final String ACTION_START_PREFERENCES_ACTIVITY = "com.termux.x11.start_preferences_activity";
|
||||
|
@ -46,7 +62,7 @@ public class LorieService extends Service {
|
|||
//private
|
||||
//static
|
||||
long compositor;
|
||||
private static ServiceEventListener listener = new ServiceEventListener();
|
||||
private static final ServiceEventListener listener = new ServiceEventListener();
|
||||
private static MainActivity act;
|
||||
|
||||
private TouchParser mTP;
|
||||
|
@ -70,14 +86,33 @@ public class LorieService extends Service {
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressLint("BatteryLife")
|
||||
@SuppressLint({"BatteryLife", "ObsoleteSdkInt"})
|
||||
@Override
|
||||
public void onCreate() {
|
||||
if (isServiceRunningInForeground(this, LorieService.class)) return;
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
|
||||
String Xdgpath = preferences.getString("CustXDG", "/data/data/com.termux/files/usr/tmp/");
|
||||
compositor = createLorieThread(Xdgpath);
|
||||
if (isServiceRunningInForeground(this, LorieService.class)) return;
|
||||
|
||||
String datadir = getApplicationInfo().dataDir;
|
||||
String[] dirs = {
|
||||
datadir + "/files/locale",
|
||||
datadir + "/files/xkb",
|
||||
};
|
||||
|
||||
for (String dir : dirs) {
|
||||
if (!(new File(dir)).exists()) {
|
||||
Log.e("LorieService", dir + " does not exist. Unpacking");
|
||||
try {
|
||||
InputStream zipStream = getAssets().open("X11.zip");
|
||||
File targetDirectory = new File(datadir + "/files/");
|
||||
unzip(zipStream, targetDirectory);
|
||||
break;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compositor = createLorieThread();
|
||||
|
||||
if (compositor == 0) {
|
||||
Log.e("LorieService", "compositor thread was not created");
|
||||
|
@ -189,6 +224,49 @@ public class LorieService extends Service {
|
|||
return START_REDELIVER_INTENT;
|
||||
}
|
||||
|
||||
private static void sendRunCommandInternal(Context ctx) {
|
||||
Intent intent = new Intent();
|
||||
intent.setClassName("com.termux", "com.termux.app.RunCommandService");
|
||||
intent.setAction("com.termux.RUN_COMMAND");
|
||||
intent.putExtra("com.termux.RUN_COMMAND_PATH",
|
||||
"/data/data/com.termux/files/usr/libexec/termux-x11/termux-startx11");
|
||||
intent.putExtra("com.termux.RUN_COMMAND_BACKGROUND", true);
|
||||
Log.d("LorieService", "sendRunCommand: " + intent);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
ctx.startForegroundService(intent);
|
||||
} else {
|
||||
ctx.startService(intent);
|
||||
}
|
||||
}
|
||||
|
||||
public static void sendRunCommand(AppCompatActivity act) {
|
||||
final String ERROR_MESSAGE =
|
||||
"It is impossible to start without " +
|
||||
"com.termux.permission.RUN_COMMAND permission. " +
|
||||
"Sorry.";
|
||||
if (act.checkSelfPermission("com.termux.permission.RUN_COMMAND") == PackageManager.PERMISSION_GRANTED) {
|
||||
LorieService.sendRunCommandInternal(act);
|
||||
} else {
|
||||
Log.d("MainActivity", "We have no permission to sendRunCommand(). Requesting it.");
|
||||
ActivityResultLauncher<String> requestPermissionLauncher =
|
||||
act.registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> {
|
||||
if (isGranted) {
|
||||
sendRunCommandInternal(act);
|
||||
} else {
|
||||
new AlertDialog.Builder(act)
|
||||
.setTitle("Insufficient permission")
|
||||
.setMessage(ERROR_MESSAGE)
|
||||
.setPositiveButton(android.R.string.yes,
|
||||
(dialog, which) -> act.finish())
|
||||
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||
.show();
|
||||
|
||||
}
|
||||
});
|
||||
requestPermissionLauncher.launch("com.termux.permission.RUN_COMMAND");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
|
@ -373,6 +451,58 @@ public class LorieService extends Service {
|
|||
}
|
||||
}
|
||||
|
||||
static final Handler handler = new Handler();
|
||||
private abstract static class Task implements Runnable {
|
||||
public Task() {
|
||||
handler.post(this);
|
||||
}
|
||||
@Override
|
||||
public void run() {
|
||||
LorieService svc = getInstance();
|
||||
if (svc == null || svc.compositor == 0 || act == null) {
|
||||
handler.postDelayed(this, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
run(svc);
|
||||
}
|
||||
public abstract void run(LorieService svc);
|
||||
}
|
||||
|
||||
public static void adoptWaylandFd(int fd) {
|
||||
new Task() {
|
||||
@Override
|
||||
public void run(LorieService svc) {
|
||||
svc.passWaylandFD(fd);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
public static void unzip(InputStream zipStream, File targetDirectory) throws IOException {
|
||||
try (ZipInputStream zis = new ZipInputStream(zipStream)) {
|
||||
ZipEntry ze;
|
||||
int count;
|
||||
byte[] buffer = new byte[8192];
|
||||
while ((ze = zis.getNextEntry()) != null) {
|
||||
File file = new File(targetDirectory, ze.getName());
|
||||
File dir = ze.isDirectory() ? file : file.getParentFile();
|
||||
if (!dir.isDirectory() && !dir.mkdirs())
|
||||
throw new FileNotFoundException("Failed to ensure directory: " +
|
||||
dir.getAbsolutePath());
|
||||
if (ze.isDirectory())
|
||||
continue;
|
||||
try (FileOutputStream fout = new FileOutputStream(file)) {
|
||||
while ((count = zis.read(buffer)) != -1)
|
||||
fout.write(buffer, 0, count);
|
||||
}
|
||||
long time = ze.getTime();
|
||||
if (time > 0)
|
||||
file.setLastModified(time);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void windowChanged(Surface s, int w, int h, int pw, int ph) {windowChanged(compositor, s, w, h, pw, ph);}
|
||||
private native void windowChanged(long compositor, Surface surface, int width, int height, int mmWidth, int mmHeight);
|
||||
|
||||
|
@ -400,10 +530,15 @@ public class LorieService extends Service {
|
|||
private void keyboardKey(int key, int type, int shift, String characters) {keyboardKey(compositor, key, type, shift, characters);}
|
||||
private native void keyboardKey(long compositor, int key, int type, int shift, String characters);
|
||||
|
||||
private native long createLorieThread(String CustXdgpath);
|
||||
private void passWaylandFD(int fd) {passWaylandFD(compositor, fd);}
|
||||
private native void passWaylandFD(long compositor, int fd);
|
||||
|
||||
private native long createLorieThread();
|
||||
|
||||
private native void terminate(long compositor);
|
||||
|
||||
public static native void startLogcatForFd(int fd);
|
||||
|
||||
static {
|
||||
System.loadLibrary("lorie");
|
||||
}
|
||||
|
|
|
@ -1,69 +0,0 @@
|
|||
package com.termux.x11;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
public class LorieTestService extends Service {
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
static void start(Context context) {
|
||||
Intent intent = new Intent(context, LorieTestService.class);
|
||||
intent.setAction("start");
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
context.startForegroundService(intent);
|
||||
} else {
|
||||
context.startService(intent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
Intent notificationIntent = new Intent(getApplicationContext(), MainActivity.class);
|
||||
notificationIntent.putExtra("foo_bar_extra_key", "foo_bar_extra_value");
|
||||
notificationIntent.setAction(Long.toString(System.currentTimeMillis()));
|
||||
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
//For creating the Foreground Service
|
||||
int priority = Notification.PRIORITY_HIGH;
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N)
|
||||
priority = NotificationManager.IMPORTANCE_HIGH;
|
||||
NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||
String channelId = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? getNotificationChannel(notificationManager) : "";
|
||||
Notification notification = new NotificationCompat.Builder(this, channelId)
|
||||
.setContentTitle("Termux:Wayland Test service")
|
||||
.setSmallIcon(R.drawable.ic_x11_icon)
|
||||
.setContentText("foreground service")
|
||||
.setContentIntent(pendingIntent)
|
||||
.setOngoing(true)
|
||||
.setPriority(priority)
|
||||
.setShowWhen(false)
|
||||
.setColor(0xFF607D8B)
|
||||
.build();
|
||||
startForeground(3, notification);
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
private String getNotificationChannel(NotificationManager notificationManager){
|
||||
String channelId = getResources().getString(R.string.app_name);
|
||||
String channelName = getResources().getString(R.string.app_name);
|
||||
NotificationChannel channel = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH);
|
||||
channel.setImportance(NotificationManager.IMPORTANCE_NONE);
|
||||
channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
|
||||
notificationManager.createNotificationChannel(channel);
|
||||
return channelId;
|
||||
}
|
||||
}
|
|
@ -1,10 +1,13 @@
|
|||
package com.termux.x11;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Build;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import android.os.Bundle;
|
||||
import android.view.KeyEvent;
|
||||
|
@ -14,7 +17,6 @@ import android.view.View;
|
|||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.Toast;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
|
@ -53,11 +55,16 @@ public class MainActivity extends AppCompatActivity {
|
|||
getWindow().
|
||||
getDecorView().
|
||||
setPointerIcon(PointerIcon.getSystemIcon(this, PointerIcon.TYPE_NULL));
|
||||
}
|
||||
|
||||
Intent i = getIntent();
|
||||
if (i != null && i.getStringExtra(LorieService.LAUNCHED_BY_COMPATION) == null) {
|
||||
LorieService.sendRunCommand(this);
|
||||
}
|
||||
}
|
||||
|
||||
int orientation;
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
public void onConfigurationChanged(@NonNull Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
|
||||
if (newConfig.orientation != orientation && kbd != null && kbd.getVisibility() == View.VISIBLE) {
|
||||
|
@ -85,7 +92,6 @@ public class MainActivity extends AppCompatActivity {
|
|||
super.onWindowFocusChanged(hasFocus);
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
Window window = getWindow();
|
||||
View decorView = window.getDecorView();
|
||||
|
||||
if (preferences.getBoolean("Reseed", true))
|
||||
{
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
package com.termux.x11;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.termux.x11.common.ITermuxX11Internal;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public class TermuxX11StarterReceiver extends Activity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Intent intent = getIntent();
|
||||
if (intent != null)
|
||||
handleIntent(intent);
|
||||
|
||||
Intent main = new Intent(this, MainActivity.class);
|
||||
main.putExtra(LorieService.LAUNCHED_BY_COMPATION, 1);
|
||||
main.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
startActivity(main);
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
private void log(String s) {
|
||||
Log.e("NewIntent", s);
|
||||
}
|
||||
|
||||
private void handleIntent(Intent intent) {
|
||||
final String extraName = "com.termux.x11.starter";
|
||||
Bundle bundle;
|
||||
IBinder token;
|
||||
ITermuxX11Internal svc;
|
||||
ParcelFileDescriptor pfd = null;
|
||||
String toastText;
|
||||
|
||||
// We do not use Object.equals(Object obj) for the case same intent was passed twice
|
||||
if (intent == null)
|
||||
return;
|
||||
|
||||
toastText = intent.getStringExtra("toast");
|
||||
if (toastText != null)
|
||||
Toast.makeText(this, toastText, Toast.LENGTH_LONG).show();
|
||||
|
||||
bundle = intent.getBundleExtra(extraName);
|
||||
if (bundle == null) {
|
||||
log("Got intent without " + extraName + " bundle");
|
||||
return;
|
||||
}
|
||||
|
||||
token = bundle.getBinder("");
|
||||
if (token == null) {
|
||||
log("got " + extraName + " extra but it has no Binder token");
|
||||
return;
|
||||
}
|
||||
|
||||
svc = ITermuxX11Internal.Stub.asInterface(token);
|
||||
if (svc == null) {
|
||||
log("Could not create " + extraName + " service proxy");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
pfd = svc.getWaylandFD();
|
||||
if (pfd != null)
|
||||
LorieService.adoptWaylandFd(pfd.getFd());
|
||||
} catch (Exception e) {
|
||||
log("Failed to receive ParcelFileDescriptor");
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
try {
|
||||
pfd = svc.getLogFD();
|
||||
if (pfd != null) {
|
||||
LorieService.startLogcatForFd(pfd.getFd());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log("Failed to receive ParcelFileDescriptor");
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
try {
|
||||
svc.finish();
|
||||
} catch (RemoteException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
ROOT_PATH := $(call my-dir)
|
||||
include $(ROOT_PATH)/libxkbcommon/Android.mk
|
||||
include $(ROOT_PATH)/lorie/Android.mk
|
||||
include $(ROOT_PATH)/../../../../common/src/main/jni/Android.mk
|
||||
|
|
|
@ -37,11 +37,11 @@ LOCAL_SRC_FILES := \
|
|||
LOCAL_CFLAGS := \
|
||||
-std=c99 -Wall -Werror -Wno-unused-parameter -Wno-missing-field-initializers -Wimplicit-function-declaration \
|
||||
-D_GNU_SOURCE \
|
||||
-DXLOCALEDIR=\"/data/data/com.termux/files/usr/share/X11/locale\" \
|
||||
-DXLOCALEDIR=\"/data/data/com.termux.x11/files/locale\" \
|
||||
-DDEFAULT_XKB_LAYOUT=\"us\" \
|
||||
-DDEFAULT_XKB_MODEL=\"pc105\" \
|
||||
-DDEFAULT_XKB_RULES=\"evdev\" \
|
||||
-DDFLT_XKB_CONFIG_ROOT=\"/data/data/com.termux/files/usr/share/X11/xkb\"
|
||||
-DDFLT_XKB_CONFIG_ROOT=\"/data/data/com.termux.x11/files/xkb\"
|
||||
LOCAL_C_INCLUDES := $(LOCAL_PATH)/xkbcommon $(LOCAL_PATH)/xkbcommon/src $(LOCAL_PATH)/xkbcommon/include
|
||||
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/xkbcommon/
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
|
|
|
@ -11,6 +11,10 @@
|
|||
#include <lorie-egl-helper.hpp>
|
||||
#include <android/native_window_jni.h>
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
#include <sys/socket.h>
|
||||
#include <dirent.h>
|
||||
|
||||
#define unused __attribute__((__unused__))
|
||||
|
||||
#define DEFAULT_WIDTH 480
|
||||
#define DEFAULT_HEIGHT 800
|
||||
|
@ -26,6 +30,7 @@ public:
|
|||
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 layout_change_callback(char *layout);
|
||||
void passfd(int fd);
|
||||
|
||||
void on_egl_init();
|
||||
void on_egl_uninit();
|
||||
|
@ -160,6 +165,10 @@ void LorieBackendAndroid::layout_change_callback(char *layout) {
|
|||
|
||||
keyboard_keymap_changed();
|
||||
}
|
||||
void LorieBackendAndroid::passfd(int fd) {
|
||||
listen(fd, 128);
|
||||
wl_display_add_socket_fd(display, fd);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
|
@ -180,16 +189,25 @@ static LorieBackendAndroid* fromLong(jlong v) {
|
|||
}
|
||||
|
||||
extern "C" JNIEXPORT jlong JNICALL
|
||||
JNI_DECLARE(LorieService, createLorieThread)(JNIEnv *env, jobject __unused instance, jstring CustXdgpath) {
|
||||
|
||||
const char *pathx = env->GetStringUTFChars(CustXdgpath, NULL);
|
||||
|
||||
setenv("XDG_RUNTIME_DIR", pathx, 1);
|
||||
JNI_DECLARE(LorieService, createLorieThread)(unused JNIEnv *env, unused jobject instance) {
|
||||
return (jlong) new LorieBackendAndroid;
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
JNI_DECLARE(LorieService, terminate)(JNIEnv __unused *env, jobject __unused instance, jlong jcompositor) {
|
||||
JNI_DECLARE(LorieService, passWaylandFD)(unused JNIEnv *env, unused jobject instance, jlong jcompositor, jint fd) {
|
||||
if (jcompositor == 0) return;
|
||||
LorieBackendAndroid *b = fromLong(jcompositor);
|
||||
char path[256] = {0};
|
||||
char realpath[256] = {0};
|
||||
sprintf(path, "/proc/self/fd/%d", fd);
|
||||
readlink(path, realpath, sizeof(realpath));
|
||||
LOGI("JNI: got fd %d (%s)", fd, realpath);
|
||||
|
||||
b->passfd(fd);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
JNI_DECLARE(LorieService, terminate)(unused JNIEnv *env, unused jobject instance, jlong jcompositor) {
|
||||
if (jcompositor == 0) return;
|
||||
LorieBackendAndroid *b = fromLong(jcompositor);
|
||||
LOGI("JNI: requested termination");
|
||||
|
@ -198,7 +216,7 @@ JNI_DECLARE(LorieService, terminate)(JNIEnv __unused *env, jobject __unused inst
|
|||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
JNI_DECLARE(LorieService, windowChanged)(JNIEnv *env, jobject __unused instance, jlong jcompositor, jobject jsurface, jint width, jint height, jint mmWidth, jint mmHeight) {
|
||||
JNI_DECLARE(LorieService, windowChanged)(JNIEnv *env, unused jobject instance, jlong jcompositor, jobject jsurface, jint width, jint height, jint mmWidth, jint mmHeight) {
|
||||
if (jcompositor == 0) return;
|
||||
LorieBackendAndroid *b = fromLong(jcompositor);
|
||||
|
||||
|
@ -209,7 +227,7 @@ JNI_DECLARE(LorieService, windowChanged)(JNIEnv *env, jobject __unused instance,
|
|||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
JNI_DECLARE(LorieService, touchDown)(JNIEnv __unused *env, jobject __unused instance, jlong jcompositor, jint id, jint x, jint y) {
|
||||
JNI_DECLARE(LorieService, touchDown)(unused JNIEnv *env, unused jobject instance, jlong jcompositor, jint id, jint x, jint y) {
|
||||
if (jcompositor == 0) return;
|
||||
LorieBackendAndroid *b = fromLong(jcompositor);
|
||||
LOGV("JNI: touch down");
|
||||
|
@ -218,7 +236,7 @@ JNI_DECLARE(LorieService, touchDown)(JNIEnv __unused *env, jobject __unused inst
|
|||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
JNI_DECLARE(LorieService, touchMotion)(JNIEnv __unused *env, jobject __unused instance, jlong jcompositor, jint id, jint x, jint y) {
|
||||
JNI_DECLARE(LorieService, touchMotion)(unused JNIEnv *env, unused jobject instance, jlong jcompositor, jint id, jint x, jint y) {
|
||||
if (jcompositor == 0) return;
|
||||
LorieBackendAndroid *b = fromLong(jcompositor);
|
||||
LOGV("JNI: touch motion");
|
||||
|
@ -227,7 +245,7 @@ JNI_DECLARE(LorieService, touchMotion)(JNIEnv __unused *env, jobject __unused in
|
|||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
JNI_DECLARE(LorieService, touchUp)(JNIEnv __unused *env, jobject __unused instance, jlong jcompositor, jint id) {
|
||||
JNI_DECLARE(LorieService, touchUp)(unused JNIEnv *env, unused jobject instance, jlong jcompositor, jint id) {
|
||||
if (jcompositor == 0) return;
|
||||
LorieBackendAndroid *b = fromLong(jcompositor);
|
||||
LOGV("JNI: touch up");
|
||||
|
@ -236,7 +254,7 @@ JNI_DECLARE(LorieService, touchUp)(JNIEnv __unused *env, jobject __unused instan
|
|||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
JNI_DECLARE(LorieService, touchFrame)(JNIEnv __unused *env, jobject __unused instance, jlong jcompositor) {
|
||||
JNI_DECLARE(LorieService, touchFrame)(unused JNIEnv *env, unused jobject instance, jlong jcompositor) {
|
||||
if (jcompositor == 0) return;
|
||||
LorieBackendAndroid *b = fromLong(jcompositor);
|
||||
LOGV("JNI: touch frame");
|
||||
|
@ -245,7 +263,7 @@ JNI_DECLARE(LorieService, touchFrame)(JNIEnv __unused *env, jobject __unused ins
|
|||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
JNI_DECLARE(LorieService, pointerMotion)(JNIEnv __unused *env, jobject __unused instance, jlong jcompositor, jint x, jint y) {
|
||||
JNI_DECLARE(LorieService, pointerMotion)(unused JNIEnv *env, unused jobject instance, jlong jcompositor, jint x, jint y) {
|
||||
if (jcompositor == 0) return;
|
||||
LorieBackendAndroid *b = fromLong(jcompositor);
|
||||
|
||||
|
@ -254,7 +272,7 @@ JNI_DECLARE(LorieService, pointerMotion)(JNIEnv __unused *env, jobject __unused
|
|||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
JNI_DECLARE(LorieService, pointerScroll)(JNIEnv __unused *env, jobject __unused instance, jlong jcompositor, jint axis, jfloat value) {
|
||||
JNI_DECLARE(LorieService, pointerScroll)(unused JNIEnv *env, unused jobject instance, jlong jcompositor, jint axis, jfloat value) {
|
||||
if (jcompositor == 0) return;
|
||||
LorieBackendAndroid *b = fromLong(jcompositor);
|
||||
|
||||
|
@ -263,7 +281,7 @@ JNI_DECLARE(LorieService, pointerScroll)(JNIEnv __unused *env, jobject __unused
|
|||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
JNI_DECLARE(LorieService, pointerButton)(JNIEnv __unused *env, jobject __unused instance, jlong jcompositor, jint button, jint type) {
|
||||
JNI_DECLARE(LorieService, pointerButton)(unused JNIEnv *env, unused jobject instance, jlong jcompositor, jint button, jint type) {
|
||||
if (jcompositor == 0) return;
|
||||
LorieBackendAndroid *b = fromLong(jcompositor);
|
||||
|
||||
|
@ -275,7 +293,7 @@ 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 __unused instance,
|
||||
JNI_DECLARE(LorieService, keyboardKey)(JNIEnv *env, unused jobject instance,
|
||||
jlong jcompositor, jint type,
|
||||
jint key_code, jint jshift,
|
||||
jstring characters_) {
|
||||
|
@ -325,3 +343,66 @@ JNI_DECLARE(LorieService, keyboardKey)(JNIEnv *env, jobject __unused instance,
|
|||
|
||||
if (characters_ != NULL) 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")) == NULL) {
|
||||
LOGE("opendir: %s", strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
while((dir_elem = readdir(proc)) != NULL) {
|
||||
if (!(pid = (pid_t) atoi (dir_elem->d_name)) || pid == self || !sameUid(pid))
|
||||
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") != NULL) {
|
||||
if (kill(pid, SIGKILL) < 0) {
|
||||
LOGE("kill %d (%s): %s", pid, link, strerror);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void fork(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 jobject thiz, jint fd) {
|
||||
killAllLogcats();
|
||||
|
||||
LOGI("Starting logcat with output to given fd");
|
||||
fork([]() {
|
||||
execl("/system/bin/logcat", "logcat", "-c", NULL);
|
||||
LOGE("exec logcat: %s", strerror(errno));
|
||||
});
|
||||
|
||||
fork([fd]() {
|
||||
dup2(fd, 1);
|
||||
dup2(fd, 2);
|
||||
execl("/system/bin/logcat", "logcat", NULL);
|
||||
LOGE("exec logcat: %s", strerror(errno));
|
||||
});
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
PACKAGE_PATH=app/build/outputs/apk/debug/termux-x11.deb
|
||||
|
||||
INTERMEDIATES=starter/build/intermediates
|
||||
NDKBUILD_DIR=starter/build/intermediates/ndkBuild/debug/obj/local
|
||||
DATA_DIR=$INTERMEDIATES/data
|
||||
CONTROL_DIR=$INTERMEDIATES/control
|
||||
PACKAGE_DIR=$INTERMEDIATES/package
|
||||
PREFIX=$DATA_DIR/data/data/com.termux/files/usr
|
||||
|
||||
rm -rf $PACKAGE_PATH $DATA_DIR $CONTROL_DIR $PACKAGE_DIR
|
||||
|
||||
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
|
||||
for arch in armeabi-v7a arm64-v8a x86 x86_64; do
|
||||
mkdir -p $PREFIX/libexec/termux-x11/$arch/
|
||||
cp $NDKBUILD_DIR/$arch/libstarter.so \
|
||||
$PREFIX/libexec/termux-x11/$arch/
|
||||
done
|
||||
|
||||
mkdir -p $CONTROL_DIR
|
||||
cat <<EOF > $CONTROL_DIR/control
|
||||
Package: termux-x11
|
||||
Architecture: all
|
||||
Maintainer: Twaik Yont @twaik
|
||||
Version: 1.02.06
|
||||
Homepage: https://github.com/termux/termux-x11
|
||||
Depends: xwayland
|
||||
Description: Companion package for termux-x11 app
|
||||
EOF
|
||||
|
||||
cat <<EOF > $CONTROL_DIR/postinst
|
||||
#!/data/data/com.termux/files/usr/bin/bash
|
||||
[ -z "\$PREFIX" ] && PREFIX=/data/data/com.termux/files/usr
|
||||
ABI=
|
||||
case \`uname -m\` in
|
||||
arm) ABI=armeabi-v7a;;
|
||||
aarch64) ABI=arm64-v8a;;
|
||||
i686) ABI=x86;;
|
||||
x86_64) ABI=x86_64;;
|
||||
esac
|
||||
mv \$PREFIX/libexec/termux-x11/\$ABI/libstarter.so \$PREFIX/libexec/termux-x11/
|
||||
EOF
|
||||
|
||||
mkdir -p $PACKAGE_DIR
|
||||
echo 2.0 > $PACKAGE_DIR/debian-binary
|
||||
tar -cJf $PACKAGE_DIR/data.tar.xz -C $DATA_DIR .
|
||||
tar -czf $PACKAGE_DIR/control.tar.gz -C $CONTROL_DIR .
|
||||
|
||||
ar -rsc $PACKAGE_PATH \
|
||||
$PACKAGE_DIR/debian-binary \
|
||||
$PACKAGE_DIR/control.tar.gz \
|
||||
$PACKAGE_DIR/data.tar.xz
|
|
@ -0,0 +1 @@
|
|||
/build
|
|
@ -0,0 +1,38 @@
|
|||
plugins {
|
||||
id 'com.android.library'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
buildToolsVersion "30.0.2"
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 27
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
consumerProguardFiles "consumer-rules.pro"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:1.3.1'
|
||||
implementation 'com.google.android.material:material:1.4.0'
|
||||
testImplementation 'junit:junit:4.+'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
# 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
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.termux.x11.common">
|
||||
|
||||
</manifest>
|
|
@ -0,0 +1,8 @@
|
|||
package com.termux.x11.common;
|
||||
|
||||
// This interface is used by utility on termux side.
|
||||
interface ITermuxX11Internal {
|
||||
ParcelFileDescriptor getWaylandFD();
|
||||
ParcelFileDescriptor getLogFD();
|
||||
void finish();
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
include $(call all-subdir-makefiles)
|
|
@ -1,6 +1,6 @@
|
|||
#Thu Sep 09 20:28:30 IDT 2021
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionSha256Sum=a8da5b02437a60819cad23e10fc7e9cf32bcb57029d9cb277e26eeff76ce014b
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
include ':starter'
|
||||
include ':common'
|
||||
include ':app'
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
/build
|
|
@ -0,0 +1,44 @@
|
|||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
defaultConfig {
|
||||
applicationId "com.termux.termuxam"
|
||||
minSdkVersion 21
|
||||
// Note: targetSdkVersion affects only tests,
|
||||
// normally, even though this is packaged as apk,
|
||||
// it's not loaded as apk so targetSdkVersion is ignored.
|
||||
// targetSdkVersion this must be < 28 because this application accesses hidden apis
|
||||
//noinspection OldTargetApi
|
||||
targetSdkVersion 27
|
||||
versionCode 1
|
||||
versionName "0.1"
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
ndkBuild {
|
||||
path "src/main/jni/Android.mk"
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(path: ':common')
|
||||
//implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
//testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'com.android.support.test:runner:1.0.2'
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
# 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
|
|
@ -0,0 +1,7 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.termux.x11.starter">
|
||||
|
||||
<application
|
||||
android:label="TermuxX11Starter"
|
||||
android:supportsRtl="true" />
|
||||
</manifest>
|
|
@ -0,0 +1,13 @@
|
|||
package android.content;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
/**
|
||||
* Stub - will be replaced by system at runtime
|
||||
*/
|
||||
public interface IIntentReceiver {
|
||||
public static abstract class Stub implements IIntentReceiver {
|
||||
public abstract void performReceive(Intent intent, int resultCode, String data, Bundle extras,
|
||||
boolean ordered, boolean sticky, int sendingUser);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,197 @@
|
|||
package com.termux.x11.starter;
|
||||
|
||||
/**
|
||||
* \@hide-hidden constants
|
||||
*/
|
||||
public class ActivityManager {
|
||||
private static final int FIRST_START_FATAL_ERROR_CODE = -100;
|
||||
private static final int LAST_START_FATAL_ERROR_CODE = -1;
|
||||
private static final int FIRST_START_SUCCESS_CODE = 0;
|
||||
private static final int LAST_START_SUCCESS_CODE = 99;
|
||||
private static final int FIRST_START_NON_FATAL_ERROR_CODE = 100;
|
||||
private static final int LAST_START_NON_FATAL_ERROR_CODE = 199;
|
||||
|
||||
/**
|
||||
* Result for IActivityManager.startVoiceActivity: active session is currently hidden.
|
||||
* @hide
|
||||
*/
|
||||
public static final int START_VOICE_HIDDEN_SESSION = FIRST_START_FATAL_ERROR_CODE;
|
||||
|
||||
/**
|
||||
* Result for IActivityManager.startVoiceActivity: active session does not match
|
||||
* the requesting token.
|
||||
* @hide
|
||||
*/
|
||||
public static final int START_VOICE_NOT_ACTIVE_SESSION = FIRST_START_FATAL_ERROR_CODE + 1;
|
||||
|
||||
/**
|
||||
* Result for IActivityManager.startActivity: trying to start a background user
|
||||
* activity that shouldn't be displayed for all users.
|
||||
* @hide
|
||||
*/
|
||||
public static final int START_NOT_CURRENT_USER_ACTIVITY = FIRST_START_FATAL_ERROR_CODE + 2;
|
||||
|
||||
/**
|
||||
* Result for IActivityManager.startActivity: trying to start an activity under voice
|
||||
* control when that activity does not support the VOICE category.
|
||||
* @hide
|
||||
*/
|
||||
public static final int START_NOT_VOICE_COMPATIBLE = FIRST_START_FATAL_ERROR_CODE + 3;
|
||||
|
||||
/**
|
||||
* Result for IActivityManager.startActivity: an error where the
|
||||
* start had to be canceled.
|
||||
* @hide
|
||||
*/
|
||||
public static final int START_CANCELED = FIRST_START_FATAL_ERROR_CODE + 4;
|
||||
|
||||
/**
|
||||
* Result for IActivityManager.startActivity: an error where the
|
||||
* thing being started is not an activity.
|
||||
* @hide
|
||||
*/
|
||||
public static final int START_NOT_ACTIVITY = FIRST_START_FATAL_ERROR_CODE + 5;
|
||||
|
||||
/**
|
||||
* Result for IActivityManager.startActivity: an error where the
|
||||
* caller does not have permission to start the activity.
|
||||
* @hide
|
||||
*/
|
||||
public static final int START_PERMISSION_DENIED = FIRST_START_FATAL_ERROR_CODE + 6;
|
||||
|
||||
/**
|
||||
* Result for IActivityManager.startActivity: an error where the
|
||||
* caller has requested both to forward a result and to receive
|
||||
* a result.
|
||||
* @hide
|
||||
*/
|
||||
public static final int START_FORWARD_AND_REQUEST_CONFLICT = FIRST_START_FATAL_ERROR_CODE + 7;
|
||||
|
||||
/**
|
||||
* Result for IActivityManager.startActivity: an error where the
|
||||
* requested class is not found.
|
||||
* @hide
|
||||
*/
|
||||
public static final int START_CLASS_NOT_FOUND = FIRST_START_FATAL_ERROR_CODE + 8;
|
||||
|
||||
/**
|
||||
* Result for IActivityManager.startActivity: an error where the
|
||||
* given Intent could not be resolved to an activity.
|
||||
* @hide
|
||||
*/
|
||||
public static final int START_INTENT_NOT_RESOLVED = FIRST_START_FATAL_ERROR_CODE + 9;
|
||||
|
||||
/**
|
||||
* Result for IActivityManager.startAssistantActivity: active session is currently hidden.
|
||||
* @hide
|
||||
*/
|
||||
public static final int START_ASSISTANT_HIDDEN_SESSION = FIRST_START_FATAL_ERROR_CODE + 10;
|
||||
|
||||
/**
|
||||
* Result for IActivityManager.startAssistantActivity: active session does not match
|
||||
* the requesting token.
|
||||
* @hide
|
||||
*/
|
||||
public static final int START_ASSISTANT_NOT_ACTIVE_SESSION = FIRST_START_FATAL_ERROR_CODE + 11;
|
||||
|
||||
/**
|
||||
* Result for IActivityManaqer.startActivity: the activity was started
|
||||
* successfully as normal.
|
||||
* @hide
|
||||
*/
|
||||
public static final int START_SUCCESS = FIRST_START_SUCCESS_CODE;
|
||||
|
||||
/**
|
||||
* Result for IActivityManaqer.startActivity: the caller asked that the Intent not
|
||||
* be executed if it is the recipient, and that is indeed the case.
|
||||
* @hide
|
||||
*/
|
||||
public static final int START_RETURN_INTENT_TO_CALLER = FIRST_START_SUCCESS_CODE + 1;
|
||||
|
||||
/**
|
||||
* Result for IActivityManaqer.startActivity: activity wasn't really started, but
|
||||
* a task was simply brought to the foreground.
|
||||
* @hide
|
||||
*/
|
||||
public static final int START_TASK_TO_FRONT = FIRST_START_SUCCESS_CODE + 2;
|
||||
|
||||
/**
|
||||
* Result for IActivityManaqer.startActivity: activity wasn't really started, but
|
||||
* the given Intent was given to the existing top activity.
|
||||
* @hide
|
||||
*/
|
||||
public static final int START_DELIVERED_TO_TOP = FIRST_START_SUCCESS_CODE + 3;
|
||||
|
||||
/**
|
||||
* Result for IActivityManaqer.startActivity: request was canceled because
|
||||
* app switches are temporarily canceled to ensure the user's last request
|
||||
|