From ebcdce52e5f3787a742655df133aaaa36def82d0 Mon Sep 17 00:00:00 2001 From: RohitKushvaha01 Date: Sun, 22 Mar 2026 17:31:43 +0530 Subject: [PATCH 01/15] feat: spawn stream support for Executor --- src/plugins/terminal/plugin.xml | 4 + .../terminal/src/android/Executor.java | 45 ++++++++++ .../terminal/src/android/ProcessServer.java | 90 +++++++++++++++++++ src/plugins/terminal/www/Executor.js | 18 ++++ 4 files changed, 157 insertions(+) create mode 100644 src/plugins/terminal/src/android/ProcessServer.java diff --git a/src/plugins/terminal/plugin.xml b/src/plugins/terminal/plugin.xml index 72273257f..54b030c40 100644 --- a/src/plugins/terminal/plugin.xml +++ b/src/plugins/terminal/plugin.xml @@ -12,6 +12,9 @@ + + + @@ -28,6 +31,7 @@ + diff --git a/src/plugins/terminal/src/android/Executor.java b/src/plugins/terminal/src/android/Executor.java index 5d0e90777..573152a6a 100644 --- a/src/plugins/terminal/src/android/Executor.java +++ b/src/plugins/terminal/src/android/Executor.java @@ -29,6 +29,9 @@ import android.app.Activity; import com.foxdebug.acode.rk.exec.terminal.*; +import java.net.ServerSocket; +import java.io.IOException; + public class Executor extends CordovaPlugin { private Messenger serviceMessenger; @@ -41,6 +44,7 @@ public class Executor extends CordovaPlugin { private final java.util.Map callbackContextMap = new java.util.concurrent.ConcurrentHashMap<>(); private static final int REQUEST_POST_NOTIFICATIONS = 1001; + private static int nextPort = 9000; private void askNotificationPermission(Activity context) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { @@ -284,12 +288,53 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo callbackContextMap.put(pidCheck, callbackContext); isProcessRunning(pidCheck); return true; + case "spawn": + JSONArray cmdArr = args.getJSONArray(0); + + String[] cmd = new String[cmdArr.length()]; + for(int i=0;i { + + try{ + + byte[] buf = new byte[8192]; + int len; + + while((len = stdout.read(buf)) != -1){ + conn.send(ByteBuffer.wrap(buf,0,len)); + } + + }catch(Exception ignored){} + + }).start(); + + }catch(Exception ignored){} + } + + @Override + public void onMessage(WebSocket conn, ByteBuffer msg) { + try { + OutputStream stdin = conn.getAttachment(); + stdin.write(msg.array(), msg.position(), msg.remaining()); + stdin.flush(); + } catch (Exception ignored) {} + } + + @Override + public void onMessage(WebSocket conn, String message) { + try { + OutputStream stdin = conn.getAttachment(); + stdin.write(message.getBytes()); + stdin.flush(); + } catch (Exception ignored) {} + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote){ + stopProcess(); + } + + @Override + public void onError(WebSocket conn, Exception ex){ + stopProcess(); + } + + @Override + public void onStart(){ + } + + private void stopProcess(){ + try{ + if(process != null) process.destroy(); + stop(); + }catch(Exception ignored){} + } +} \ No newline at end of file diff --git a/src/plugins/terminal/www/Executor.js b/src/plugins/terminal/www/Executor.js index 4cb6c970c..f533b3ecd 100644 --- a/src/plugins/terminal/www/Executor.js +++ b/src/plugins/terminal/www/Executor.js @@ -12,6 +12,22 @@ class Executor { constructor(BackgroundExecutor = false) { this.ExecutorType = BackgroundExecutor ? "BackgroundExecutor" : "Executor"; } + + spawnStream(cmd, callback){ + + exec((port)=>{ + const ws = new WebSocket(`ws://127.0.0.1:${port}`); + ws.binaryType = "arraybuffer"; + + ws.onopen = ()=>{ + callback(ws); + }; + + }, null, "Executor", "spawn", [cmd]); + + } + + /** * Starts a shell process and enables real-time streaming of stdout, stderr, and exit status. * @@ -150,6 +166,8 @@ class Executor { * * @returns {Promise} Resolves when the service has been stopped. * + * Note: This does not gurantee that all running processes have been killed, but the service will no longer be active. Use with caution. + * * @example * executor.stopService(); */ From 35cda6198bb0446a59b17bb88f1794572fb17966 Mon Sep 17 00:00:00 2001 From: RohitKushvaha01 Date: Mon, 23 Mar 2026 11:31:08 +0530 Subject: [PATCH 02/15] fix issues --- .../terminal/src/android/Executor.java | 67 ++++++++--------- .../terminal/src/android/ProcessServer.java | 71 +++++++++---------- 2 files changed, 60 insertions(+), 78 deletions(-) diff --git a/src/plugins/terminal/src/android/Executor.java b/src/plugins/terminal/src/android/Executor.java index 573152a6a..a6d48ae89 100644 --- a/src/plugins/terminal/src/android/Executor.java +++ b/src/plugins/terminal/src/android/Executor.java @@ -256,6 +256,30 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo return true; } + if(actions.equals("spawn")){ + try{ + JSONArray cmdArr = args.getJSONArray(0); + String[] cmd = new String[cmdArr.length()]; + for (int i = 0; i < cmdArr.length(); i++) { + cmd[i] = cmdArr.getString(i); + } + + ServerSocket socket = findFreePort(); + int port = socket.getLocalPort(); + + ProcessServer server = new ProcessServer(socket, cmd); + server.start(); + + callbackContext.success(port); + }catch(Exception e){ + e.printStackTrace(); + callbackContext.error("Failed to spawn process: " + e.getMessage()); + } + + return true; + } + + // For all other actions, ensure service is bound first if (!ensureServiceBound(callbackContext)) { // Error already sent by ensureServiceBound @@ -288,51 +312,16 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo callbackContextMap.put(pidCheck, callbackContext); isProcessRunning(pidCheck); return true; - case "spawn": - JSONArray cmdArr = args.getJSONArray(0); - - String[] cmd = new String[cmdArr.length()]; - for(int i=0;i { - - try{ - + try { byte[] buf = new byte[8192]; int len; - - while((len = stdout.read(buf)) != -1){ - conn.send(ByteBuffer.wrap(buf,0,len)); + while ((len = stdout.read(buf)) != -1) { + conn.send(ByteBuffer.wrap(buf, 0, len)); } - - }catch(Exception ignored){} - + } catch (Exception ignored) {} }).start(); - }catch(Exception ignored){} + } catch (Exception ignored) {} } @Override public void onMessage(WebSocket conn, ByteBuffer msg) { try { - OutputStream stdin = conn.getAttachment(); - stdin.write(msg.array(), msg.position(), msg.remaining()); - stdin.flush(); + ConnState state = conn.getAttachment(); + state.stdin().write(msg.array(), msg.position(), msg.remaining()); + state.stdin().flush(); } catch (Exception ignored) {} } @Override public void onMessage(WebSocket conn, String message) { try { - OutputStream stdin = conn.getAttachment(); - stdin.write(message.getBytes()); - stdin.flush(); + ConnState state = conn.getAttachment(); + state.stdin().write(message.getBytes()); + state.stdin().flush(); } catch (Exception ignored) {} } @Override - public void onClose(WebSocket conn, int code, String reason, boolean remote){ - stopProcess(); + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + stopProcess(conn); } @Override - public void onError(WebSocket conn, Exception ex){ - stopProcess(); + public void onError(WebSocket conn, Exception ex) { + if (conn != null) stopProcess(conn); } @Override - public void onStart(){ - } + public void onStart() {} - private void stopProcess(){ - try{ - if(process != null) process.destroy(); + private void stopProcess(WebSocket conn) { + try { + ConnState state = conn.getAttachment(); + if (state != null) state.process().destroy(); stop(); - }catch(Exception ignored){} + } catch (Exception ignored) {} } } \ No newline at end of file From eee71e83a90f4b5d51c00ee11bad739745a0900a Mon Sep 17 00:00:00 2001 From: RohitKushvaha01 Date: Mon, 23 Mar 2026 12:19:48 +0530 Subject: [PATCH 03/15] feat: improvements --- .../terminal/src/android/Executor.java | 24 ++++++++--------- .../terminal/src/android/ProcessServer.java | 27 ++++++++++++------- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/src/plugins/terminal/src/android/Executor.java b/src/plugins/terminal/src/android/Executor.java index a6d48ae89..e8eeb6fbd 100644 --- a/src/plugins/terminal/src/android/Executor.java +++ b/src/plugins/terminal/src/android/Executor.java @@ -31,6 +31,7 @@ import java.net.ServerSocket; import java.io.IOException; +import java.net.InetSocketAddress; public class Executor extends CordovaPlugin { @@ -256,26 +257,29 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo return true; } - if(actions.equals("spawn")){ - try{ + if (action.equals("spawn")) { + try { JSONArray cmdArr = args.getJSONArray(0); String[] cmd = new String[cmdArr.length()]; for (int i = 0; i < cmdArr.length(); i++) { cmd[i] = cmdArr.getString(i); } - ServerSocket socket = findFreePort(); - int port = socket.getLocalPort(); + int port; + try (ServerSocket socket = new ServerSocket(0)) { + socket.setReuseAddress(true); + port = socket.getLocalPort(); + } - ProcessServer server = new ProcessServer(socket, cmd); + ProcessServer server = new ProcessServer(port, cmd); server.start(); callbackContext.success(port); - }catch(Exception e){ + } catch (Exception e) { e.printStackTrace(); callbackContext.error("Failed to spawn process: " + e.getMessage()); } - + return true; } @@ -318,12 +322,6 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo } } - private ServerSocket findFreePort() throws IOException { - ServerSocket socket = new ServerSocket(0); - socket.setReuseAddress(true); - return socket; - } - private void stopServiceNow() { if (isServiceBound) { try { diff --git a/src/plugins/terminal/src/android/ProcessServer.java b/src/plugins/terminal/src/android/ProcessServer.java index 728b285f5..bec2c70ee 100644 --- a/src/plugins/terminal/src/android/ProcessServer.java +++ b/src/plugins/terminal/src/android/ProcessServer.java @@ -6,17 +6,26 @@ import java.io.InputStream; import java.io.OutputStream; -import java.net.InetSocketAddress; +import java.net.ServerSocket; import java.nio.ByteBuffer; +import java.net.InetSocketAddress; class ProcessServer extends WebSocketServer { private final String[] cmd; - private record ConnState(Process process, OutputStream stdin) {} + private static final class ConnState { + final Process process; + final OutputStream stdin; + + ConnState(Process process, OutputStream stdin) { + this.process = process; + this.stdin = stdin; + } + } - ProcessServer(ServerSocket socket, String[] cmd) { - super(socket); + ProcessServer(int port, String[] cmd) { + super(new InetSocketAddress(port)); this.cmd = cmd; } @@ -46,8 +55,8 @@ public void onOpen(WebSocket conn, ClientHandshake handshake) { public void onMessage(WebSocket conn, ByteBuffer msg) { try { ConnState state = conn.getAttachment(); - state.stdin().write(msg.array(), msg.position(), msg.remaining()); - state.stdin().flush(); + state.stdin.write(msg.array(), msg.position(), msg.remaining()); + state.stdin.flush(); } catch (Exception ignored) {} } @@ -55,8 +64,8 @@ public void onMessage(WebSocket conn, ByteBuffer msg) { public void onMessage(WebSocket conn, String message) { try { ConnState state = conn.getAttachment(); - state.stdin().write(message.getBytes()); - state.stdin().flush(); + state.stdin.write(message.getBytes()); + state.stdin.flush(); } catch (Exception ignored) {} } @@ -76,7 +85,7 @@ public void onStart() {} private void stopProcess(WebSocket conn) { try { ConnState state = conn.getAttachment(); - if (state != null) state.process().destroy(); + if (state != null) state.process.destroy(); stop(); } catch (Exception ignored) {} } From bbed8836cf75016c3cff549cf4ce2f64c3288c32 Mon Sep 17 00:00:00 2001 From: RohitKushvaha01 Date: Mon, 23 Mar 2026 12:29:19 +0530 Subject: [PATCH 04/15] feat: improvements --- .../terminal/src/android/Executor.java | 4 ++-- .../terminal/src/android/ProcessServer.java | 22 ++++++++++++++----- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/plugins/terminal/src/android/Executor.java b/src/plugins/terminal/src/android/Executor.java index e8eeb6fbd..5bb8c6c37 100644 --- a/src/plugins/terminal/src/android/Executor.java +++ b/src/plugins/terminal/src/android/Executor.java @@ -31,7 +31,7 @@ import java.net.ServerSocket; import java.io.IOException; -import java.net.InetSocketAddress; + public class Executor extends CordovaPlugin { @@ -272,7 +272,7 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo } ProcessServer server = new ProcessServer(port, cmd); - server.start(); + server.startAndAwait(); // blocks until onStart() fires — server is listening before port is returned callbackContext.success(port); } catch (Exception e) { diff --git a/src/plugins/terminal/src/android/ProcessServer.java b/src/plugins/terminal/src/android/ProcessServer.java index bec2c70ee..82cbab39e 100644 --- a/src/plugins/terminal/src/android/ProcessServer.java +++ b/src/plugins/terminal/src/android/ProcessServer.java @@ -6,13 +6,15 @@ import java.io.InputStream; import java.io.OutputStream; +import java.net.InetSocketAddress; import java.net.ServerSocket; import java.nio.ByteBuffer; -import java.net.InetSocketAddress; +import java.util.concurrent.CountDownLatch; class ProcessServer extends WebSocketServer { private final String[] cmd; + private final CountDownLatch readyLatch = new CountDownLatch(1); private static final class ConnState { final Process process; @@ -24,11 +26,22 @@ private static final class ConnState { } } - ProcessServer(int port, String[] cmd) { - super(new InetSocketAddress(port)); + ProcessServer(int port, String[] cmd) { + super(new InetSocketAddress("127.0.0.1", port)); // loopback only this.cmd = cmd; } + /** Blocks the calling thread until onStart() fires. */ + void startAndAwait() throws InterruptedException { + start(); + readyLatch.await(); // returns as soon as the server socket is open + } + + @Override + public void onStart() { + readyLatch.countDown(); // unblocks startAndAwait() + } + @Override public void onOpen(WebSocket conn, ClientHandshake handshake) { try { @@ -79,9 +92,6 @@ public void onError(WebSocket conn, Exception ex) { if (conn != null) stopProcess(conn); } - @Override - public void onStart() {} - private void stopProcess(WebSocket conn) { try { ConnState state = conn.getAttachment(); From cad63f8f669f2950f35a1620fe344013ab3c9efc Mon Sep 17 00:00:00 2001 From: Rohit Kushvaha Date: Mon, 23 Mar 2026 12:36:02 +0530 Subject: [PATCH 05/15] Update src/plugins/terminal/www/Executor.js Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- src/plugins/terminal/www/Executor.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/plugins/terminal/www/Executor.js b/src/plugins/terminal/www/Executor.js index f533b3ecd..a898c9d76 100644 --- a/src/plugins/terminal/www/Executor.js +++ b/src/plugins/terminal/www/Executor.js @@ -13,6 +13,13 @@ class Executor { this.ExecutorType = BackgroundExecutor ? "BackgroundExecutor" : "Executor"; } + /** + * Spawns a process and exposes it as a raw WebSocket stream. + * + * @param {string[]} cmd - Command and arguments to execute (e.g. `["sh", "-c", "echo hi"]`). + * @param {(ws: WebSocket) => void} callback - Called with the connected WebSocket once the + * process is ready. Use `ws.send()` to write to stdin and `ws.onmessage` to read stdout. + */ spawnStream(cmd, callback){ exec((port)=>{ From 2f1d5757031b479c09d3f71f2407e415524d325c Mon Sep 17 00:00:00 2001 From: Rohit Kushvaha Date: Mon, 23 Mar 2026 12:36:33 +0530 Subject: [PATCH 06/15] Update src/plugins/terminal/src/android/Executor.java Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- src/plugins/terminal/src/android/Executor.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/plugins/terminal/src/android/Executor.java b/src/plugins/terminal/src/android/Executor.java index 5bb8c6c37..a9bbb69cd 100644 --- a/src/plugins/terminal/src/android/Executor.java +++ b/src/plugins/terminal/src/android/Executor.java @@ -45,7 +45,8 @@ public class Executor extends CordovaPlugin { private final java.util.Map callbackContextMap = new java.util.concurrent.ConcurrentHashMap<>(); private static final int REQUEST_POST_NOTIFICATIONS = 1001; - private static int nextPort = 9000; + private static final int REQUEST_POST_NOTIFICATIONS = 1001; + private void askNotificationPermission(Activity context) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { From e145f47290e98531cb67fc91cf379c5209b03552 Mon Sep 17 00:00:00 2001 From: Rohit Kushvaha Date: Mon, 23 Mar 2026 12:37:21 +0530 Subject: [PATCH 07/15] Update src/plugins/terminal/src/android/ProcessServer.java Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- src/plugins/terminal/src/android/ProcessServer.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/plugins/terminal/src/android/ProcessServer.java b/src/plugins/terminal/src/android/ProcessServer.java index 82cbab39e..13089ba21 100644 --- a/src/plugins/terminal/src/android/ProcessServer.java +++ b/src/plugins/terminal/src/android/ProcessServer.java @@ -7,7 +7,10 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.InetSocketAddress; -import java.net.ServerSocket; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.util.concurrent.CountDownLatch; From 6d27ded3d78b020814865a862951bbcdfeac0f1a Mon Sep 17 00:00:00 2001 From: Rohit Kushvaha Date: Mon, 23 Mar 2026 13:24:28 +0530 Subject: [PATCH 08/15] Update src/plugins/terminal/src/android/ProcessServer.java Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- src/plugins/terminal/src/android/ProcessServer.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/plugins/terminal/src/android/ProcessServer.java b/src/plugins/terminal/src/android/ProcessServer.java index 13089ba21..0acacaafc 100644 --- a/src/plugins/terminal/src/android/ProcessServer.java +++ b/src/plugins/terminal/src/android/ProcessServer.java @@ -76,10 +76,7 @@ public void onMessage(WebSocket conn, ByteBuffer msg) { } catch (Exception ignored) {} } - @Override - public void onMessage(WebSocket conn, String message) { - try { - ConnState state = conn.getAttachment(); + state.stdin.write(message.getBytes(java.nio.charset.StandardCharsets.UTF_8)); state.stdin.write(message.getBytes()); state.stdin.flush(); } catch (Exception ignored) {} From 44f291ac68d4ab5c46499cb1bb03761f8c1e3984 Mon Sep 17 00:00:00 2001 From: RohitKushvaha01 Date: Mon, 23 Mar 2026 13:29:19 +0530 Subject: [PATCH 09/15] fix: issues --- .../terminal/src/android/ProcessServer.java | 44 +++++++++++++------ src/plugins/terminal/www/Executor.js | 12 ++--- 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/src/plugins/terminal/src/android/ProcessServer.java b/src/plugins/terminal/src/android/ProcessServer.java index 0acacaafc..9b800b787 100644 --- a/src/plugins/terminal/src/android/ProcessServer.java +++ b/src/plugins/terminal/src/android/ProcessServer.java @@ -7,18 +7,19 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.InetSocketAddress; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; class ProcessServer extends WebSocketServer { private final String[] cmd; private final CountDownLatch readyLatch = new CountDownLatch(1); + // Holds a bind-time exception if onError fires before onStart. + // AtomicReference because onError and startAndAwait run on different threads. + private final AtomicReference startError = new AtomicReference<>(); + private static final class ConnState { final Process process; final OutputStream stdin; @@ -30,19 +31,39 @@ private static final class ConnState { } ProcessServer(int port, String[] cmd) { - super(new InetSocketAddress("127.0.0.1", port)); // loopback only + super(new InetSocketAddress("127.0.0.1", port)); this.cmd = cmd; } - /** Blocks the calling thread until onStart() fires. */ - void startAndAwait() throws InterruptedException { + /** + * Starts the server and blocks until it is listening. + * Throws if the server fails to bind so the caller can report the error + * instead of hanging indefinitely. + */ + void startAndAwait() throws Exception { start(); - readyLatch.await(); // returns as soon as the server socket is open + readyLatch.await(); + + // Re-throw any bind-time error captured in onError + Exception err = startError.get(); + if (err != null) throw err; } @Override public void onStart() { - readyLatch.countDown(); // unblocks startAndAwait() + readyLatch.countDown(); // server is listening — unblock startAndAwait() + } + + @Override + public void onError(WebSocket conn, Exception ex) { + if (conn == null) { + // Startup/bind failure — record it and unblock startAndAwait() + // so it can throw rather than hang. + startError.set(ex); + readyLatch.countDown(); + } else { + stopProcess(conn); + } } @Override @@ -87,11 +108,6 @@ public void onClose(WebSocket conn, int code, String reason, boolean remote) { stopProcess(conn); } - @Override - public void onError(WebSocket conn, Exception ex) { - if (conn != null) stopProcess(conn); - } - private void stopProcess(WebSocket conn) { try { ConnState state = conn.getAttachment(); diff --git a/src/plugins/terminal/www/Executor.js b/src/plugins/terminal/www/Executor.js index a898c9d76..7724447a7 100644 --- a/src/plugins/terminal/www/Executor.js +++ b/src/plugins/terminal/www/Executor.js @@ -20,18 +20,20 @@ class Executor { * @param {(ws: WebSocket) => void} callback - Called with the connected WebSocket once the * process is ready. Use `ws.send()` to write to stdin and `ws.onmessage` to read stdout. */ - spawnStream(cmd, callback){ - - exec((port)=>{ + spawnStream(cmd, callback, onError) { + exec((port) => { const ws = new WebSocket(`ws://127.0.0.1:${port}`); ws.binaryType = "arraybuffer"; - ws.onopen = ()=>{ + ws.onopen = () => { callback(ws); }; - }, null, "Executor", "spawn", [cmd]); + ws.onerror = (e) => { + if (onError) onError(e); + }; + }, (err) => { if (onError) onError(err); }, "Executor", "spawn", [cmd]); } From 2b7ac850f2f84b671e249629b19cdb70cf0199b1 Mon Sep 17 00:00:00 2001 From: Rohit Kushvaha Date: Mon, 23 Mar 2026 13:39:58 +0530 Subject: [PATCH 10/15] Update src/plugins/terminal/src/android/Executor.java Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- src/plugins/terminal/src/android/Executor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/terminal/src/android/Executor.java b/src/plugins/terminal/src/android/Executor.java index a9bbb69cd..ce2a11386 100644 --- a/src/plugins/terminal/src/android/Executor.java +++ b/src/plugins/terminal/src/android/Executor.java @@ -45,7 +45,7 @@ public class Executor extends CordovaPlugin { private final java.util.Map callbackContextMap = new java.util.concurrent.ConcurrentHashMap<>(); private static final int REQUEST_POST_NOTIFICATIONS = 1001; - private static final int REQUEST_POST_NOTIFICATIONS = 1001; + private void askNotificationPermission(Activity context) { From abb77683d9262471e14b78c8dd94aed64782be98 Mon Sep 17 00:00:00 2001 From: Rohit Kushvaha Date: Mon, 23 Mar 2026 13:40:37 +0530 Subject: [PATCH 11/15] Update src/plugins/terminal/src/android/ProcessServer.java Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- src/plugins/terminal/src/android/ProcessServer.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/plugins/terminal/src/android/ProcessServer.java b/src/plugins/terminal/src/android/ProcessServer.java index 9b800b787..66692e812 100644 --- a/src/plugins/terminal/src/android/ProcessServer.java +++ b/src/plugins/terminal/src/android/ProcessServer.java @@ -97,11 +97,6 @@ public void onMessage(WebSocket conn, ByteBuffer msg) { } catch (Exception ignored) {} } - state.stdin.write(message.getBytes(java.nio.charset.StandardCharsets.UTF_8)); - state.stdin.write(message.getBytes()); - state.stdin.flush(); - } catch (Exception ignored) {} - } @Override public void onClose(WebSocket conn, int code, String reason, boolean remote) { From 4b204a8637e0fc993a16f848ff8c88e724864d36 Mon Sep 17 00:00:00 2001 From: RohitKushvaha01 Date: Mon, 23 Mar 2026 13:44:30 +0530 Subject: [PATCH 12/15] fix: issues --- .../terminal/src/android/ProcessServer.java | 39 +++++++++---------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/src/plugins/terminal/src/android/ProcessServer.java b/src/plugins/terminal/src/android/ProcessServer.java index 66692e812..8fe676051 100644 --- a/src/plugins/terminal/src/android/ProcessServer.java +++ b/src/plugins/terminal/src/android/ProcessServer.java @@ -8,6 +8,7 @@ import java.io.OutputStream; import java.net.InetSocketAddress; import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicReference; @@ -15,9 +16,6 @@ class ProcessServer extends WebSocketServer { private final String[] cmd; private final CountDownLatch readyLatch = new CountDownLatch(1); - - // Holds a bind-time exception if onError fires before onStart. - // AtomicReference because onError and startAndAwait run on different threads. private final AtomicReference startError = new AtomicReference<>(); private static final class ConnState { @@ -35,35 +33,27 @@ private static final class ConnState { this.cmd = cmd; } - /** - * Starts the server and blocks until it is listening. - * Throws if the server fails to bind so the caller can report the error - * instead of hanging indefinitely. - */ void startAndAwait() throws Exception { start(); readyLatch.await(); - - // Re-throw any bind-time error captured in onError Exception err = startError.get(); if (err != null) throw err; } @Override public void onStart() { - readyLatch.countDown(); // server is listening — unblock startAndAwait() + readyLatch.countDown(); } @Override public void onError(WebSocket conn, Exception ex) { if (conn == null) { - // Startup/bind failure — record it and unblock startAndAwait() - // so it can throw rather than hang. + // Bind/startup failure — unblock startAndAwait() so it can throw. startError.set(ex); readyLatch.countDown(); - } else { - stopProcess(conn); } + // Per-connection errors: do nothing. onClose fires immediately after + // for the same connection, which is the single place cleanup happens. } @Override @@ -83,9 +73,12 @@ public void onOpen(WebSocket conn, ClientHandshake handshake) { conn.send(ByteBuffer.wrap(buf, 0, len)); } } catch (Exception ignored) {} + conn.close(1000, "process exited"); }).start(); - } catch (Exception ignored) {} + } catch (Exception e) { + conn.close(1011, "Failed to start process: " + e.getMessage()); + } } @Override @@ -97,13 +90,19 @@ public void onMessage(WebSocket conn, ByteBuffer msg) { } catch (Exception ignored) {} } - @Override - public void onClose(WebSocket conn, int code, String reason, boolean remote) { - stopProcess(conn); + public void onMessage(WebSocket conn, String message) { + try { + ConnState state = conn.getAttachment(); + state.stdin.write(message.getBytes(StandardCharsets.UTF_8)); + state.stdin.flush(); + } catch (Exception ignored) {} } - private void stopProcess(WebSocket conn) { + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + // Single point of cleanup for every connection lifecycle ending — + // whether closed cleanly, after an error, or by the process exiting. try { ConnState state = conn.getAttachment(); if (state != null) state.process.destroy(); From b9c8a5f3bbc67f83d043cce8237ca064e3b772ba Mon Sep 17 00:00:00 2001 From: RohitKushvaha01 Date: Mon, 23 Mar 2026 13:54:11 +0530 Subject: [PATCH 13/15] fix: issues --- src/plugins/terminal/src/android/Executor.java | 1 - src/plugins/terminal/src/android/ProcessServer.java | 12 +++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/plugins/terminal/src/android/Executor.java b/src/plugins/terminal/src/android/Executor.java index ce2a11386..8940890c2 100644 --- a/src/plugins/terminal/src/android/Executor.java +++ b/src/plugins/terminal/src/android/Executor.java @@ -268,7 +268,6 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo int port; try (ServerSocket socket = new ServerSocket(0)) { - socket.setReuseAddress(true); port = socket.getLocalPort(); } diff --git a/src/plugins/terminal/src/android/ProcessServer.java b/src/plugins/terminal/src/android/ProcessServer.java index 8fe676051..c6c264164 100644 --- a/src/plugins/terminal/src/android/ProcessServer.java +++ b/src/plugins/terminal/src/android/ProcessServer.java @@ -101,12 +101,18 @@ public void onMessage(WebSocket conn, String message) { @Override public void onClose(WebSocket conn, int code, String reason, boolean remote) { - // Single point of cleanup for every connection lifecycle ending — - // whether closed cleanly, after an error, or by the process exiting. try { ConnState state = conn.getAttachment(); if (state != null) state.process.destroy(); - stop(); } catch (Exception ignored) {} + + // stop() calls w.join() on every worker thread. If called directly from + // onClose (which runs on a WebSocketWorker thread), it deadlocks waiting + // for itself to finish. A separate thread sidesteps that entirely. + new Thread(() -> { + try { + stop(); + } catch (Exception ignored) {} + }).start(); } } \ No newline at end of file From 86827e88798d403e582e4f6ffaaaa7e54b766d7c Mon Sep 17 00:00:00 2001 From: Rohit Kushvaha Date: Mon, 23 Mar 2026 14:02:25 +0530 Subject: [PATCH 14/15] Update src/plugins/terminal/src/android/Executor.java Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- src/plugins/terminal/src/android/Executor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/terminal/src/android/Executor.java b/src/plugins/terminal/src/android/Executor.java index 8940890c2..431f33595 100644 --- a/src/plugins/terminal/src/android/Executor.java +++ b/src/plugins/terminal/src/android/Executor.java @@ -30,7 +30,7 @@ import com.foxdebug.acode.rk.exec.terminal.*; import java.net.ServerSocket; -import java.io.IOException; +import java.net.ServerSocket; public class Executor extends CordovaPlugin { From a6d835aab3b1a1de485cce8d50577d8af10eb001 Mon Sep 17 00:00:00 2001 From: RohitKushvaha01 Date: Mon, 23 Mar 2026 14:19:20 +0530 Subject: [PATCH 15/15] fix: remove duplicate import --- src/plugins/terminal/src/android/Executor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/terminal/src/android/Executor.java b/src/plugins/terminal/src/android/Executor.java index 431f33595..6fc2c4bd8 100644 --- a/src/plugins/terminal/src/android/Executor.java +++ b/src/plugins/terminal/src/android/Executor.java @@ -30,7 +30,7 @@ import com.foxdebug.acode.rk.exec.terminal.*; import java.net.ServerSocket; -import java.net.ServerSocket; + public class Executor extends CordovaPlugin {