Browse Source
* 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.mdmaster
39 changed files with 1971 additions and 131 deletions
@ -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.
@ -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; |
||||
} |
||||
} |
@ -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 |
||||
|
@ -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,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,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 |
||||
* (such as pressing home) is performed. |
||||
* @hide |
||||
*/ |
||||
public static final int START_SWITCHES_CANCELED = FIRST_START_NON_FATAL_ERROR_CODE; |
||||
|
||||
/** |
||||
* Result for IActivityManaqer.startActivity: a new activity was attempted to be started |
||||
* while in Lock Task Mode. |
||||
* @hide |
||||
*/ |
||||
public static |