refactor(gui): remove the Services system, flatten into plain singleton modules

The GUI had a mini service framework in src/gui/src/services/: a `Service`
base class (definitions.js) with `construct`/`init`/`__on`/`context` magic, a
`globalThis.services` string-keyed registry with a two-phase construct→init
lifecycle and an `emit`/`__on_*` event bus, plus a frontend↔backend
`service_script` bridge for registering extra services from the backend.

Nothing used the extension bridge, and the framework added indirection over
what are really just nine long-lived singletons. This flattens all of it:

- Move the nine services to src/gui/src/modules/ as plain classes exporting a
  singleton (process, exec, ipc, broadcast, theme, debug, locale, anti-csrf).
  `__launch-on-init` becomes a plain `launch_on_init(gui_params)` function
  since nothing else references it. Method bodies are unchanged.
- Cross-service access is now direct ES-module imports instead of
  `globalThis.services.get(name)`. No base class, no registry, no lifecycle
  phases, no event bus. The single `gui:ready` event becomes a direct
  `process_service.on_gui_ready()` call from UIDesktop.
- launch_services() now just calls init() on the four modules that need
  runtime setup (exec/debug/theme/process), then runs launch_on_init. All
  singletons are constructed at import, so construct-before-init ordering is
  preserved.
- Remove the `Service` base class from definitions.js.
- Remove the unused `service_script` bridge from PuterHomepageService (the
  `window.service_script`/`service_script_api_promise` shim, `registerScript`,
  `#serviceScripts`, and the `serviceScriptTags` injection point).

Verified: webpack GUI build and backend tsc both clean; homepage controller
tests pass; booted the app and exercised the desktop, launching an SDK app
(process registration) and the Task Manager (process tree) with no console
errors.
This commit is contained in:
jelveh
2026-07-17 12:29:25 -07:00
parent aab21ef91d
commit bc3f7783ae
20 changed files with 121 additions and 253 deletions
@@ -68,8 +68,6 @@ interface PuterGuiAddonsEvent {
* Serves the root HTML shell that bootstraps the Puter GUI.
*
* Extensions contribute by:
* - `registerScript(url)` — adds a `<script type="module" src=...>` tag
* after the GUI init script.
* - `setGuiParam(key, value)` — injects a value into the client-side
* `gui()` call.
* - Listening for `puter.gui.addons` — mutate the event object's
@@ -78,7 +76,6 @@ interface PuterGuiAddonsEvent {
*/
export class PuterHomepageService extends PuterService {
#manifest: Manifest | null = null;
#serviceScripts: string[] = [];
#guiParams: Record<string, unknown> = {};
override async onServerStart(): Promise<void> {
@@ -102,10 +99,6 @@ export class PuterHomepageService extends PuterService {
}
}
registerScript(url: string): void {
this.#serviceScripts.push(url);
}
setGuiParam(key: string, val: unknown): void {
this.#guiParams[key] = val;
}
@@ -253,10 +246,6 @@ export class PuterHomepageService extends PuterService {
.join('\n')
: '';
const serviceScriptTags = this.#serviceScripts
.map((url) => `<script type="module" src="${url}"></script>`)
.join('\n');
const guiParamsJson = JSON.stringify(guiParams).replace(
/</g,
'\\u003c',
@@ -310,22 +299,6 @@ export class PuterHomepageService extends PuterService {
<link rel="preload" as="image" href="https://puter-assets.b-cdn.net/wallpaper.webp">
<script>
if ( ! window.service_script ) {
window.service_script_api_promise = (() => {
let resolve, reject;
const promise = new Promise((res, rej) => { resolve = res; reject = rej; });
promise.resolve = resolve;
promise.reject = reject;
return promise;
})();
window.service_script = async fn => {
try { await fn(await window.service_script_api_promise); }
catch (e) { console.error('service_script(ERROR)', e); }
};
}
</script>
${manifestCss}
${event.headContent}
@@ -341,7 +314,6 @@ export class PuterHomepageService extends PuterService {
gui(${guiParamsJson});
});
</script>
${serviceScriptTags}
<div id="templates" style="display: none;"></div>
${event.bodyContent}
+5 -11
View File
@@ -37,6 +37,8 @@ import UIWindowSignup from './UI/UIWindowSignup.js';
import UINotification from './UI/UINotification.js';
import { PROCESS_IPC_ATTACHED } from './definitions.js';
import { process_service } from './modules/process.js';
import { ipc_service } from './modules/ipc.js';
import TeePromise from './util/TeePromise.js';
window.ipc_handlers = {};
@@ -113,9 +115,6 @@ const ipc_listener = async (event, handled) => {
// New IPC handlers should be registered here.
// Do this by calling `register_ipc_handler` of IPCService.
if ( window.ipc_handlers.hasOwnProperty(event.data.msg) ) {
const services = globalThis.services;
const svc_process = services.get('process');
// Add version info to old puter.js messages
// (and coerce them into the format of new ones)
if ( event.data.$ === undefined ) {
@@ -130,7 +129,7 @@ const ipc_listener = async (event, handled) => {
// The IPC context contains information about the call
const iframe = window.iframe_for_app_instance(event.data.appInstanceID);
const process = svc_process.get_by_uuid(event.data.appInstanceID);
const process = process_service.get_by_uuid(event.data.appInstanceID);
const ipc_context = {
caller: {
process: process,
@@ -162,9 +161,7 @@ const ipc_listener = async (event, handled) => {
// READY
//-------------------------------------------------
if ( event.data.msg === 'READY' ) {
const services = globalThis.services;
const svc_process = services.get('process');
const process = svc_process.get_by_uuid(event.data.appInstanceID);
const process = process_service.get_by_uuid(event.data.appInstanceID);
process.ipc_status = PROCESS_IPC_ATTACHED;
}
@@ -1888,10 +1885,7 @@ const ipc_listener = async (event, handled) => {
const { appInstanceID, targetAppInstanceID, targetAppOrigin, contents } = event.data;
// TODO: Determine if we should allow the message
// TODO: Track message traffic between apps
const svc_ipc = globalThis.services.get('ipc');
// const svc_exec = globalThis.services()
const conn = svc_ipc.get_connection(targetAppInstanceID);
const conn = ipc_service.get_connection(targetAppInstanceID);
if ( conn ) {
conn.send(contents);
return;
+2 -1
View File
@@ -18,6 +18,7 @@
*/
import path from '../lib/path.js';
import { process_service } from '../modules/process.js';
import UIContextMenu from './UIContextMenu.js';
import UIItem from './UIItem.js';
@@ -1262,7 +1263,7 @@ async function UIDesktop (options) {
// GUI is ready to launch apps!
//-----------------------------
window.dispatchEvent(new CustomEvent('desktop:ready'));
globalThis.services.emit('gui:ready');
process_service.on_gui_ready();
//--------------------------------------------------------
// Open the AI app (best-effort — the `ai` app isn't seeded
+5 -5
View File
@@ -17,6 +17,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { anti_csrf_service } from '../modules/anti-csrf.js';
// Renders the Session Manager as a self-contained, responsive modal (a plain
// DOM overlay) rather than a draggable UIWindow. Confirmations are shown as
// in-modal sheets instead of UIAlert windows, so nothing here depends on the
@@ -94,8 +96,6 @@ const kindBadgeLabel = (kind) => {
const UIWindowManageSessions = async function UIWindowManageSessions (options) {
options = options ?? {};
const services = globalThis.services;
// =====================================================================
// Responsive modal shell
// =====================================================================
@@ -450,7 +450,7 @@ const UIWindowManageSessions = async function UIWindowManageSessions (options) {
session.label = next || null;
el_title.textContent = sessionTitle(session);
try {
const anti_csrf = await services.get('anti-csrf').token();
const anti_csrf = await anti_csrf_service.token();
const resp = await fetch(
`${window.api_origin}/auth/sessions/${encodeURIComponent(session.uuid)}/label`,
{
@@ -558,7 +558,7 @@ const UIWindowManageSessions = async function UIWindowManageSessions (options) {
});
if ( ! ok ) return;
const anti_csrf = await services.get('anti-csrf').token();
const anti_csrf = await anti_csrf_service.token();
// Route access-token rows to the dedicated endpoint
// so `access_token_permissions` is cleared in addition
@@ -732,7 +732,7 @@ const UIWindowManageSessions = async function UIWindowManageSessions (options) {
});
if ( ! ok ) return;
const anti_csrf = await services.get('anti-csrf').token();
const anti_csrf = await anti_csrf_service.token();
const resp = await fetch(
`${window.api_origin}/auth/revoke-all-sessions`,
{
+2 -2
View File
@@ -19,6 +19,7 @@
import UIWindow from './UIWindow.js';
import UIAlert from './UIAlert.js';
import { locale_service } from '../modules/locale.js';
import UIWindowLogin from './UIWindowLogin.js';
import check_password_strength from '../helpers/check_password_strength.js';
@@ -122,8 +123,7 @@ async function UIWindowNewPassword (options) {
return;
}
const svc_locale = globalThis.services.get('locale');
const countdown = svc_locale.format_duration(time_remaining);
const countdown = locale_service.format_duration(time_remaining);
$(el_window).find('.change-password-btn').html(`Set New Password (${countdown})`);
}, 1000);
+4 -3
View File
@@ -17,12 +17,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { END_HARD, END_SOFT } from '../definitions.js';
import { process_service } from '../modules/process.js';
import UIAlert from './UIAlert.js';
import UIContextMenu from './UIContextMenu.js';
import UIWindow from './UIWindow.js';
const end_process = async (uuid, force) => {
const svc_process = globalThis.services.get('process');
const svc_process = process_service;
const process = svc_process.get_by_uuid(uuid);
if ( ! process ) {
console.warn(`Can't end process with uuid='${uuid}': does not exist`);
@@ -86,7 +87,7 @@ const calculate_indent_string = (indent_level, is_last_item_stack, is_last_item)
};
const generate_task_rows = (items, { indent_level, is_last_item_stack }) => {
const svc_process = globalThis.services.get('process');
const svc_process = process_service;
let rows_html = '';
for ( let i = 0; i < items.length; i++ ) {
@@ -141,7 +142,7 @@ const generate_task_rows = (items, { indent_level, is_last_item_stack }) => {
};
const UIWindowTaskManager = async function UIWindowTaskManager () {
const svc_process = globalThis.services.get('process');
const svc_process = process_service;
let h = '';
+2 -2
View File
@@ -18,11 +18,11 @@
*/
import { UIColorPickerWidget, hslaToHex8 } from './UIColorPickerWidget.js';
import UIWindow from './UIWindow.js';
import { theme_service } from '../modules/theme.js';
const UIWindowThemeDialog = async function UIWindowThemeDialog (options) {
options = options ?? {};
const services = globalThis.services;
const svc_theme = services.get('theme');
const svc_theme = theme_service;
// Get current theme values and convert to hex8 for the color picker
const currentHue = svc_theme.get('hue');
+2 -36
View File
@@ -19,40 +19,7 @@
import TeePromise from './util/TeePromise.js';
import AdvancedBase from './util/AdvancedBase.js';
const NOOP = async () => {
};
export class Service extends AdvancedBase {
// TODO: Service todo items
static TODO = [
'consolidate with BaseService from backend',
];
async __on (id, args) {
const handler = this.__get_event_handler(id);
return await handler(id, ...args);
}
__get_event_handler (id) {
return this[`__on_${id}`]?.bind?.(this)
|| this.constructor[`__on_${id}`]?.bind?.(this.constructor)
|| NOOP;
}
construct (o) {
this.$puter = {};
for ( const k in o ) this.$puter[k] = o[k];
if ( ! this._construct ) return;
return this._construct();
}
init (...a) {
if ( ! this._init ) return;
this.services = a[0].services;
return this._init(...a);
}
get context () {
return { services: this.services };
}
};
import { process_service } from './modules/process.js';
export const PROCESS_INITIALIZING = { i18n_key: 'initializing' };
export const PROCESS_RUNNING = { i18n_key: 'running' };
@@ -137,8 +104,7 @@ export class InitProcess extends Process {
}
_signal (sig) {
const svc_process = globalThis.services.get('process');
for ( const process of svc_process.processes ) {
for ( const process of process_service.processes ) {
if ( process === this ) continue;
process.signal(sig);
}
+3 -3
View File
@@ -25,6 +25,7 @@ import update_title_based_on_uploads from './helpers/update_title_based_on_uploa
import update_username_in_gui from './helpers/update_username_in_gui.js';
import mime from './lib/mime.js';
import path from './lib/path.js';
import { broadcast_service } from './modules/broadcast.js';
import UIAlert from './UI/UIAlert.js';
import UIItem from './UI/UIItem.js';
import UIWindowLogin from './UI/UIWindowLogin.js';
@@ -719,8 +720,7 @@ window.update_user_preferences = function (user_preferences) {
window.locale = language;
// Broadcast locale change to apps
const broadcastService = globalThis.services.get('broadcast');
broadcastService.sendBroadcast('localeChanged', {
broadcast_service.sendBroadcast('localeChanged', {
language: language,
}, { sendToNewAppInstances: true });
};
@@ -3271,7 +3271,7 @@ window.iframe_for_app_instance = (instance_id) => {
};
// Run any callbacks to say that the app has closed
// ref(./services/ExecService.js): this is called from ExecService.js on
// ref(./modules/exec.js): this is called from the exec module on
// close if the app does not use puter.js
window.report_app_closed = (instance_id, status_code) => {
const el_window = window.window_for_app_instance(instance_id);
+6 -4
View File
@@ -19,6 +19,8 @@
import path from '../lib/path.js';
import { PROCESS_IPC_ATTACHED, PROCESS_RUNNING, PortalProcess, PseudoProcess } from '../definitions.js';
import { process_service } from '../modules/process.js';
import { broadcast_service } from '../modules/broadcast.js';
import UIWindow from '../UI/UIWindow.js';
const normalizePrivateAccessDecision = (privateAccess) => {
@@ -295,7 +297,7 @@ const launch_app = async (options) => {
app_info: app_info,
},
});
const svc_process = globalThis.services.get('process');
const svc_process = process_service;
svc_process.register(process);
if ( options.path === window.home_path ) {
title = i18n('home');
@@ -367,7 +369,7 @@ const launch_app = async (options) => {
app_info: app_info,
},
});
const svc_process = globalThis.services.get('process');
const svc_process = process_service;
svc_process.register(process);
//-----------------------------------
@@ -691,7 +693,7 @@ const launch_app = async (options) => {
$(process.references.iframe).attr('data-appUsesSDK', 'true');
// Send any saved broadcasts to the new app
globalThis.services.get('broadcast').sendSavedBroadcastsTo(uuid);
broadcast_service.sendSavedBroadcastsTo(uuid);
// If `window-active` is set (meaning the window is focused), focus the window one more time
// this is to ensure that the iframe is `definitely` focused and can receive keyboard events (e.g. keydown)
@@ -704,7 +706,7 @@ const launch_app = async (options) => {
process.chstatus(PROCESS_RUNNING);
$(el).on('remove', () => {
const svc_process = globalThis.services.get('process');
const svc_process = process_service;
svc_process.unregister(process.uuid);
});
+15 -73
View File
@@ -44,15 +44,11 @@ import update_last_touch_coordinates from './helpers/update_last_touch_coordinat
import update_mouse_position from './helpers/update_mouse_position.js';
import update_title_based_on_uploads from './helpers/update_title_based_on_uploads.js';
import path from './lib/path.js';
import { AntiCSRFService } from './services/AntiCSRFService.js';
import { BroadcastService } from './services/BroadcastService.js';
import { DebugService } from './services/DebugService.js';
import { ExecService } from './services/ExecService.js';
import { IPCService } from './services/IPCService.js';
import { LaunchOnInitService } from './services/LaunchOnInitService.js';
import { LocaleService } from './services/LocaleService.js';
import { ProcessService } from './services/ProcessService.js';
import { ThemeService } from './services/ThemeService.js';
import { exec_service } from './modules/exec.js';
import { debug_service } from './modules/debug.js';
import { theme_service } from './modules/theme.js';
import { process_service } from './modules/process.js';
import { launch_on_init } from './modules/launch-on-init.js';
import { privacy_aware_path } from './util/desktop.js';
const postAuthActions = async (action) => {
@@ -397,75 +393,21 @@ const postAuthActions = async (action) => {
};
const launch_services = async function (options) {
// === Services Data Structures ===
const services_l_ = [];
const services_m_ = {};
globalThis.services = {
get: (name) => services_m_[name],
emit: (id, args) => {
for (const [_, instance] of services_l_) {
instance.__on(id, args ?? []);
}
},
};
const register = (name, instance) => {
services_l_.push([name, instance]);
services_m_[name] = instance;
};
globalThis.def(UIComponentWindow, 'ui.UIComponentWindow');
// === Hooks for Service Scripts from Backend ===
const service_script_deferred = { services: [], on_ready: [] };
const service_script_api = {
register: (...a) => service_script_deferred.services.push(a),
on_ready: (fn) => service_script_deferred.on_ready.push(fn),
// Some files can't be imported by service scripts,
// so this hack makes that possible.
def: globalThis.def,
use: globalThis.use,
// use: name => ({ UIWindow, UIComponentWindow })[name],
};
globalThis.service_script_api_promise.resolve(service_script_api);
// These modules are plain singletons wired by direct import; their
// constructors have already run, so init() just performs the one-time
// setup that needs the runtime (DOM, puter, window.ipc_handlers).
exec_service.init();
debug_service.init();
theme_service.init();
process_service.init();
// === Builtin Services ===
register('ipc', new IPCService());
register('exec', new ExecService());
register('debug', new DebugService());
register('broadcast', new BroadcastService());
register('theme', new ThemeService());
register('process', new ProcessService());
register('locale', new LocaleService());
register('anti-csrf', new AntiCSRFService());
register('__launch-on-init', new LaunchOnInitService());
// === Service-Script Services ===
for (const [name, script] of service_script_deferred.services) {
register(name, script);
}
for (const [_, instance] of services_l_) {
await instance.construct({
gui_params: options,
});
}
for (const [_, instance] of services_l_) {
await instance.init({
services: globalThis.services,
});
}
// === Service-Script Ready ===
for (const fn of service_script_deferred.on_ready) {
await fn();
}
// Run any commands the GUI was launched with.
launch_on_init(options);
// Set init process status
{
const svc_process = globalThis.services.get('process');
svc_process.get_init().chstatus(PROCESS_RUNNING);
}
process_service.get_init().chstatus(PROCESS_RUNNING);
};
// This code snippet addresses the issue flagged by Lighthouse regarding the use of
@@ -17,9 +17,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Service } from '../definitions.js';
export class AntiCSRFService extends Service {
class AntiCSRFService {
/**
* Request an anti-csrf token from the server
* @return anti_csrf: string
@@ -39,3 +37,5 @@ export class AntiCSRFService extends Service {
return anti_csrf;
}
}
export const anti_csrf_service = new AntiCSRFService();
@@ -17,16 +17,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Service } from '../definitions.js';
export class BroadcastService extends Service {
class BroadcastService {
// After a new app is launched, it will receive these broadcasts
#broadcastsToSendToNewAppInstances = new Map(); // name -> data
async _init () {
// Nothing
}
// Send a 'broadcast' message to all open apps, with the given name and data.
// If sendToNewAppInstances is true, the message will be saved, and sent to any apps that are launched later.
// A new saved broadcast will replace an earlier one with the same name.
@@ -60,3 +54,5 @@ export class BroadcastService extends Service {
}
}
}
export const broadcast_service = new BroadcastService();
@@ -17,16 +17,17 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Service } from '../definitions.js';
import { exec_service } from './exec.js';
export class DebugService extends Service {
async _init () {
class DebugService {
constructor () {
// Track enabled log categories
this.enabled_logs = [];
}
init () {
// Provide enabled logs as a query param
const svc_exec = this.services.get('exec');
svc_exec.register_param_provider(() => {
exec_service.register_param_provider(() => {
return {
...(this.enabled_logs.length > 0
? { enabled_logs: this.enabled_logs.join(';') }
@@ -48,3 +49,5 @@ export class DebugService extends Service {
});
}
}
export const debug_service = new DebugService();
@@ -17,15 +17,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { PROCESS_IPC_ATTACHED, Service } from '../definitions.js';
import { PROCESS_IPC_ATTACHED } from '../definitions.js';
import launch_app from '../helpers/launch_app.js';
import { broadcast_service } from './broadcast.js';
import { ipc_service } from './ipc.js';
import { process_service } from './process.js';
export class ExecService extends Service {
static description = `
Manages instances of apps on the Puter desktop.
`;
_construct () {
class ExecService {
constructor () {
this.param_providers = [];
}
@@ -33,12 +32,11 @@ export class ExecService extends Service {
this.param_providers.push(param_provider);
}
async _init ({ services }) {
const svc_ipc = services.get('ipc');
svc_ipc.register_ipc_handler('launchApp', {
init () {
ipc_service.register_ipc_handler('launchApp', {
handler: this.launchApp.bind(this),
});
svc_ipc.register_ipc_handler('connectToInstance', {
ipc_service.register_ipc_handler('connectToInstance', {
handler: this.connectToInstance.bind(this),
});
@@ -55,8 +53,7 @@ export class ExecService extends Service {
// This mechanism will be replated with xdrpc soon
const child_instance_id = window.uuidv4();
const svc_ipc = this.services.get('ipc');
const connection = ipc_context ? svc_ipc.add_connection({
const connection = ipc_context ? ipc_service.add_connection({
source: process.uuid,
target: child_instance_id,
}) : undefined;
@@ -199,7 +196,7 @@ export class ExecService extends Service {
send_child_launched_msg({ uses_sdk: true });
// Send any saved broadcasts to the new app
globalThis.services.get('broadcast').sendSavedBroadcastsTo(child_instance_id);
broadcast_service.sendSavedBroadcastsTo(child_instance_id);
// If `window-active` is set (meanign the window is focused), focus the window one more time
// this is to ensure that the iframe is `definitely` focused and can receive keyboard events (e.g. keydown)
@@ -245,16 +242,14 @@ export class ExecService extends Service {
throw new Error('Connection not allowed.');
}
const svc_process = this.services.get('process');
const options = svc_process.select_by_name(app_name);
const options = process_service.select_by_name(app_name);
const process = options[0];
if ( ! process ) {
throw new Error(`No process found: ${app_name}`);
}
const svc_ipc = this.services.get('ipc');
const connection = svc_ipc.add_connection({
const connection = ipc_service.add_connection({
source: caller_process.uuid,
target: process.uuid,
});
@@ -268,3 +263,5 @@ export class ExecService extends Service {
};
}
}
export const exec_service = new ExecService();
@@ -17,11 +17,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Service } from '../definitions.js';
import { process_service } from './process.js';
class InternalConnection {
constructor ({ source, target, uuid, reverse }, { services }) {
this.services = services;
constructor ({ source, target, uuid, reverse }) {
this.source = source;
this.target = target;
this.uuid = uuid;
@@ -29,8 +28,7 @@ class InternalConnection {
}
send (data) {
const svc_process = this.services.get('process');
const process = svc_process.get_by_uuid(this.target);
const process = process_service.get_by_uuid(this.target);
const channel = {
returnAddress: this.reverse,
};
@@ -38,12 +36,8 @@ class InternalConnection {
}
}
export class IPCService extends Service {
static description = `
Allows other services to expose methods to apps.
`;
async _init () {
class IPCService {
constructor () {
this.connections_ = {};
}
@@ -69,10 +63,12 @@ export class IPCService extends Service {
const entry = this.connections_[uuid];
if ( ! entry ) return;
if ( entry.object ) return entry.object;
return entry.object = new InternalConnection(entry, this.context);
return entry.object = new InternalConnection(entry);
}
register_ipc_handler (name, spec) {
window.ipc_handlers[name] = spec;
}
}
export const ipc_service = new IPCService();
@@ -16,32 +16,27 @@
* 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/>.
*/
import UIAlert from '../UI/UIAlert.js';
import { Service } from '../definitions.js';
const commands = {
'window-call': ({ fn_name, args }) => {
window[fn_name](...args);
},
};
export class LaunchOnInitService extends Service {
_construct () {
this.commands = {
'window-call': ({ fn_name, args }) => {
window[fn_name](...args);
},
};
}
async _init () {
const launch_options = this.$puter.gui_params.launch_options;
if ( ! launch_options ) return;
const run_command = (command) => {
const args = { ...command };
delete args.$;
commands[command.$](args);
};
if ( launch_options.on_initialized ) {
for ( const command of launch_options.on_initialized ) {
this.run_(command);
}
// Run any commands the GUI was launched with (gui_params.launch_options.on_initialized).
export const launch_on_init = (gui_params) => {
const launch_options = gui_params?.launch_options;
if ( ! launch_options ) return;
if ( launch_options.on_initialized ) {
for ( const command of launch_options.on_initialized ) {
run_command(command);
}
}
run_ (command) {
const args = { ...command };
delete args.$;
this.commands[command.$](args);
}
}
};
@@ -16,10 +16,9 @@
* 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/>.
*/
import { Service } from '../definitions.js';
import i18n from '../i18n/i18n.js';
export class LocaleService extends Service {
class LocaleService {
format_duration (seconds) {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
@@ -41,3 +40,5 @@ export class LocaleService extends Service {
return `${paddedHours}:${paddedMinutes}:${paddedSeconds}`;
}
}
export const locale_service = new LocaleService();
@@ -16,35 +16,37 @@
* 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/>.
*/
import { InitProcess, Service } from '../definitions.js';
import { InitProcess } from '../definitions.js';
import { exec_service } from './exec.js';
// The NULL UUID is also the UUID for the init process.
const NULL_UUID = '00000000-0000-0000-0000-000000000000';
export class ProcessService extends Service {
class ProcessService {
static INITRC = [
// 'puter-linux'
];
async _init () {
constructor () {
this.processes = [];
this.processes_map = new Map();
this.uuid_to_treelist = new Map();
}
init () {
const root = new InitProcess({
uuid: NULL_UUID,
});
this.register_(root);
}
['__on_gui:ready'] () {
const svc_exec = this.services.get('exec');
on_gui_ready () {
for ( let spec of ProcessService.INITRC ) {
if ( typeof spec === 'string' ) {
spec = { name: spec };
}
svc_exec.launchApp({
exec_service.launchApp({
app_name: spec.name,
});
}
@@ -120,3 +122,5 @@ export class ProcessService extends Service {
}
}
}
export const process_service = new ProcessService();
@@ -18,7 +18,7 @@
*/
import UIAlert from '../UI/UIAlert.js';
import { Service } from '../definitions.js';
import { broadcast_service } from './broadcast.js';
const PUTER_THEME_DATA_FILENAME = '~/.__puter_gui.json';
@@ -32,12 +32,8 @@ const default_values = {
light_text: false,
};
export class ThemeService extends Service {
#broadcastService;
async _init () {
this.#broadcastService = globalThis.services.get('broadcast');
class ThemeService {
init () {
this.state = {
sat: 41.18,
hue: 210,
@@ -129,7 +125,7 @@ export class ThemeService extends Service {
this.root.style.setProperty('--primary-color-sidebar-item', s.light_text ? '#5a5d61aa' : '#fefeff');
// TODO: Should we debounce this to reduce traffic?
this.#broadcastService.sendBroadcast('themeChanged', {
broadcast_service.sendBroadcast('themeChanged', {
palette: {
primaryHue: s.hue,
primarySaturation: `${s.sat }%`,
@@ -154,3 +150,5 @@ export class ThemeService extends Service {
5));
}
}
export const theme_service = new ThemeService();