diff --git a/src/lib/electron/electron_relay.ts b/src/lib/electron/electron_relay.ts index 7fc4e203..bb47f939 100644 --- a/src/lib/electron/electron_relay.ts +++ b/src/lib/electron/electron_relay.ts @@ -384,13 +384,33 @@ export async function control_presentation({ // 4. System Management (Phase 5+) -export async function set_wallpaper({ path }: { path: string }) { +export async function set_wallpaper({ + path, + url, + url_external, + display = 'all', + api_key, + account_id +}: { + /** Local file path (existing behavior). */ + path?: string; + /** HTTPS URL — downloaded to ~/Library/Caches/OSIT/wallpaper/ before applying. */ + url?: string; + /** Optional separate URL for the external/projector display only. */ + url_external?: string; + /** Which display(s) to target. Defaults to 'all'. */ + display?: 'all' | 'primary' | 'external'; + /** Aether API key passed as x-aether-api-key header on the download request. */ + api_key?: string; + /** Aether account ID passed as x-account-id header on the download request. */ + account_id?: string; +}) { if (!native || !native.set_wallpaper) return { success: false, error: 'Native handler set_wallpaper not available' }; - return await native.set_wallpaper({ path }); + return await native.set_wallpaper({ path, url, url_external, display, api_key, account_id }); } export async function update_app(args: { diff --git a/src/lib/stores/ae_events_stores__launcher_defaults.ts b/src/lib/stores/ae_events_stores__launcher_defaults.ts index 4bc9155b..22bb6e0c 100644 --- a/src/lib/stores/ae_events_stores__launcher_defaults.ts +++ b/src/lib/stores/ae_events_stores__launcher_defaults.ts @@ -39,12 +39,17 @@ export interface LauncherLocState { section_state__health: SectionState; section_state__native_os: SectionState; section_state__launch_timing: SectionState; + section_state__wallpaper: SectionState; section_state__sync_timers: SectionState; section_state__updates: SectionState; section_state__controller: SectionState; section_state__screen_saver: SectionState; section_state__app_modes: SectionState; section_state__local_actions: SectionState; + /** URL last successfully applied as wallpaper on this device. Used to detect remote config changes. */ + wallpaper_applied_url: string | null; + /** URL last applied to the external display (when url_external is configured). */ + wallpaper_applied_url_external: string | null; datetime_format: string; time_format: string; time_hours: 12 | 24; @@ -160,6 +165,7 @@ export const launcher_loc_defaults: LauncherLocState = { section_state__health: 'auto', section_state__native_os: 'collapsed', section_state__launch_timing: 'collapsed', + section_state__wallpaper: 'collapsed', section_state__sync_timers: 'collapsed', section_state__updates: 'collapsed', section_state__controller: 'auto', @@ -208,7 +214,9 @@ export const launcher_loc_defaults: LauncherLocState = { controller: 'local', controller_group_code: 'launcher-00', controller_client_id: null, - native_test_mode: false + native_test_mode: false, + wallpaper_applied_url: null, + wallpaper_applied_url_external: null // controller_cmd: null, // controller_trigger_send: null, }; diff --git a/src/routes/events/[event_id]/(launcher)/cfg_components/launcher_cfg_wallpaper.svelte b/src/routes/events/[event_id]/(launcher)/cfg_components/launcher_cfg_wallpaper.svelte new file mode 100644 index 00000000..9236d68b --- /dev/null +++ b/src/routes/events/[event_id]/(launcher)/cfg_components/launcher_cfg_wallpaper.svelte @@ -0,0 +1,296 @@ + + + + + {#if $ae_loc.edit_mode && !$ae_loc.is_native} +
+ + + Dev Preview — Apply requires Electron; Save works from any device + +
+ {/if} + +
+

+ Desktop Background Images +

+

+ Paste an HTTPS image URL. Save writes it to this device's config so all + devices auto-apply on their next heartbeat. Apply sets it immediately on + this machine. +

+ + {#if !get_device_id()} +
+ No device record loaded — Save requires a native device config. +
+ {/if} + + +
+
+ +

+ All Displays +

+ {#if is_applied} + Applied ✓ + {:else if $events_loc.launcher.wallpaper_applied_url} + Pending apply + {/if} +
+ + {#if $events_loc.launcher.wallpaper_applied_url && $events_loc.launcher.wallpaper_applied_url !== url_input.trim()} +

+ Applied: {$events_loc.launcher.wallpaper_applied_url} +

+ {/if} +
+ + +
+
+ +

+ External / Projector + (optional) +

+
+

+ Leave blank to use the same image on all displays. +

+ +
+ + +
+ + +
+ + + {#if save_status} +
+ {save_status} +
+ {/if} + {#if apply_status} +
+ {apply_status} +
+ {/if} +
+
diff --git a/src/routes/events/[event_id]/(launcher)/launcher_background_sync.svelte b/src/routes/events/[event_id]/(launcher)/launcher_background_sync.svelte index 6f5bb1bd..c51590f3 100644 --- a/src/routes/events/[event_id]/(launcher)/launcher_background_sync.svelte +++ b/src/routes/events/[event_id]/(launcher)/launcher_background_sync.svelte @@ -17,6 +17,69 @@ import { db_events } from '$lib/ae_events/db_events'; import * as native from '$lib/electron/electron_relay'; const { cleanup_tmp_files } = native; +/** + * Parses other_json (string or object) into a plain Record. + * Duplicated from launcher_cfg_wallpaper to keep this component self-contained. + */ +function parse_other_json(raw: unknown): Record { + if (!raw) return {}; + if (typeof raw === 'object') return { ...(raw as Record) }; + if (typeof raw !== 'string') return {}; + try { + const parsed = JSON.parse(raw); + return parsed && typeof parsed === 'object' ? { ...parsed } : {}; + } catch { + return {}; + } +} + +/** + * Auto-applies the configured wallpaper if the URL in device config differs from + * what was last applied on this machine. Called at startup and after each heartbeat. + * + * WHY: staff updates the wallpaper URL in the device config once (from any machine); + * all native Launcher instances pick it up within one heartbeat cycle (~60s) without + * anyone needing to visit each podium Mac physically. + */ +async function apply_wallpaper_if_changed(device_other_json: unknown) { + if (!$ae_loc.is_native) return; + + const other = parse_other_json(device_other_json); + const launcher_cfg = (other.launcher as Record | undefined) ?? {}; + const wallpaper = (launcher_cfg.wallpaper as { url?: string; url_external?: string } | undefined) ?? {}; + + const configured_url = wallpaper.url ?? ''; + const configured_url_external = wallpaper.url_external ?? ''; + const applied_url = $events_loc.launcher.wallpaper_applied_url ?? ''; + const applied_url_external = $events_loc.launcher.wallpaper_applied_url_external ?? ''; + + const url_changed = configured_url && configured_url !== applied_url; + const ext_changed = configured_url_external !== applied_url_external; + + if (!url_changed && !ext_changed) return; + + if (log_lvl) console.log('Sync: Wallpaper config changed — applying.', { configured_url, applied_url }); + + // If only the external URL is configured, leave the built-in display unchanged. + const display = !configured_url && configured_url_external ? 'external' : 'all'; + + const result = await native.set_wallpaper({ + url: configured_url || undefined, + url_external: configured_url_external || undefined, + display, + api_key: $ae_api.api_secret_key, + account_id: String($ae_api.account_id ?? '') + }); + + if (result?.success) { + $events_loc.launcher.wallpaper_applied_url = configured_url || null; + $events_loc.launcher.wallpaper_applied_url_external = configured_url_external || null; + if (log_lvl) console.log('Sync: Wallpaper applied.'); + } else { + console.warn('Sync: Wallpaper apply failed.', (result as any)?.error); + } +} + let { log_lvl = 1 } = $props(); let currently_syncing: string | null = $state(null); @@ -134,6 +197,12 @@ onMount(async () => { run_device_heartbeat(); refresh_location_config(); + // On startup: apply wallpaper if the device config has one that differs from + // what was last applied (covers reboots and first-time device setup). + if ($ae_loc.is_native) { + apply_wallpaper_if_changed($ae_loc.native_device?.other_json); + } + // Stagger initial data fetches setTimeout(() => refresh_session_data(), 1000); setTimeout(() => refresh_presentation_data(), 3000); @@ -432,13 +501,19 @@ async function run_device_heartbeat() { update_payload.notes = 'Heartbeat from non-native web session.'; } - await events_func.update_ae_obj__event_device({ + const heartbeat_result = await events_func.update_ae_obj__event_device({ api_cfg: $ae_api, event_device_id: String(device_id), data_kv: update_payload, log_lvl: 0 }); + // V3 PATCH returns the full updated record. Use it to detect wallpaper + // config changes made remotely (e.g. staff updated other_json from another device). + if (heartbeat_result) { + apply_wallpaper_if_changed((heartbeat_result as any).other_json); + } + last_heartbeat = new Date().toLocaleTimeString(); $events_sess.launcher.heartbeat_info = { last_timestamp: last_heartbeat, diff --git a/src/routes/events/[event_id]/(launcher)/launcher_cfg.svelte b/src/routes/events/[event_id]/(launcher)/launcher_cfg.svelte index bf7daba6..bbc6f87e 100644 --- a/src/routes/events/[event_id]/(launcher)/launcher_cfg.svelte +++ b/src/routes/events/[event_id]/(launcher)/launcher_cfg.svelte @@ -33,6 +33,7 @@ import Launcher_Cfg_Screen_Saver from './cfg_components/launcher_cfg_screen_save import Launcher_Cfg_App_Modes from './cfg_components/launcher_cfg_app_modes.svelte'; import Launcher_Cfg_Local_Actions from './cfg_components/launcher_cfg_local_actions.svelte'; import Launcher_Cfg_Launch_Timing from './cfg_components/launcher_cfg_launch_timing.svelte'; +import Launcher_Cfg_Wallpaper from './cfg_components/launcher_cfg_wallpaper.svelte'; import { Bug, Code, @@ -181,6 +182,8 @@ function handle_section_expand(current_key: string) { on_expand={() => handle_section_expand('health')} /> handle_section_expand('native_os')} /> + handle_section_expand('wallpaper')} /> handle_section_expand('launch_timing')} /> {#if $ae_loc.is_native}