mirror of
https://github.com/HeyPuter/puter
synced 2024-11-14 22:06:00 +00:00
Phoenix: Wait for apps to finish executing, and connect stdio to them
After launching an app, if successful, we connect stdio streams to it, and wait for it to exit before we return to the prompt. stdio is implemented as regular AppConnection messages: - stdin: `{ $: 'stdin', data: Uint8Array }` from phoenix -> child - stdout: `{ $: 'stdout', data: Uint8Array }` from child -> phoenix Terminal and Phoenix now communicate with each other using the same style, instead of 'input' and 'output' messages. This will help with eventually running subshells. SIGINT currently is not sent. We also suffer from the same "one more read from stdin happens after app exits" bug that's in PathCommandProvider where I copied the stdin code from.
This commit is contained in:
parent
2890f19bfd
commit
3526d5d9eb
@ -38,7 +38,7 @@ export class XDocumentPTT {
|
|||||||
chunk = encoder.encode(chunk);
|
chunk = encoder.encode(chunk);
|
||||||
}
|
}
|
||||||
terminalConnection.postMessage({
|
terminalConnection.postMessage({
|
||||||
$: 'output',
|
$: 'stdout',
|
||||||
data: chunk,
|
data: chunk,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -52,7 +52,7 @@ export class XDocumentPTT {
|
|||||||
this.emit('ioctl.set', message);
|
this.emit('ioctl.set', message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (message.$ === 'input') {
|
if (message.$ === 'stdin') {
|
||||||
this.readController.enqueue(message.data);
|
this.readController.enqueue(message.data);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,9 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
import { Exit } from '../coreutils/coreutil_lib/exit.js';
|
||||||
|
import { signals } from '../../ansi-shell/signals.js';
|
||||||
|
|
||||||
const BUILT_IN_APPS = [
|
const BUILT_IN_APPS = [
|
||||||
'explorer',
|
'explorer',
|
||||||
];
|
];
|
||||||
@ -31,8 +34,7 @@ export class PuterAppCommandProvider {
|
|||||||
// TODO: Parameters and options?
|
// TODO: Parameters and options?
|
||||||
async execute(ctx) {
|
async execute(ctx) {
|
||||||
const args = {}; // TODO: Passed-in parameters and options would go here
|
const args = {}; // TODO: Passed-in parameters and options would go here
|
||||||
// NOTE: No await here, because launchApp() currently only resolves for Puter SDK apps.
|
await puter.ui.launchApp(id, args);
|
||||||
puter.ui.launchApp(id, args);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -57,8 +59,55 @@ export class PuterAppCommandProvider {
|
|||||||
// TODO: Parameters and options?
|
// TODO: Parameters and options?
|
||||||
async execute(ctx) {
|
async execute(ctx) {
|
||||||
const args = {}; // TODO: Passed-in parameters and options would go here
|
const args = {}; // TODO: Passed-in parameters and options would go here
|
||||||
// NOTE: No await here, yet, because launchApp() currently only resolves for Puter SDK apps.
|
const child = await puter.ui.launchApp(name, args);
|
||||||
puter.ui.launchApp(name, args);
|
|
||||||
|
// Wait for app to close.
|
||||||
|
const app_close_promise = new Promise((resolve, reject) => {
|
||||||
|
child.on('close', () => {
|
||||||
|
// TODO: Exit codes for apps
|
||||||
|
resolve({ done: true });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wait for SIGINT
|
||||||
|
const sigint_promise = new Promise((resolve, reject) => {
|
||||||
|
ctx.externs.sig.on((signal) => {
|
||||||
|
if (signal === signals.SIGINT) {
|
||||||
|
child.close();
|
||||||
|
reject(new Exit(130));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// We don't connect stdio to non-SDK apps, because they won't make use of it.
|
||||||
|
if (child.usesSDK) {
|
||||||
|
const decoder = new TextDecoder();
|
||||||
|
child.on('message', message => {
|
||||||
|
if (message.$ === 'stdout') {
|
||||||
|
ctx.externs.out.write(decoder.decode(message.data));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Repeatedly copy data from stdin to the child, while it's running.
|
||||||
|
// DRY: Initially copied from PathCommandProvider
|
||||||
|
let data, done;
|
||||||
|
const next_data = async () => {
|
||||||
|
// FIXME: This waits for one more read() after we finish.
|
||||||
|
({ value: data, done } = await Promise.race([
|
||||||
|
app_close_promise, sigint_promise, ctx.externs.in_.read(),
|
||||||
|
]));
|
||||||
|
if (data) {
|
||||||
|
child.postMessage({
|
||||||
|
$: 'stdin',
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
if (!done) setTimeout(next_data, 0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
setTimeout(next_data, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.race([ app_close_promise, sigint_promise ]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ export class XDocumentANSIShell {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.$ === 'output') {
|
if (message.$ === 'stdout') {
|
||||||
ptt.out.write(message.data);
|
ptt.out.write(message.data);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -69,7 +69,7 @@ export class XDocumentANSIShell {
|
|||||||
for ( ;; ) {
|
for ( ;; ) {
|
||||||
const chunk = (await ptt.in.read()).value;
|
const chunk = (await ptt.in.read()).value;
|
||||||
shell.postMessage({
|
shell.postMessage({
|
||||||
$: 'input',
|
$: 'stdin',
|
||||||
data: chunk,
|
data: chunk,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user