diff --git a/src/emulator/src/main.js b/src/emulator/src/main.js index 633d0a0d..1b7d7f99 100644 --- a/src/emulator/src/main.js +++ b/src/emulator/src/main.js @@ -23,6 +23,10 @@ class WispClient { } } +puter.ui.on('connection', event => { + console.log('emulator got connection event', event); +}); + window.onload = async function() { const resp = await fetch( diff --git a/src/gui/src/definitions.js b/src/gui/src/definitions.js index 7f928bca..5ef2f925 100644 --- a/src/gui/src/definitions.js +++ b/src/gui/src/definitions.js @@ -85,6 +85,10 @@ export class Process extends AdvancedBase{ this._signal(sig); } + handle_connection (other_process) { + throw new Error('Not implemented'); + } + get type () { const _to_type_name = (name) => { return name.replace(/Process$/, '').toLowerCase(); @@ -139,6 +143,15 @@ export class PortalProcess extends Process { const target = this.references.iframe.contentWindow; // NEXT: ... } + + handle_connection (other_process, args) { + const target = this.references.iframe.contentWindow; + target.postMessage({ + msg: 'connection', + appInstanceID: other_process.uuid, + args, + }); + } }; export class PseudoProcess extends Process { _construct () { this.type_ = 'ui' } diff --git a/src/gui/src/services/ExecService.js b/src/gui/src/services/ExecService.js index 15ef144c..213705b9 100644 --- a/src/gui/src/services/ExecService.js +++ b/src/gui/src/services/ExecService.js @@ -11,6 +11,9 @@ export class ExecService extends Service { svc_ipc.register_ipc_handler('launchApp', { handler: this.launchApp.bind(this), }); + svc_ipc.register_ipc_handler('connectToInstance', { + handler: this.connectToInstance.bind(this), + }); } // This method is exposed to apps via IPCService. @@ -70,4 +73,34 @@ export class ExecService extends Service { usesSDK: true, }; } + + async connectToInstance ({ app_name, args }, { ipc_context, msg_id } = {}) { + const caller_process = ipc_context?.caller?.process; + if ( ! caller_process ) { + throw new Error('Caller process not found'); + } + + console.log( + caller_process.name, + app_name, + ); + // TODO: permissions integration; for now it's hardcoded + if ( caller_process.name !== 'phoenix' ) { + throw new Error('Connection not allowed.'); + } + if ( app_name !== 'test-emu' ) { + throw new Error('Connection not allowed.'); + } + + const svc_process = this.services.get('process'); + const options = svc_process.select_by_name(app_name); + const process = options[0]; + + await process.handle_connection(caller_process, args); + + return { + appInstanceID: process.uuid, + response, + }; + } } diff --git a/src/gui/src/services/ProcessService.js b/src/gui/src/services/ProcessService.js index b210a6b6..d41404ee 100644 --- a/src/gui/src/services/ProcessService.js +++ b/src/gui/src/services/ProcessService.js @@ -66,6 +66,16 @@ export class ProcessService extends Service { return this.uuid_to_treelist.get(uuid); } + select_by_name (name) { + const list = []; + for ( const process of this.processes ) { + if ( process.name === name ) { + list.push(process); + } + } + return list; + } + register (process) { this.register_(process); this.attach_to_parent_(process); diff --git a/src/phoenix/src/puter-shell/main.js b/src/phoenix/src/puter-shell/main.js index 0d4b94cd..e79252da 100644 --- a/src/phoenix/src/puter-shell/main.js +++ b/src/phoenix/src/puter-shell/main.js @@ -35,6 +35,7 @@ import { MultiWriter } from '../ansi-shell/ioutil/MultiWriter.js'; import { CompositeCommandProvider } from './providers/CompositeCommandProvider.js'; import { ScriptCommandProvider } from './providers/ScriptCommandProvider.js'; import { PuterAppCommandProvider } from './providers/PuterAppCommandProvider.js'; +import { EmuCommandProvider } from './providers/EmuCommandProvider.js'; const argparser_registry = { [SimpleArgParser.name]: SimpleArgParser @@ -92,6 +93,7 @@ export const launchPuterShell = async (ctx) => { // PuterAppCommandProvider is only usable on Puter ...(ctx.platform.name === 'puter' ? [new PuterAppCommandProvider()] : []), new ScriptCommandProvider(), + new EmuCommandProvider(), ]); ctx = ctx.sub({ diff --git a/src/phoenix/src/puter-shell/providers/EmuCommandProvider.js b/src/phoenix/src/puter-shell/providers/EmuCommandProvider.js new file mode 100644 index 00000000..1af4d9d6 --- /dev/null +++ b/src/phoenix/src/puter-shell/providers/EmuCommandProvider.js @@ -0,0 +1,46 @@ +import { Exit } from "../coreutils/coreutil_lib/exit"; + +export class EmuCommandProvider { + static AVAILABLE = [ + 'bash', + 'htop', + ]; + + static EMU_APP_NAME = 'test-emu'; + + constructor () { + this.available = this.constructor.AVAILABLE; + this.emulator = null; + } + + async aquire_emulator () { + if ( this.emulator ) return this.emulator; + + // FUTURE: when we have a way to query instances + // without exposing the real instance id + /* + const instances = await puter.ui.queryInstances(); + if ( instances.length < 0 ) { + return; + } + const instance = instances[0]; + */ + + const conn = await puter.ui.connectToInstance(this.constructor.EMU_APP_NAME); + return this.emulator = conn; + } + + async lookup (id, { ctx }) { + if ( ! this.available.includes(id) ) { + return; + } + + const emu = await this.aquire_emulator(); + if ( ! emu ) { + ctx.externs.out.write('No emulator available.\n'); + return new Exit(1); + } + + ctx.externs.out.write(`Launching ${id} in emulator ${emu.appInstanceID}\n`); + } +} diff --git a/src/puter-js/src/modules/UI.js b/src/puter-js/src/modules/UI.js index 2cf327a5..f8d9a794 100644 --- a/src/puter-js/src/modules/UI.js +++ b/src/puter-js/src/modules/UI.js @@ -203,6 +203,7 @@ class UI extends EventListener { const eventNames = [ 'localeChanged', 'themeChanged', + 'connection', ]; super(eventNames); this.#eventNames = eventNames; @@ -460,6 +461,15 @@ class UI extends EventListener { this.emit(name, data); this.#lastBroadcastValue.set(name, data); } + else if ( e.data.msg === 'connection' ) { + const conn = AppConnection.from(e.data, { + appInstanceID: this.appInstanceID, + messageTarget: window.parent, + }); + this.emit('connection', { + conn + }); + } }); // We need to send the mouse position to the host environment @@ -951,6 +961,20 @@ class UI extends EventListener { }); } + connectToInstance = async function connectToInstance (app_name) { + const app_info = await this.#ipc_stub({ + method: 'connectToInstance', + parameters: { + app_name, + } + }); + + return AppConnection.from(app_info, { + appInstanceID: this.appInstanceID, + messageTarget: this.messageTarget, + }); + } + parentApp() { return this.#parentAppConnection; }