[eric] desktop: per-card dock glyphs and hues; real OS wallpaper read at runtime (SVG fallback)

This commit is contained in:
ciregenz
2026-07-20 14:18:58 -07:00
parent 2a015b83e1
commit 355c07a7dc
4 changed files with 102 additions and 8 deletions
+53
View File
@@ -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.
+1
View File
@@ -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'),
@@ -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: <AutoAwesomeIcon sx={{ fontSize: 17, color: '#fff' }} />,
tileBg: hueFor(title),
icon: <DashboardGlyph name={title} size={16} color="#fff" />,
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: <CoPresentIcon sx={{ fontSize: 16, color: '#fff' }} />,
icon: (
<Typography sx={{ fontSize: 14, fontWeight: 700, color: '#fff', lineHeight: 1 }}>
{appName.charAt(0).toUpperCase()}
</Typography>
),
thumbnail: output?.thumbnail,
});
}
@@ -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<string | null>(null);
useEffect(() => {
const getWallpaper = (window as unknown as { openswarm?: { getDesktopWallpaper?: () => Promise<string | null> } })
.openswarm?.getDesktopWallpaper;
if (!getWallpaper) return;
let cancelled = false;
getWallpaper().then((dataUrl) => { if (!cancelled && dataUrl) setRealWallpaper(dataUrl); }).catch(() => undefined);
return () => { cancelled = true; };
}, []);
if (realWallpaper) {
return (
<Box aria-hidden sx={{ position: 'absolute', inset: 0, pointerEvents: 'none', overflow: 'hidden' }}>
<Box component="img" src={realWallpaper} alt="" sx={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover' }} />
{mode === 'dark' && <Box sx={{ position: 'absolute', inset: 0, background: 'rgba(12,6,24,0.38)' }} />}
</Box>
);
}
return (
<Box
aria-hidden