diff --git a/src/IPC.js b/src/IPC.js index de771e447..56e257876 100644 --- a/src/IPC.js +++ b/src/IPC.js @@ -105,6 +105,9 @@ window.addEventListener('message', async (event) => { }, '*'); delete window.child_launch_callbacks[event.data.appInstanceID]; } + + // Send any saved broadcasts to the new app + globalThis.services.get('broadcast').sendSavedBroadcastsTo(event.data.appInstanceID); } //------------------------------------------------- // windowFocused diff --git a/src/helpers.js b/src/helpers.js index a8486d11e..7a8731b85 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -722,7 +722,14 @@ window.mutate_user_preferences = function(user_preferences_delta) { window.update_user_preferences = function(user_preferences) { window.user_preferences = user_preferences; localStorage.setItem('user_preferences', JSON.stringify(user_preferences)); - window.locale = user_preferences.language ?? 'en'; + const language = user_preferences.language ?? 'en'; + window.locale = language; + + // Broadcast locale change to apps + const broadcastService = globalThis.services.get('broadcast'); + broadcastService.sendBroadcast('localeChanged', { + language: language, + }, { sendToNewAppInstances: true }); } window.sendWindowWillCloseMsg = function(iframe_element) { diff --git a/src/initgui.js b/src/initgui.js index b5d2afb37..dfb1e3b04 100644 --- a/src/initgui.js +++ b/src/initgui.js @@ -36,6 +36,7 @@ import PuterDialog from './UI/PuterDialog.js'; import determine_active_container_parent from './helpers/determine_active_container_parent.js'; import { ThemeService } from './services/ThemeService.js'; import UIWindowThemeDialog from './UI/UIWindowThemeDialog.js'; +import { BroadcastService } from './services/BroadcastService.js'; const launch_services = async function () { const services_l_ = []; @@ -49,6 +50,7 @@ const launch_services = async function () { services_m_[name] = instance; } + register('broadcast', new BroadcastService()); register('theme', new ThemeService()); for (const [_, instance] of services_l_) { diff --git a/src/services/BroadcastService.js b/src/services/BroadcastService.js new file mode 100644 index 000000000..e0e6a470d --- /dev/null +++ b/src/services/BroadcastService.js @@ -0,0 +1,61 @@ +/** + * Copyright (C) 2024 Puter Technologies Inc. + * + * This file is part of Puter. + * + * Puter is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import { Service } from "../definitions.js"; + +export class BroadcastService extends Service { + // 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. + sendBroadcast(name, data, { sendToNewAppInstances = false } = {}) { + $('.window-app-iframe[data-appUsesSDK=true]').each((_, iframe) => { + iframe.contentWindow.postMessage({ + msg: 'broadcast', + name: name, + data: data, + }, '*'); + }); + + if (sendToNewAppInstances) { + this.#broadcastsToSendToNewAppInstances.set(name, data); + } + } + + // Send all saved broadcast messages to the given app instance. + sendSavedBroadcastsTo(appInstanceID) { + const iframe = $(`.window[data-element_uuid="${appInstanceID}"] .window-app-iframe[data-appUsesSDK=true]`).get(0); + if (!iframe) { + console.error(`Attempted to send saved broadcasts to app instance ${appInstanceID}, which is not using the Puter SDK`); + return; + } + for (const [name, data] of this.#broadcastsToSendToNewAppInstances) { + iframe.contentWindow.postMessage({ + msg: 'broadcast', + name: name, + data: data, + }, '*'); + } + } +} diff --git a/src/services/ThemeService.js b/src/services/ThemeService.js index afd6c8414..10921b3ef 100644 --- a/src/services/ThemeService.js +++ b/src/services/ThemeService.js @@ -31,7 +31,11 @@ const default_values = { }; export class ThemeService extends Service { + #broadcastService; + async _init () { + this.#broadcastService = globalThis.services.get('broadcast'); + this.state = { sat: 41.18, hue: 210, @@ -113,6 +117,16 @@ export class ThemeService extends Service { this.root.style.setProperty('--primary-saturation', s.sat + '%'); this.root.style.setProperty('--primary-lightness', s.lig + '%'); this.root.style.setProperty('--primary-alpha', s.alpha); + + // TODO: Should we debounce this to reduce traffic? + this.#broadcastService.sendBroadcast('themeChanged', { + palette: { + primaryHue: s.hue, + primarySaturation: s.sat + '%', + primaryLightness: s.lig + '%', + primaryAlpha: s.alpha, + }, + }, { sendToNewAppInstances: true }); } save_ () {