From 355c07a7dc072f3148a986bf568a9b4aa83f340f Mon Sep 17 00:00:00 2001 From: ciregenz Date: Mon, 20 Jul 2026 14:18:58 -0700 Subject: [PATCH] [eric] desktop: per-card dock glyphs and hues; real OS wallpaper read at runtime (SVG fallback) --- electron/main.js | 53 +++++++++++++++++++ electron/preload.js | 1 + .../pages/Dashboard/desktop/DesktopDock.tsx | 34 +++++++++--- .../Dashboard/desktop/DesktopWallpaper.tsx | 22 +++++++- 4 files changed, 102 insertions(+), 8 deletions(-) diff --git a/electron/main.js b/electron/main.js index 1303894e..101ed172 100644 --- a/electron/main.js +++ b/electron/main.js @@ -3114,6 +3114,59 @@ ipcMain.handle('open-application', (_event, name) => { return true; }); +// The user's REAL desktop wallpaper for the dashboard background, read at runtime and never +// bundled (Apple's images can't ship in an open-source repo). Any failure on any platform +// returns null and the renderer keeps its SVG scenery, so this can only ever add. +let desktopWallpaperCache; +ipcMain.handle('get-desktop-wallpaper', async () => { + if (desktopWallpaperCache !== undefined) return desktopWallpaperCache; + const { execFile } = require('child_process'); + const run = (cmd, args) => new Promise((resolve, reject) => { + execFile(cmd, args, { timeout: 8000 }, (err, stdout) => (err ? reject(err) : resolve(String(stdout).trim()))); + }); + try { + if (process.platform === 'darwin') { + let srcPath = await run('/usr/bin/osascript', ['-e', 'tell application "System Events" to get picture of current desktop']).catch(() => ''); + if (!srcPath || !fs.existsSync(srcPath)) { + // Dynamic/aerial wallpapers report a reaped temp frame; the wallpaper store may still hold a real image path. + srcPath = ''; + try { + const storePath = path.join(app.getPath('home'), 'Library', 'Application Support', 'com.apple.wallpaper', 'Store', 'Index.plist'); + const raw = fs.readFileSync(storePath, 'latin1'); + const candidates = [...raw.matchAll(/file:\/\/(\/[ -~]+?\.(?:jpg|jpeg|png|heic|tiff))/gi)].map((m) => decodeURIComponent(m[1])); + srcPath = candidates.find((c) => fs.existsSync(c)) || ''; + } catch (_) { /* fall through to the SVG scenery */ } + } + if (!srcPath) { desktopWallpaperCache = null; return null; } + // sips normalizes HEIC sources to jpeg and downscales so the data URL stays small. + const outPath = path.join(app.getPath('userData'), 'wallpaper-cache.jpg'); + await run('/usr/bin/sips', ['-s', 'format', 'jpeg', '--resampleHeightWidthMax', '2400', srcPath, '--out', outPath]); + desktopWallpaperCache = `data:image/jpeg;base64,${fs.readFileSync(outPath).toString('base64')}`; + return desktopWallpaperCache; + } + if (process.platform === 'win32') { + // Windows keeps the active wallpaper pre-transcoded as a JPEG; the registry path is the fallback. + const transcoded = path.join(app.getPath('appData'), 'Microsoft', 'Windows', 'Themes', 'TranscodedWallpaper'); + let srcPath = fs.existsSync(transcoded) ? transcoded : null; + if (!srcPath) { + const out = await run('reg', ['query', 'HKCU\\Control Panel\\Desktop', '/v', 'WallPaper']); + const match = out.match(/WallPaper\s+REG_SZ\s+(.+)$/m); + if (match && fs.existsSync(match[1].trim())) srcPath = match[1].trim(); + } + if (!srcPath) { desktopWallpaperCache = null; return null; } + const ext = path.extname(srcPath).toLowerCase(); + const mime = ext === '.png' ? 'image/png' : ext === '.bmp' ? 'image/bmp' : 'image/jpeg'; + desktopWallpaperCache = `data:${mime};base64,${fs.readFileSync(srcPath).toString('base64')}`; + return desktopWallpaperCache; + } + desktopWallpaperCache = null; + return null; + } catch (_) { + desktopWallpaperCache = null; + return null; + } +}); + // Affiliate install state. Returns the persisted install.json contents so // the renderer can attach the referral code to authenticated cloud calls // (Stripe checkout, sign-in events) for downstream attribution. diff --git a/electron/preload.js b/electron/preload.js index b34f065a..26c736dc 100644 --- a/electron/preload.js +++ b/electron/preload.js @@ -77,6 +77,7 @@ contextBridge.exposeInMainWorld('openswarm', { capturePage: (rect) => ipcRenderer.invoke('capture-page', rect), getAppIcon: (name) => ipcRenderer.invoke('get-app-icon', name), openApplication: (name) => ipcRenderer.invoke('open-application', name), + getDesktopWallpaper: () => ipcRenderer.invoke('get-desktop-wallpaper'), getUpdateStatus: () => ipcRenderer.invoke('get-update-status'), getCrashRecoveryInfo: () => ipcRenderer.invoke('get-crash-recovery-info'), checkForUpdates: () => ipcRenderer.invoke('check-for-updates'), diff --git a/frontend/src/app/pages/Dashboard/desktop/DesktopDock.tsx b/frontend/src/app/pages/Dashboard/desktop/DesktopDock.tsx index d720d29d..8bc49405 100644 --- a/frontend/src/app/pages/Dashboard/desktop/DesktopDock.tsx +++ b/frontend/src/app/pages/Dashboard/desktop/DesktopDock.tsx @@ -1,13 +1,12 @@ import React, { useCallback, useMemo, useRef, useState } from 'react'; import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; -import AutoAwesomeIcon from '@mui/icons-material/AutoAwesome'; import LanguageIcon from '@mui/icons-material/Language'; +import DashboardGlyph from '../canvas/DashboardGlyph'; import SettingsIcon from '@mui/icons-material/Settings'; import AppsRoundedIcon from '@mui/icons-material/AppsRounded'; import EditNoteIcon from '@mui/icons-material/EditNote'; import CalendarMonthIcon from '@mui/icons-material/CalendarMonth'; -import CoPresentIcon from '@mui/icons-material/CoPresent'; import { useAppDispatch } from '@/shared/hooks'; import { openSettingsModal } from '@/shared/state/settingsSlice'; import { getWebview } from '@/shared/browserRegistry'; @@ -57,6 +56,21 @@ interface DesktopDockProps { const TILE = 30; const PREVIEW_W = 190; +// Frames show a colorful per-card dock, not uniform tiles; hues rotate by name so two agents rarely match. +const AGENT_TILE_HUES = [ + 'linear-gradient(135deg, #4a7dd6, #2b4fa8)', + 'linear-gradient(135deg, #8a5bd6, #5b34a8)', + 'linear-gradient(135deg, #3aa88f, #1f7a64)', + 'linear-gradient(135deg, #d6754a, #a8492b)', + 'linear-gradient(135deg, #c94a7d, #96305c)', +]; + +function hueFor(name: string): string { + let h = 0; + for (let i = 0; i < name.length; i++) h = (h * 31 + name.charCodeAt(i)) | 0; + return AGENT_TILE_HUES[Math.abs(h) % AGENT_TILE_HUES.length]; +} + /** Left-edge desktop dock: one tile per open card, hover previews, click focuses the window. */ function DesktopDock({ sessions, @@ -80,12 +94,13 @@ function DesktopDock({ for (const card of Object.values(cards)) { const session = sessions[card.session_id]; if (!session) continue; + const title = displayChatTitle(session); list.push({ id: card.session_id, - label: displayChatTitle(session), + label: title, rect: card, - tileBg: 'linear-gradient(135deg, #4a7dd6, #2b4fa8)', - icon: , + tileBg: hueFor(title), + icon: , snippet: session.turn_label?.label || undefined, }); } @@ -103,12 +118,17 @@ function DesktopDock({ } for (const [cardKey, vc] of Object.entries(viewCards)) { const output = outputs[vc.output_id]; + const appName = output?.name || 'App'; list.push({ id: cardKey, - label: output?.name || 'App', + label: appName, rect: vc, tileBg: 'linear-gradient(135deg, #ef9552, #d96a2b)', - icon: , + icon: ( + + {appName.charAt(0).toUpperCase()} + + ), thumbnail: output?.thumbnail, }); } diff --git a/frontend/src/app/pages/Dashboard/desktop/DesktopWallpaper.tsx b/frontend/src/app/pages/Dashboard/desktop/DesktopWallpaper.tsx index c9a7d035..ee366058 100644 --- a/frontend/src/app/pages/Dashboard/desktop/DesktopWallpaper.tsx +++ b/frontend/src/app/pages/Dashboard/desktop/DesktopWallpaper.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import Box from '@mui/material/Box'; import { useThemeMode } from '@/shared/styles/ThemeContext'; @@ -10,6 +10,26 @@ import { useThemeMode } from '@/shared/styles/ThemeContext'; */ function DesktopWallpaper(): React.ReactElement { const { mode } = useThemeMode(); + // The user's real OS wallpaper when the Electron bridge can supply it (never bundled); SVG scenery otherwise. + const [realWallpaper, setRealWallpaper] = useState(null); + useEffect(() => { + const getWallpaper = (window as unknown as { openswarm?: { getDesktopWallpaper?: () => Promise } }) + .openswarm?.getDesktopWallpaper; + if (!getWallpaper) return; + let cancelled = false; + getWallpaper().then((dataUrl) => { if (!cancelled && dataUrl) setRealWallpaper(dataUrl); }).catch(() => undefined); + return () => { cancelled = true; }; + }, []); + + if (realWallpaper) { + return ( + + + {mode === 'dark' && } + + ); + } + return (