From cf30c2ecf9a4745bd2fcaebc2728fa0dac84dbc4 Mon Sep 17 00:00:00 2001 From: RohitKushvaha01 Date: Wed, 17 Dec 2025 16:04:19 +0530 Subject: [PATCH 1/6] feat: move service to background --- package-lock.json | 21 +++++-------------- package.json | 2 +- .../terminal/src/android/Executor.java | 8 +++++++ .../terminal/src/android/TerminalService.java | 3 +++ src/plugins/terminal/www/Executor.js | 6 ++++++ 5 files changed, 23 insertions(+), 17 deletions(-) diff --git a/package-lock.json b/package-lock.json index c80e0ef00..76c306d95 100644 --- a/package-lock.json +++ b/package-lock.json @@ -128,7 +128,6 @@ "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -2959,7 +2958,8 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/markdown-it": { "version": "14.1.2", @@ -2976,7 +2976,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/node": { "version": "24.2.1", @@ -3289,8 +3290,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz", "integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", @@ -3321,7 +3321,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3355,7 +3354,6 @@ "version": "8.12.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -3736,7 +3734,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001733", "electron-to-chromium": "^1.5.199", @@ -3839,7 +3836,6 @@ "integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@chevrotain/cst-dts-gen": "11.0.3", "@chevrotain/gast": "11.0.3", @@ -6020,7 +6016,6 @@ "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", "license": "MIT", - "peer": true, "dependencies": { "argparse": "^2.0.1", "entities": "^4.4.0", @@ -6906,7 +6901,6 @@ "url": "https://github.com/sponsors/ai" } ], - "peer": true, "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", @@ -7061,7 +7055,6 @@ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -7228,7 +7221,6 @@ "version": "6.12.6", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -8061,7 +8053,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -8333,7 +8324,6 @@ "integrity": "sha512-B4t+nJqytPeuZlHuIKTbalhljIFXeNRqrUGAQgTGlfOl2lXXKXw+yZu6bicycP+PUlM44CxBjCFD6aciKFT3LQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -8383,7 +8373,6 @@ "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.6.1", "@webpack-cli/configtest": "^3.0.1", diff --git a/package.json b/package.json index 1de0509ce..38d4e607f 100644 --- a/package.json +++ b/package.json @@ -129,4 +129,4 @@ "yargs": "^18.0.0" }, "browserslist": "cover 100%,not android < 5" -} \ No newline at end of file +} diff --git a/src/plugins/terminal/src/android/Executor.java b/src/plugins/terminal/src/android/Executor.java index 3e0b4e83d..1b0dbf5f9 100644 --- a/src/plugins/terminal/src/android/Executor.java +++ b/src/plugins/terminal/src/android/Executor.java @@ -235,6 +235,14 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo return true; } + if (action.equals("moveToBackground")) { + Intent intent = new Intent(context, TerminalService.class); + intent.setAction(TerminalService.MOVE_TO_BACKGROUND); + context.startService(intent); + callbackContext.success("Service moved to background mode"); + return true; + } + // For all other actions, ensure service is bound first if (!ensureServiceBound(callbackContext)) { // Error already sent by ensureServiceBound diff --git a/src/plugins/terminal/src/android/TerminalService.java b/src/plugins/terminal/src/android/TerminalService.java index f8e26c67f..37073fc8f 100644 --- a/src/plugins/terminal/src/android/TerminalService.java +++ b/src/plugins/terminal/src/android/TerminalService.java @@ -43,6 +43,7 @@ public class TerminalService extends Service { public static final String CHANNEL_ID = "terminal_exec_channel"; public static final String ACTION_EXIT_SERVICE = "com.foxdebug.acode.ACTION_EXIT_SERVICE"; + public static final String MOVE_TO_BACKGROUND = "com.foxdebug.acode.MOVE_TO_BACKGROUND"; public static final String ACTION_TOGGLE_WAKE_LOCK = "com.foxdebug.acode.ACTION_TOGGLE_WAKE_LOCK"; private final Map processes = new ConcurrentHashMap<>(); @@ -70,6 +71,8 @@ public int onStartCommand(Intent intent, int flags, int startId) { return START_NOT_STICKY; } else if (ACTION_TOGGLE_WAKE_LOCK.equals(action)) { toggleWakeLock(); + }else if(MOVE_TO_BACKGROUND.equals(action)){ + stopForeground(true); } } return START_STICKY; diff --git a/src/plugins/terminal/www/Executor.js b/src/plugins/terminal/www/Executor.js index 78103fafc..f056feea3 100644 --- a/src/plugins/terminal/www/Executor.js +++ b/src/plugins/terminal/www/Executor.js @@ -82,6 +82,12 @@ const Executor = { }); }, + moveToBackground() { + return new Promise((resolve, reject) => { + exec(resolve, reject, "Executor", "moveToBackground", []); + }); + } + /** * Terminates a running process. * From a2aa28d077edbf9fc3dffbe46bd222785cbea206 Mon Sep 17 00:00:00 2001 From: RohitKushvaha01 Date: Wed, 17 Dec 2025 16:17:43 +0530 Subject: [PATCH 2/6] feat: move to foreground --- src/plugins/terminal/src/android/Executor.java | 8 ++++++++ .../terminal/src/android/TerminalService.java | 16 +++++++++++++--- src/plugins/terminal/www/Executor.js | 8 +++++++- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/plugins/terminal/src/android/Executor.java b/src/plugins/terminal/src/android/Executor.java index 1b0dbf5f9..edd0cea15 100644 --- a/src/plugins/terminal/src/android/Executor.java +++ b/src/plugins/terminal/src/android/Executor.java @@ -243,6 +243,14 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo return true; } + if (action.equals("moveToForeground")) { + Intent intent = new Intent(context, TerminalService.class); + intent.setAction(TerminalService.MOVE_TO_FOREGROUND); + context.startService(intent); + callbackContext.success("Service moved to foreground mode"); + return true; + } + // For all other actions, ensure service is bound first if (!ensureServiceBound(callbackContext)) { // Error already sent by ensureServiceBound diff --git a/src/plugins/terminal/src/android/TerminalService.java b/src/plugins/terminal/src/android/TerminalService.java index 37073fc8f..304868325 100644 --- a/src/plugins/terminal/src/android/TerminalService.java +++ b/src/plugins/terminal/src/android/TerminalService.java @@ -43,8 +43,10 @@ public class TerminalService extends Service { public static final String CHANNEL_ID = "terminal_exec_channel"; public static final String ACTION_EXIT_SERVICE = "com.foxdebug.acode.ACTION_EXIT_SERVICE"; - public static final String MOVE_TO_BACKGROUND = "com.foxdebug.acode.MOVE_TO_BACKGROUND"; + public static final String MOVE_TO_BACKGROUND = "com.foxdebug.acode.MOVE_TO_BACKGROUND"; + public static final String MOVE_TO_FOREGROUND = "com.foxdebug.acode.MOVE_TO_FOREGROUND"; public static final String ACTION_TOGGLE_WAKE_LOCK = "com.foxdebug.acode.ACTION_TOGGLE_WAKE_LOCK"; + public static boolean Default_Foreground = true; private final Map processes = new ConcurrentHashMap<>(); private final Map processInputs = new ConcurrentHashMap<>(); @@ -72,7 +74,12 @@ public int onStartCommand(Intent intent, int flags, int startId) { } else if (ACTION_TOGGLE_WAKE_LOCK.equals(action)) { toggleWakeLock(); }else if(MOVE_TO_BACKGROUND.equals(action)){ + Default_Foreground = false; stopForeground(true); + }else if(MOVE_TO_FOREGROUND.equals(action)){ + Default_Foreground = true; + createNotificationChannel(); + updateNotification(); } } return START_STICKY; @@ -341,8 +348,11 @@ private void cleanup(String id) { @Override public void onCreate() { super.onCreate(); - createNotificationChannel(); - updateNotification(); + if(Default_Foreground){ + createNotificationChannel(); + updateNotification(); + } + } private void createNotificationChannel() { diff --git a/src/plugins/terminal/www/Executor.js b/src/plugins/terminal/www/Executor.js index f056feea3..5b9b6926a 100644 --- a/src/plugins/terminal/www/Executor.js +++ b/src/plugins/terminal/www/Executor.js @@ -86,7 +86,13 @@ const Executor = { return new Promise((resolve, reject) => { exec(resolve, reject, "Executor", "moveToBackground", []); }); - } + }, + + moveToForeground() { + return new Promise((resolve, reject) => { + exec(resolve, reject, "Executor", "moveToForeground", []); + }); + }, /** * Terminates a running process. From 41973b31a0497d33156c8b554b4b991ed0575548 Mon Sep 17 00:00:00 2001 From: RohitKushvaha01 Date: Mon, 22 Dec 2025 13:31:33 +0530 Subject: [PATCH 3/6] feat: added backgroudExecutor --- src/plugins/terminal/plugin.xml | 3 + .../src/android/BackgroundExecutor.java | 163 ++++++++++++++++ .../terminal/src/android/ProcessManager.java | 99 ++++++++++ .../terminal/src/android/ProcessUtils.java | 44 +++++ .../terminal/src/android/StreamHandler.java | 31 ++++ .../terminal/src/android/TerminalService.java | 175 +++++------------- src/plugins/terminal/www/Executor.js | 170 ++++++++++------- 7 files changed, 488 insertions(+), 197 deletions(-) create mode 100644 src/plugins/terminal/src/android/BackgroundExecutor.java create mode 100644 src/plugins/terminal/src/android/ProcessManager.java create mode 100644 src/plugins/terminal/src/android/ProcessUtils.java create mode 100644 src/plugins/terminal/src/android/StreamHandler.java diff --git a/src/plugins/terminal/plugin.xml b/src/plugins/terminal/plugin.xml index fbde6010f..4c4ab48b7 100644 --- a/src/plugins/terminal/plugin.xml +++ b/src/plugins/terminal/plugin.xml @@ -19,7 +19,10 @@ + + + diff --git a/src/plugins/terminal/src/android/BackgroundExecutor.java b/src/plugins/terminal/src/android/BackgroundExecutor.java new file mode 100644 index 000000000..9a3d0258b --- /dev/null +++ b/src/plugins/terminal/src/android/BackgroundExecutor.java @@ -0,0 +1,163 @@ +package com.foxdebug.acode.rk.exec.terminal; + +import org.apache.cordova.*; +import org.json.*; +import java.io.*; +import java.util.*; +import java.util.concurrent.*; + +public class BackgroundExecutor extends CordovaPlugin { + + private final Map processes = new ConcurrentHashMap<>(); + private final Map processInputs = new ConcurrentHashMap<>(); + private final Map processCallbacks = new ConcurrentHashMap<>(); + private ProcessManager processManager; + + @Override + public void initialize(CordovaInterface cordova, CordovaWebView webView) { + super.initialize(cordova, webView); + this.processManager = new ProcessManager(cordova.getContext()); + } + + @Override + public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { + switch (action) { + case "start": + String pid = UUID.randomUUID().toString(); + startProcess(pid, args.getString(0), args.getString(1).equals("true"), callbackContext); + return true; + case "write": + writeToProcess(args.getString(0), args.getString(1), callbackContext); + return true; + case "stop": + stopProcess(args.getString(0), callbackContext); + return true; + case "exec": + exec(args.getString(0), args.getString(1).equals("true"), callbackContext); + return true; + case "isRunning": + isProcessRunning(args.getString(0), callbackContext); + return true; + case "loadLibrary": + loadLibrary(args.getString(0), callbackContext); + return true; + default: + callbackContext.error("Unknown action: " + action); + return false; + } + } + + private void exec(String cmd, boolean useAlpine, CallbackContext callbackContext) { + cordova.getThreadPool().execute(() -> { + try { + ProcessManager.ExecResult result = processManager.executeCommand(cmd, useAlpine); + + if (result.isSuccess()) { + callbackContext.success(result.stdout); + } else { + callbackContext.error(result.getErrorMessage()); + } + } catch (Exception e) { + callbackContext.error("Exception: " + e.getMessage()); + } + }); + } + + private void startProcess(String pid, String cmd, boolean useAlpine, CallbackContext callbackContext) { + cordova.getThreadPool().execute(() -> { + try { + ProcessBuilder builder = processManager.createProcessBuilder(cmd, useAlpine); + Process process = builder.start(); + + processes.put(pid, process); + processInputs.put(pid, process.getOutputStream()); + processCallbacks.put(pid, callbackContext); + + sendPluginResult(callbackContext, pid, true); + + // Stream stdout + new Thread(() -> StreamHandler.streamOutput( + process.getInputStream(), + line -> sendPluginMessage(pid, "stdout:" + line) + )).start(); + + // Stream stderr + new Thread(() -> StreamHandler.streamOutput( + process.getErrorStream(), + line -> sendPluginMessage(pid, "stderr:" + line) + )).start(); + + int exitCode = process.waitFor(); + sendPluginMessage(pid, "exit:" + exitCode); + cleanup(pid); + } catch (Exception e) { + callbackContext.error("Failed to start process: " + e.getMessage()); + } + }); + } + + private void writeToProcess(String pid, String input, CallbackContext callbackContext) { + try { + OutputStream os = processInputs.get(pid); + if (os != null) { + StreamHandler.writeToStream(os, input); + callbackContext.success("Written to process"); + } else { + callbackContext.error("Process not found or closed"); + } + } catch (IOException e) { + callbackContext.error("Write error: " + e.getMessage()); + } + } + + private void stopProcess(String pid, CallbackContext callbackContext) { + Process process = processes.get(pid); + if (process != null) { + ProcessUtils.killProcessTree(process); + cleanup(pid); + callbackContext.success("Process terminated"); + } else { + callbackContext.error("No such process"); + } + } + + private void isProcessRunning(String pid, CallbackContext callbackContext) { + Process process = processes.get(pid); + + if (process != null) { + String status = ProcessUtils.isAlive(process) ? "running" : "exited"; + if (status.equals("exited")) cleanup(pid); + callbackContext.success(status); + } else { + callbackContext.success("not_found"); + } + } + + private void loadLibrary(String path, CallbackContext callbackContext) { + try { + System.load(path); + callbackContext.success("Library loaded successfully."); + } catch (Exception e) { + callbackContext.error("Failed to load library: " + e.getMessage()); + } + } + + private void sendPluginResult(CallbackContext ctx, String message, boolean keepCallback) { + PluginResult result = new PluginResult(PluginResult.Status.OK, message); + result.setKeepCallback(keepCallback); + ctx.sendPluginResult(result); + } + + private void sendPluginMessage(String pid, String message) { + CallbackContext ctx = processCallbacks.get(pid); + if (ctx != null) { + sendPluginResult(ctx, message, true); + } + } + + private void cleanup(String pid) { + processes.remove(pid); + processInputs.remove(pid); + processCallbacks.remove(pid); + } +} \ No newline at end of file diff --git a/src/plugins/terminal/src/android/ProcessManager.java b/src/plugins/terminal/src/android/ProcessManager.java new file mode 100644 index 000000000..65f1ab503 --- /dev/null +++ b/src/plugins/terminal/src/android/ProcessManager.java @@ -0,0 +1,99 @@ +package com.foxdebug.acode.rk.exec.terminal; + +import android.content.Context; +import android.content.pm.PackageManager; +import java.io.*; +import java.util.Map; +import java.util.TimeZone; + +public class ProcessManager { + + private final Context context; + + public ProcessManager(Context context) { + this.context = context; + } + + /** + * Creates a ProcessBuilder with common environment setup + */ + public ProcessBuilder createProcessBuilder(String cmd, boolean useAlpine) { + String xcmd = useAlpine ? "source $PREFIX/init-sandbox.sh " + cmd : cmd; + ProcessBuilder builder = new ProcessBuilder("sh", "-c", xcmd); + setupEnvironment(builder.environment()); + return builder; + } + + /** + * Sets up common environment variables + */ + private void setupEnvironment(Map env) { + env.put("PREFIX", context.getFilesDir().getAbsolutePath()); + env.put("NATIVE_DIR", context.getApplicationInfo().nativeLibraryDir); + + TimeZone tz = TimeZone.getDefault(); + env.put("ANDROID_TZ", tz.getID()); + + try { + int target = context.getPackageManager() + .getPackageInfo(context.getPackageName(), 0) + .applicationInfo.targetSdkVersion; + env.put("FDROID", String.valueOf(target <= 28)); + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } + } + + /** + * Reads all output from a stream + */ + public static String readStream(InputStream stream) throws IOException { + BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); + StringBuilder output = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + output.append(line).append("\n"); + } + return output.toString(); + } + + /** + * Executes a command and returns the result + */ + public ExecResult executeCommand(String cmd, boolean useAlpine) throws Exception { + ProcessBuilder builder = createProcessBuilder(cmd, useAlpine); + Process process = builder.start(); + + String stdout = readStream(process.getInputStream()); + String stderr = readStream(process.getErrorStream()); + int exitCode = process.waitFor(); + + return new ExecResult(exitCode, stdout.trim(), stderr.trim()); + } + + /** + * Result container for command execution + */ + public static class ExecResult { + public final int exitCode; + public final String stdout; + public final String stderr; + + public ExecResult(int exitCode, String stdout, String stderr) { + this.exitCode = exitCode; + this.stdout = stdout; + this.stderr = stderr; + } + + public boolean isSuccess() { + return exitCode == 0; + } + + public String getErrorMessage() { + if (!stderr.isEmpty()) { + return stderr; + } + return "Command exited with code: " + exitCode; + } + } +} \ No newline at end of file diff --git a/src/plugins/terminal/src/android/ProcessUtils.java b/src/plugins/terminal/src/android/ProcessUtils.java new file mode 100644 index 000000000..3cdd7946b --- /dev/null +++ b/src/plugins/terminal/src/android/ProcessUtils.java @@ -0,0 +1,44 @@ +package com.foxdebug.acode.rk.exec.terminal; + +import java.lang.reflect.Field; + +public class ProcessUtils { + + /** + * Gets the PID of a process using reflection + */ + public static long getPid(Process process) { + try { + Field f = process.getClass().getDeclaredField("pid"); + f.setAccessible(true); + return f.getLong(process); + } catch (Exception e) { + return -1; + } + } + + /** + * Checks if a process is still alive + */ + public static boolean isAlive(Process process) { + try { + process.exitValue(); + return false; + } catch(IllegalThreadStateException e) { + return true; + } + } + + /** + * Forcefully kills a process and its children + */ + public static void killProcessTree(Process process) { + try { + long pid = getPid(process); + if (pid > 0) { + Runtime.getRuntime().exec("kill -9 -" + pid); + } + } catch (Exception ignored) {} + process.destroy(); + } +} \ No newline at end of file diff --git a/src/plugins/terminal/src/android/StreamHandler.java b/src/plugins/terminal/src/android/StreamHandler.java new file mode 100644 index 000000000..f758fd63d --- /dev/null +++ b/src/plugins/terminal/src/android/StreamHandler.java @@ -0,0 +1,31 @@ +package com.foxdebug.acode.rk.exec.terminal; + +import java.io.*; + +public class StreamHandler { + + public interface OutputListener { + void onLine(String line); + } + + /** + * Streams output from an InputStream to a listener + */ + public static void streamOutput(InputStream inputStream, OutputListener listener) { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { + String line; + while ((line = reader.readLine()) != null) { + listener.onLine(line); + } + } catch (IOException ignored) { + } + } + + /** + * Writes input to an OutputStream + */ + public static void writeToStream(OutputStream outputStream, String input) throws IOException { + outputStream.write((input + "\n").getBytes()); + outputStream.flush(); + } +} \ No newline at end of file diff --git a/src/plugins/terminal/src/android/TerminalService.java b/src/plugins/terminal/src/android/TerminalService.java index 304868325..bf164ad5f 100644 --- a/src/plugins/terminal/src/android/TerminalService.java +++ b/src/plugins/terminal/src/android/TerminalService.java @@ -16,21 +16,11 @@ import android.os.PowerManager; import android.os.RemoteException; import androidx.core.app.NotificationCompat; -import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; import java.io.OutputStream; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; -import java.lang.reflect.Field; - -import java.util.TimeZone; -import java.util.Map; -import java.util.HashMap; - - public class TerminalService extends Service { @@ -44,7 +34,7 @@ public class TerminalService extends Service { public static final String ACTION_EXIT_SERVICE = "com.foxdebug.acode.ACTION_EXIT_SERVICE"; public static final String MOVE_TO_BACKGROUND = "com.foxdebug.acode.MOVE_TO_BACKGROUND"; - public static final String MOVE_TO_FOREGROUND = "com.foxdebug.acode.MOVE_TO_FOREGROUND"; + public static final String MOVE_TO_FOREGROUND = "com.foxdebug.acode.MOVE_TO_FOREGROUND"; public static final String ACTION_TOGGLE_WAKE_LOCK = "com.foxdebug.acode.ACTION_TOGGLE_WAKE_LOCK"; public static boolean Default_Foreground = true; @@ -57,6 +47,17 @@ public class TerminalService extends Service { private PowerManager.WakeLock wakeLock; private boolean isWakeLockHeld = false; + private ProcessManager processManager; + + @Override + public void onCreate() { + super.onCreate(); + processManager = new ProcessManager(this); + if(Default_Foreground){ + createNotificationChannel(); + updateNotification(); + } + } @Override public IBinder onBind(Intent intent) { @@ -73,10 +74,10 @@ public int onStartCommand(Intent intent, int flags, int startId) { return START_NOT_STICKY; } else if (ACTION_TOGGLE_WAKE_LOCK.equals(action)) { toggleWakeLock(); - }else if(MOVE_TO_BACKGROUND.equals(action)){ + } else if(MOVE_TO_BACKGROUND.equals(action)){ Default_Foreground = false; stopForeground(true); - }else if(MOVE_TO_FOREGROUND.equals(action)){ + } else if(MOVE_TO_FOREGROUND.equals(action)){ Default_Foreground = true; createNotificationChannel(); updateNotification(); @@ -97,7 +98,7 @@ public void handleMessage(Message msg) { String cmd = bundle.getString("cmd"); String alpine = bundle.getString("alpine"); clientMessengers.put(id, clientMessenger); - startProcess(id, cmd, alpine); + startProcess(id, cmd, "true".equals(alpine)); break; case MSG_WRITE_TO_PROCESS: String input = bundle.getString("input"); @@ -113,7 +114,7 @@ public void handleMessage(Message msg) { String execCmd = bundle.getString("cmd"); String execAlpine = bundle.getString("alpine"); clientMessengers.put(id, clientMessenger); - exec(id, execCmd, execAlpine); + exec(id, execCmd, "true".equals(execAlpine)); break; } } @@ -147,31 +148,28 @@ private void releaseWakeLock() { } } - private void startProcess(String pid, String cmd, String alpine) { + private void startProcess(String pid, String cmd, boolean useAlpine) { threadPool.execute(() -> { try { - String xcmd = alpine.equals("true") ? "source $PREFIX/init-sandbox.sh " + cmd : cmd; - ProcessBuilder builder = new ProcessBuilder("sh", "-c", xcmd); - - Map env = builder.environment(); - env.put("PREFIX", getFilesDir().getAbsolutePath()); - env.put("NATIVE_DIR", getApplicationInfo().nativeLibraryDir); - TimeZone tz = TimeZone.getDefault(); - String timezoneId = tz.getID(); - env.put("ANDROID_TZ", timezoneId); - - try { - int target = getPackageManager().getPackageInfo(getPackageName(), 0).applicationInfo.targetSdkVersion; - env.put("FDROID", String.valueOf(target <= 28)); - } catch (Exception e) { - e.printStackTrace(); - } - + ProcessBuilder builder = processManager.createProcessBuilder(cmd, useAlpine); Process process = builder.start(); + processes.put(pid, process); processInputs.put(pid, process.getOutputStream()); - threadPool.execute(() -> streamOutput(process.getInputStream(), pid, "stdout")); - threadPool.execute(() -> streamOutput(process.getErrorStream(), pid, "stderr")); + + // Stream stdout + threadPool.execute(() -> + StreamHandler.streamOutput(process.getInputStream(), + line -> sendMessageToClient(pid, "stdout", line)) + ); + + // Stream stderr + threadPool.execute(() -> + StreamHandler.streamOutput(process.getErrorStream(), + line -> sendMessageToClient(pid, "stderr", line)) + ); + + // Wait for process completion threadPool.execute(() -> { try { int exitCode = process.waitFor(); @@ -190,53 +188,17 @@ private void startProcess(String pid, String cmd, String alpine) { }); } - private void exec(String execId, String cmd, String alpine) { + private void exec(String execId, String cmd, boolean useAlpine) { threadPool.execute(() -> { try { - String xcmd = alpine.equals("true") ? "source $PREFIX/init-sandbox.sh " + cmd : cmd; - ProcessBuilder builder = new ProcessBuilder("sh", "-c", xcmd); - Map env = builder.environment(); - env.put("PREFIX", getFilesDir().getAbsolutePath()); - env.put("NATIVE_DIR", getApplicationInfo().nativeLibraryDir); - TimeZone tz = TimeZone.getDefault(); - String timezoneId = tz.getID(); - env.put("ANDROID_TZ", timezoneId); - - try { - int target = getPackageManager().getPackageInfo(getPackageName(), 0).applicationInfo.targetSdkVersion; - env.put("FDROID", String.valueOf(target <= 28)); - } catch (Exception e) { - e.printStackTrace(); - } - - Process process = builder.start(); - BufferedReader stdOutReader = new BufferedReader( - new InputStreamReader(process.getInputStream())); - StringBuilder stdOut = new StringBuilder(); - String line; - while ((line = stdOutReader.readLine()) != null) { - stdOut.append(line).append("\n"); - } - - BufferedReader stdErrReader = new BufferedReader( - new InputStreamReader(process.getErrorStream())); - StringBuilder stdErr = new StringBuilder(); - while ((line = stdErrReader.readLine()) != null) { - stdErr.append(line).append("\n"); - } - - int exitCode = process.waitFor(); - - if (exitCode == 0) { - sendExecResultToClient(execId, true, stdOut.toString().trim()); + ProcessManager.ExecResult result = processManager.executeCommand(cmd, useAlpine); + + if (result.isSuccess()) { + sendExecResultToClient(execId, true, result.stdout); } else { - String errorOutput = stdErr.toString().trim(); - if (errorOutput.isEmpty()) { - errorOutput = "Command exited with code: " + exitCode; - } - sendExecResultToClient(execId, false, errorOutput); + sendExecResultToClient(execId, false, result.getErrorMessage()); } - + cleanup(execId); } catch (Exception e) { sendExecResultToClient(execId, false, "Exception: " + e.getMessage()); @@ -245,16 +207,6 @@ private void exec(String execId, String cmd, String alpine) { }); } - private void streamOutput(InputStream inputStream, String pid, String streamType) { - try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { - String line; - while ((line = reader.readLine()) != null) { - sendMessageToClient(pid, streamType, line); - } - } catch (IOException ignored) { - } - } - private void sendMessageToClient(String id, String action, String data) { Messenger clientMessenger = clientMessengers.get(id); if (clientMessenger != null) { @@ -294,67 +246,33 @@ private void writeToProcess(String pid, String input) { try { OutputStream os = processInputs.get(pid); if (os != null) { - os.write((input + "\n").getBytes()); - os.flush(); + StreamHandler.writeToStream(os, input); } } catch (IOException e) { e.printStackTrace(); } } - private long getPid(Process process) { - try { - Field f = process.getClass().getDeclaredField("pid"); - f.setAccessible(true); - return f.getLong(process); - } catch (Exception e) { - return -1; - } -} - - private void stopProcess(String pid) { Process process = processes.get(pid); if (process != null) { - try { - Runtime.getRuntime().exec("kill -9 -" + getPid(process)); - } catch (Exception ignored) {} - process.destroy(); + ProcessUtils.killProcessTree(process); cleanup(pid); } } private void isProcessRunning(String pid, Messenger clientMessenger) { Process process = processes.get(pid); - String status = process != null && isProcessAlive(process) ? "running" : "not_found"; + String status = process != null && ProcessUtils.isAlive(process) ? "running" : "not_found"; sendMessageToClient(pid, "isRunning", status); } - private boolean isProcessAlive(Process process) { - try { - process.exitValue(); - return false; - } catch(IllegalThreadStateException e) { - return true; - } - } - private void cleanup(String id) { processes.remove(id); processInputs.remove(id); clientMessengers.remove(id); } - @Override - public void onCreate() { - super.onCreate(); - if(Default_Foreground){ - createNotificationChannel(); - updateNotification(); - } - - } - private void createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel serviceChannel = new NotificationChannel( @@ -404,10 +322,7 @@ public void onDestroy() { releaseWakeLock(); for (Process process : processes.values()) { - try { - Runtime.getRuntime().exec("kill -9 -" + getPid(process)); - } catch (Exception ignored) {} - process.destroyForcibly(); + ProcessUtils.killProcessTree(process); } processes.clear(); @@ -423,4 +338,4 @@ private int resolveDrawableId(String... names) { } return android.R.drawable.sym_def_app_icon; } -} +} \ No newline at end of file diff --git a/src/plugins/terminal/www/Executor.js b/src/plugins/terminal/www/Executor.js index 5b9b6926a..2f9970d96 100644 --- a/src/plugins/terminal/www/Executor.js +++ b/src/plugins/terminal/www/Executor.js @@ -1,14 +1,17 @@ /** - * @module Executor + * @class Executor * @description - * This module provides an interface to run shell commands from a Cordova app. + * This class provides an interface to run shell commands from a Cordova app. * It supports real-time process streaming, writing input to running processes, * stopping them, and executing one-time commands. */ const exec = require('cordova/exec'); -const Executor = { +class Executor { + constructor(BackgroundExecutor = false) { + this.ExecutorType = BackgroundExecutor ? "BackgroundExecutor" : "Executor"; + } /** * Starts a shell process and enables real-time streaming of stdout, stderr, and exit status. * @@ -17,149 +20,182 @@ const Executor = { * - `"stdout"`: Standard output line. * - `"stderr"`: Standard error line. * - `"exit"`: Exit code of the process. - * @param {boolean} alpine - Whether to run the command inside the Alpine sandbox environment (`true`) or on Android directly (`false`). + * @param {boolean} [alpine=false] - Whether to run the command inside the Alpine sandbox environment (`true`) or on Android directly (`false`). * @returns {Promise} Resolves with a unique process ID (UUID) used for future references like `write()` or `stop()`. * * @example - * Executor.start('sh', (type, data) => { + * const executor = new Executor(); + * executor.start('sh', (type, data) => { * console.log(`[${type}] ${data}`); * }).then(uuid => { - * Executor.write(uuid, 'echo Hello World'); - * Executor.stop(uuid); + * executor.write(uuid, 'echo Hello World'); + * executor.stop(uuid); * }); */ - - - start(command, onData) { - this.start(command, onData, false); - }, - - start(command, onData, alpine) { - console.log("start: " + command); - + start(command, onData, alpine = false) { return new Promise((resolve, reject) => { let first = true; - exec(async (message) => { - console.log(message); - if (first) { - first = false; - await new Promise(resolve => setTimeout(resolve, 100)); - // First message is always the process UUID - resolve(message); - } else { - const match = message.match(/^([^:]+):(.*)$/); - if (match) { - const prefix = match[1]; // e.g. "stdout" - const message = match[2].trim(); // output - onData(prefix, message); + exec( + async (message) => { + console.log(message); + if (first) { + first = false; + await new Promise(resolve => setTimeout(resolve, 100)); + // First message is always the process UUID + resolve(message); } else { - onData("unknown", message); + const match = message.match(/^([^:]+):(.*)$/); + if (match) { + const prefix = match[1]; // e.g. "stdout" + const content = match[2].trim(); // output + onData(prefix, content); + } else { + onData("unknown", message); + } } - } - }, + }, reject, - "Executor", + this.ExecutorType, "start", [command, String(alpine)] ); }); - }, + } /** * Sends input to a running process's stdin. * - * @param {string} uuid - The process ID returned by {@link Executor.start}. + * @param {string} uuid - The process ID returned by {@link Executor#start}. * @param {string} input - Input string to send (e.g., shell commands). * @returns {Promise} Resolves once the input is written. * * @example - * Executor.write(uuid, 'ls /sdcard'); + * executor.write(uuid, 'ls /sdcard'); */ write(uuid, input) { console.log("write: " + input + " to " + uuid); return new Promise((resolve, reject) => { - exec(resolve, reject, "Executor", "write", [uuid, input]); + exec(resolve, reject, this.ExecutorType, "write", [uuid, input]); }); - }, + } + /** + * Moves the executor service to the background (stops foreground notification). + * + * @returns {Promise} Resolves when the service is moved to background. + * + * @example + * executor.moveToBackground(); + */ moveToBackground() { return new Promise((resolve, reject) => { - exec(resolve, reject, "Executor", "moveToBackground", []); + exec(resolve, reject, this.ExecutorType, "moveToBackground", []); }); - }, + } + /** + * Moves the executor service to the foreground (shows notification). + * + * @returns {Promise} Resolves when the service is moved to foreground. + * + * @example + * executor.moveToForeground(); + */ moveToForeground() { return new Promise((resolve, reject) => { - exec(resolve, reject, "Executor", "moveToForeground", []); + exec(resolve, reject, this.ExecutorType, "moveToForeground", []); }); - }, + } /** * Terminates a running process. * - * @param {string} uuid - The process ID returned by {@link Executor.start}. + * @param {string} uuid - The process ID returned by {@link Executor#start}. * @returns {Promise} Resolves when the process has been stopped. * * @example - * Executor.stop(uuid); + * executor.stop(uuid); */ stop(uuid) { return new Promise((resolve, reject) => { - exec(resolve, reject, "Executor", "stop", [uuid]); + exec(resolve, reject, this.ExecutorType, "stop", [uuid]); }); - }, + } /** * Checks if a process is still running. * - * @param {string} uuid - The process ID returned by {@link Executor.start}. + * @param {string} uuid - The process ID returned by {@link Executor#start}. * @returns {Promise} Resolves `true` if the process is running, `false` otherwise. * * @example - * const isAlive = await Executor.isRunning(uuid); + * const isAlive = await executor.isRunning(uuid); */ isRunning(uuid) { return new Promise((resolve, reject) => { - exec((result) => { - resolve(result === "running"); - }, reject, "Executor", "isRunning", [uuid]); + exec( + (result) => { + resolve(result === "running"); + }, + reject, + this.ExecutorType, + "isRunning", + [uuid] + ); }); - }, + } + /** + * Stops the executor service completely. + * + * @returns {Promise} Resolves when the service has been stopped. + * + * @example + * executor.stopService(); + */ stopService() { return new Promise((resolve, reject) => { - exec(resolve, reject, "Executor", "stopService", []); + exec(resolve, reject, this.ExecutorType, "stopService", []); }); - }, + } /** * Executes a shell command once and waits for it to finish. - * Unlike {@link Executor.start}, this does not stream output. + * Unlike {@link Executor#start}, this does not stream output. * * @param {string} command - The shell command to execute. - * @param {boolean} alpine - Whether to run the command in the Alpine sandbox (`true`) or Android environment (`false`). + * @param {boolean} [alpine=false] - Whether to run the command in the Alpine sandbox (`true`) or Android environment (`false`). * @returns {Promise} Resolves with standard output on success, rejects with an error or standard error on failure. * * @example - * Executor.execute('ls -l') + * executor.execute('ls -l') * .then(console.log) * .catch(console.error); */ - execute(command) { - this.execute(command, false); - } - , - execute(command, alpine) { + execute(command, alpine = false) { return new Promise((resolve, reject) => { - exec(resolve, reject, "Executor", "exec", [command, String(alpine)]); + exec(resolve, reject, this.ExecutorType, "exec", [command, String(alpine)]); }); - }, + } + /** + * Loads a native library from the specified path. + * + * @param {string} path - The path to the native library to load. + * @returns {Promise} Resolves when the library has been loaded. + * + * @example + * executor.loadLibrary('/path/to/library.so'); + */ loadLibrary(path) { return new Promise((resolve, reject) => { - exec(resolve, reject, "Executor", "loadLibrary", [path]); + exec(resolve, reject, this.ExecutorType, "loadLibrary", [path]); }); } -}; +} + +//backward compatibility +const executorInstance = new Executor(); +executorInstance.BackgroundExecutor = new Executor(true); -module.exports = Executor; +module.exports = executorInstance; \ No newline at end of file From 52d7d540f3beb9d8c92877f72df03587ece00b1a Mon Sep 17 00:00:00 2001 From: RohitKushvaha01 Date: Mon, 22 Dec 2025 15:29:42 +0530 Subject: [PATCH 4/6] feat: add missing source files --- src/plugins/terminal/plugin.xml | 3 +++ src/plugins/terminal/www/Executor.js | 8 ++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/plugins/terminal/plugin.xml b/src/plugins/terminal/plugin.xml index 4c4ab48b7..a12e4242e 100644 --- a/src/plugins/terminal/plugin.xml +++ b/src/plugins/terminal/plugin.xml @@ -20,6 +20,9 @@ + + + diff --git a/src/plugins/terminal/www/Executor.js b/src/plugins/terminal/www/Executor.js index 2f9970d96..419713e6d 100644 --- a/src/plugins/terminal/www/Executor.js +++ b/src/plugins/terminal/www/Executor.js @@ -26,7 +26,7 @@ class Executor { * @example * const executor = new Executor(); * executor.start('sh', (type, data) => { - * console.log(`[${type}] ${data}`); + * //console.log(`[${type}] ${data}`); * }).then(uuid => { * executor.write(uuid, 'echo Hello World'); * executor.stop(uuid); @@ -37,7 +37,7 @@ class Executor { let first = true; exec( async (message) => { - console.log(message); + //console.log(message); if (first) { first = false; await new Promise(resolve => setTimeout(resolve, 100)); @@ -73,7 +73,7 @@ class Executor { * executor.write(uuid, 'ls /sdcard'); */ write(uuid, input) { - console.log("write: " + input + " to " + uuid); + //console.log("write: " + input + " to " + uuid); return new Promise((resolve, reject) => { exec(resolve, reject, this.ExecutorType, "write", [uuid, input]); }); @@ -169,7 +169,7 @@ class Executor { * * @example * executor.execute('ls -l') - * .then(console.log) + * .then(//console.log) * .catch(console.error); */ execute(command, alpine = false) { From 93618a4e8f51f6e5b1a52110290c2611caed5384 Mon Sep 17 00:00:00 2001 From: RohitKushvaha01 Date: Mon, 22 Dec 2025 16:03:26 +0530 Subject: [PATCH 5/6] feat: add BackgroundExecutor as plugin --- package.json | 4 ++-- src/plugins/terminal/plugin.xml | 5 ++++- src/plugins/terminal/src/android/AlpineDocumentProvider.java | 1 + src/plugins/terminal/src/android/BackgroundExecutor.java | 1 + src/plugins/terminal/src/android/Executor.java | 1 + src/plugins/terminal/src/android/ProcessManager.java | 1 + src/plugins/terminal/src/android/ProcessUtils.java | 1 + src/plugins/terminal/src/android/StreamHandler.java | 2 +- src/plugins/terminal/src/android/TerminalService.java | 2 ++ 9 files changed, 14 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 38d4e607f..f29c7648b 100644 --- a/package.json +++ b/package.json @@ -37,8 +37,8 @@ "cordova-plugin-browser": {}, "cordova-plugin-sftp": {}, "cordova-plugin-system": {}, - "com.foxdebug.acode.rk.exec.terminal": {}, - "com.foxdebug.acode.rk.exec.proot": {} + "com.foxdebug.acode.rk.exec.proot": {}, + "com.foxdebug.acode.rk.exec.terminal": {} }, "platforms": [ "android" diff --git a/src/plugins/terminal/plugin.xml b/src/plugins/terminal/plugin.xml index a12e4242e..72273257f 100644 --- a/src/plugins/terminal/plugin.xml +++ b/src/plugins/terminal/plugin.xml @@ -16,12 +16,15 @@ + + + - + diff --git a/src/plugins/terminal/src/android/AlpineDocumentProvider.java b/src/plugins/terminal/src/android/AlpineDocumentProvider.java index 7cdcaec90..97a4dd50a 100644 --- a/src/plugins/terminal/src/android/AlpineDocumentProvider.java +++ b/src/plugins/terminal/src/android/AlpineDocumentProvider.java @@ -20,6 +20,7 @@ import java.util.LinkedList; import java.util.Locale; import com.foxdebug.acode.R; +import com.foxdebug.acode.rk.exec.terminal.*; public class AlpineDocumentProvider extends DocumentsProvider { diff --git a/src/plugins/terminal/src/android/BackgroundExecutor.java b/src/plugins/terminal/src/android/BackgroundExecutor.java index 9a3d0258b..79e3f33ad 100644 --- a/src/plugins/terminal/src/android/BackgroundExecutor.java +++ b/src/plugins/terminal/src/android/BackgroundExecutor.java @@ -5,6 +5,7 @@ import java.io.*; import java.util.*; import java.util.concurrent.*; +import com.foxdebug.acode.rk.exec.terminal.*; public class BackgroundExecutor extends CordovaPlugin { diff --git a/src/plugins/terminal/src/android/Executor.java b/src/plugins/terminal/src/android/Executor.java index edd0cea15..5d0e90777 100644 --- a/src/plugins/terminal/src/android/Executor.java +++ b/src/plugins/terminal/src/android/Executor.java @@ -27,6 +27,7 @@ import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import android.app.Activity; +import com.foxdebug.acode.rk.exec.terminal.*; public class Executor extends CordovaPlugin { diff --git a/src/plugins/terminal/src/android/ProcessManager.java b/src/plugins/terminal/src/android/ProcessManager.java index 65f1ab503..fe7be24ba 100644 --- a/src/plugins/terminal/src/android/ProcessManager.java +++ b/src/plugins/terminal/src/android/ProcessManager.java @@ -5,6 +5,7 @@ import java.io.*; import java.util.Map; import java.util.TimeZone; +import com.foxdebug.acode.rk.exec.terminal.*; public class ProcessManager { diff --git a/src/plugins/terminal/src/android/ProcessUtils.java b/src/plugins/terminal/src/android/ProcessUtils.java index 3cdd7946b..10ccc4034 100644 --- a/src/plugins/terminal/src/android/ProcessUtils.java +++ b/src/plugins/terminal/src/android/ProcessUtils.java @@ -1,6 +1,7 @@ package com.foxdebug.acode.rk.exec.terminal; import java.lang.reflect.Field; +import com.foxdebug.acode.rk.exec.terminal.*; public class ProcessUtils { diff --git a/src/plugins/terminal/src/android/StreamHandler.java b/src/plugins/terminal/src/android/StreamHandler.java index f758fd63d..ab4c643c7 100644 --- a/src/plugins/terminal/src/android/StreamHandler.java +++ b/src/plugins/terminal/src/android/StreamHandler.java @@ -1,7 +1,7 @@ package com.foxdebug.acode.rk.exec.terminal; import java.io.*; - +import com.foxdebug.acode.rk.exec.terminal.*; public class StreamHandler { public interface OutputListener { diff --git a/src/plugins/terminal/src/android/TerminalService.java b/src/plugins/terminal/src/android/TerminalService.java index bf164ad5f..1d8281ee8 100644 --- a/src/plugins/terminal/src/android/TerminalService.java +++ b/src/plugins/terminal/src/android/TerminalService.java @@ -21,6 +21,8 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; +import com.foxdebug.acode.rk.exec.terminal.*; + public class TerminalService extends Service { From 89d33a405c58f95127ac597b1f2ea76ea94e2fd0 Mon Sep 17 00:00:00 2001 From: RohitKushvaha01 Date: Mon, 22 Dec 2025 16:38:48 +0530 Subject: [PATCH 6/6] feat: use background executor for Terminal.isAxsRunning() --- src/plugins/terminal/www/Terminal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/terminal/www/Terminal.js b/src/plugins/terminal/www/Terminal.js index 279cefbf6..d63c29c16 100644 --- a/src/plugins/terminal/www/Terminal.js +++ b/src/plugins/terminal/www/Terminal.js @@ -91,7 +91,7 @@ const Terminal = { if (!pidExists) return false; - const result = await Executor.execute(`kill -0 $(cat $PREFIX/pid) 2>/dev/null && echo "true" || echo "false"`); + const result = await Executor.BackgroundExecutor.execute(`kill -0 $(cat $PREFIX/pid) 2>/dev/null && echo "true" || echo "false"`); return String(result).toLowerCase() === "true"; },