Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 5 additions & 16 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -129,4 +129,4 @@
"yargs": "^18.0.0"
},
"browserslist": "cover 100%,not android < 5"
}
}
9 changes: 9 additions & 0 deletions src/plugins/terminal/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,19 @@
<feature name="Executor">
<param name="android-package" value="com.foxdebug.acode.rk.exec.terminal.Executor" />
</feature>
<feature name="BackgroundExecutor">
<param name="android-package" value="com.foxdebug.acode.rk.exec.terminal.BackgroundExecutor" />
</feature>
</config-file>
<config-file parent="/*" target="AndroidManifest.xml" />

<source-file src="src/android/BackgroundExecutor.java" target-dir="src/com/foxdebug/acode/rk/exec/terminal" />
<source-file src="src/android/ProcessManager.java" target-dir="src/com/foxdebug/acode/rk/exec/terminal" />
<source-file src="src/android/ProcessUtils.java" target-dir="src/com/foxdebug/acode/rk/exec/terminal" />
<source-file src="src/android/StreamHandler.java" target-dir="src/com/foxdebug/acode/rk/exec/terminal" />

<source-file src="src/android/Executor.java" target-dir="src/com/foxdebug/acode/rk/exec/terminal" />

<source-file src="src/android/TerminalService.java" target-dir="src/com/foxdebug/acode/rk/exec/terminal" />

<source-file src="src/android/AlpineDocumentProvider.java" target-dir="src/com/foxdebug/acode/rk/exec/terminal" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down
164 changes: 164 additions & 0 deletions src/plugins/terminal/src/android/BackgroundExecutor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package com.foxdebug.acode.rk.exec.terminal;

import org.apache.cordova.*;
import org.json.*;
import java.io.*;
import java.util.*;
import java.util.concurrent.*;
import com.foxdebug.acode.rk.exec.terminal.*;

public class BackgroundExecutor extends CordovaPlugin {

private final Map<String, Process> processes = new ConcurrentHashMap<>();
private final Map<String, OutputStream> processInputs = new ConcurrentHashMap<>();
private final Map<String, CallbackContext> 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);
}
}
17 changes: 17 additions & 0 deletions src/plugins/terminal/src/android/Executor.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -235,6 +236,22 @@ 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;
}

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
Expand Down
Loading