mirror of
https://github.com/davidkaya/aryx.git
synced 2026-08-27 05:13:58 +02:00
feat: add system tray with quick actions and minimize-to-tray
System tray: - Always-visible tray icon with context menu - Quick Scratchpad action from tray (triggers session creation via IPC bridge to renderer) - Running session count in tray tooltip and menu - Click tray icon to show/focus window - Quit option in tray menu Minimize to tray: - New 'Minimize to tray on close' toggle in Settings > Appearance - When enabled, closing the window hides to tray instead of quitting (configurable per-user, default off) - macOS: hides dock icon when minimized to tray - Proper force-quit handling (Cmd+Q on macOS, tray Quit) Full IPC contract chain: - minimizeToTray setting in WorkspaceSettings - setMinimizeToTray channel + ElectronApi method - Preload bridge, service handler, IPC registration - Preserved through workspace normalization Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -514,6 +514,12 @@ export class AryxAppService extends EventEmitter<AppServiceEvents> {
|
|||||||
return this.persistAndBroadcast(workspace);
|
return this.persistAndBroadcast(workspace);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async setMinimizeToTray(enabled: boolean): Promise<WorkspaceState> {
|
||||||
|
const workspace = await this.loadWorkspace();
|
||||||
|
workspace.settings.minimizeToTray = enabled;
|
||||||
|
return this.persistAndBroadcast(workspace);
|
||||||
|
}
|
||||||
|
|
||||||
async describeTerminal(): Promise<TerminalSnapshot | undefined> {
|
async describeTerminal(): Promise<TerminalSnapshot | undefined> {
|
||||||
return this.ptyManager.getSnapshot();
|
return this.ptyManager.getSnapshot();
|
||||||
}
|
}
|
||||||
|
|||||||
+36
-3
@@ -5,11 +5,13 @@ import { registerIpcHandlers } from '@main/ipc/registerIpcHandlers';
|
|||||||
import { AryxAppService } from '@main/AryxAppService';
|
import { AryxAppService } from '@main/AryxAppService';
|
||||||
import { createMainWindow } from '@main/windows/createMainWindow';
|
import { createMainWindow } from '@main/windows/createMainWindow';
|
||||||
import { applyTitleBarTheme } from '@main/windows/titleBarTheme';
|
import { applyTitleBarTheme } from '@main/windows/titleBarTheme';
|
||||||
|
import { SystemTray, setupCloseToTray, showAndFocusWindow } from '@main/services/systemTray';
|
||||||
|
|
||||||
const { app, BrowserWindow } = electron;
|
const { app, BrowserWindow } = electron;
|
||||||
|
|
||||||
let mainWindow: BrowserWindowType | undefined;
|
let mainWindow: BrowserWindowType | undefined;
|
||||||
let appService: AryxAppService | undefined;
|
let appService: AryxAppService | undefined;
|
||||||
|
let systemTray: SystemTray | undefined;
|
||||||
|
|
||||||
async function bootstrap(): Promise<void> {
|
async function bootstrap(): Promise<void> {
|
||||||
appService = new AryxAppService();
|
appService = new AryxAppService();
|
||||||
@@ -21,6 +23,29 @@ async function bootstrap(): Promise<void> {
|
|||||||
const workspace = await appService.loadWorkspace();
|
const workspace = await appService.loadWorkspace();
|
||||||
applyTitleBarTheme(mainWindow, workspace.settings.theme);
|
applyTitleBarTheme(mainWindow, workspace.settings.theme);
|
||||||
|
|
||||||
|
// Set up system tray
|
||||||
|
systemTray = new SystemTray({
|
||||||
|
onShowWindow: showAndFocusWindow,
|
||||||
|
onCreateScratchpad: () => {
|
||||||
|
showAndFocusWindow();
|
||||||
|
mainWindow?.webContents.send('tray:create-scratchpad');
|
||||||
|
},
|
||||||
|
onQuit: () => app.quit(),
|
||||||
|
});
|
||||||
|
systemTray.create();
|
||||||
|
systemTray.updateRunningCount(workspace);
|
||||||
|
|
||||||
|
// Intercept close to hide to tray when the setting is enabled
|
||||||
|
setupCloseToTray(mainWindow, () => {
|
||||||
|
const currentWorkspace = appService?.getCachedWorkspace();
|
||||||
|
return currentWorkspace?.settings.minimizeToTray === true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Keep tray status in sync when workspace changes
|
||||||
|
appService.on('workspace-updated', (updatedWorkspace) => {
|
||||||
|
systemTray?.updateRunningCount(updatedWorkspace);
|
||||||
|
});
|
||||||
|
|
||||||
if (!app.isPackaged) {
|
if (!app.isPackaged) {
|
||||||
mainWindow.webContents.openDevTools({ mode: 'detach' });
|
mainWindow.webContents.openDevTools({ mode: 'detach' });
|
||||||
}
|
}
|
||||||
@@ -29,17 +54,25 @@ async function bootstrap(): Promise<void> {
|
|||||||
app.whenReady().then(bootstrap);
|
app.whenReady().then(bootstrap);
|
||||||
|
|
||||||
app.on('window-all-closed', () => {
|
app.on('window-all-closed', () => {
|
||||||
if (process.platform !== 'darwin') {
|
// When minimize-to-tray is enabled, don't quit on window close
|
||||||
app.quit();
|
if (process.platform === 'darwin') return;
|
||||||
}
|
|
||||||
|
const windows = BrowserWindow.getAllWindows();
|
||||||
|
const allHidden = windows.length > 0 && windows.every((w) => !w.isVisible());
|
||||||
|
if (allHidden) return;
|
||||||
|
|
||||||
|
app.quit();
|
||||||
});
|
});
|
||||||
|
|
||||||
app.on('activate', async () => {
|
app.on('activate', async () => {
|
||||||
if (BrowserWindow.getAllWindows().length === 0) {
|
if (BrowserWindow.getAllWindows().length === 0) {
|
||||||
await bootstrap();
|
await bootstrap();
|
||||||
|
} else {
|
||||||
|
showAndFocusWindow();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.on('before-quit', async () => {
|
app.on('before-quit', async () => {
|
||||||
|
systemTray?.dispose();
|
||||||
await appService?.dispose();
|
await appService?.dispose();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -91,6 +91,10 @@ export function registerIpcHandlers(window: BrowserWindow, service: AryxAppServi
|
|||||||
ipcChannels.setNotificationsEnabled,
|
ipcChannels.setNotificationsEnabled,
|
||||||
(_event, enabled: boolean) => service.setNotificationsEnabled(enabled),
|
(_event, enabled: boolean) => service.setNotificationsEnabled(enabled),
|
||||||
);
|
);
|
||||||
|
ipcMain.handle(
|
||||||
|
ipcChannels.setMinimizeToTray,
|
||||||
|
(_event, enabled: boolean) => service.setMinimizeToTray(enabled),
|
||||||
|
);
|
||||||
ipcMain.handle(ipcChannels.saveMcpServer, (_event, input: SaveMcpServerInput) =>
|
ipcMain.handle(ipcChannels.saveMcpServer, (_event, input: SaveMcpServerInput) =>
|
||||||
service.saveMcpServer(input.server),
|
service.saveMcpServer(input.server),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,142 @@
|
|||||||
|
import electron from 'electron';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
|
||||||
|
import type { WorkspaceState } from '@shared/domain/workspace';
|
||||||
|
|
||||||
|
const { app, Menu, Tray, nativeImage, BrowserWindow } = electron;
|
||||||
|
type TrayType = InstanceType<typeof Tray>;
|
||||||
|
type NativeImageType = ReturnType<typeof nativeImage.createFromPath>;
|
||||||
|
|
||||||
|
export interface SystemTrayOptions {
|
||||||
|
onShowWindow: () => void;
|
||||||
|
onCreateScratchpad: () => void;
|
||||||
|
onQuit: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveTrayIcon(): NativeImageType {
|
||||||
|
const basePath = app.getAppPath();
|
||||||
|
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
return nativeImage.createFromPath(join(basePath, 'assets', 'icons', 'windows', 'icon.ico'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use a smaller icon for tray on Linux/macOS — 32x32 for crispness
|
||||||
|
const pngPath =
|
||||||
|
process.platform === 'linux'
|
||||||
|
? join(basePath, 'assets', 'icons', 'linux', 'icons', '32x32.png')
|
||||||
|
: join(basePath, 'assets', 'icons', 'icon.png');
|
||||||
|
|
||||||
|
const image = nativeImage.createFromPath(pngPath);
|
||||||
|
|
||||||
|
// Resize to 16x16 for system tray standard size
|
||||||
|
return image.resize({ width: 16, height: 16 });
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildContextMenu(options: SystemTrayOptions, runningCount: number): Electron.Menu {
|
||||||
|
const statusLabel =
|
||||||
|
runningCount > 0 ? `${runningCount} session${runningCount > 1 ? 's' : ''} running` : 'No active sessions';
|
||||||
|
|
||||||
|
return Menu.buildFromTemplate([
|
||||||
|
{ label: 'Open Aryx', click: options.onShowWindow, type: 'normal' },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ label: 'Quick Scratchpad', click: options.onCreateScratchpad, type: 'normal' },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ label: statusLabel, enabled: false, type: 'normal' },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ label: 'Quit', click: options.onQuit, type: 'normal' },
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SystemTray {
|
||||||
|
private tray: TrayType | null = null;
|
||||||
|
private options: SystemTrayOptions;
|
||||||
|
private runningCount = 0;
|
||||||
|
|
||||||
|
constructor(options: SystemTrayOptions) {
|
||||||
|
this.options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
create(): void {
|
||||||
|
if (this.tray) return;
|
||||||
|
|
||||||
|
const icon = resolveTrayIcon();
|
||||||
|
this.tray = new Tray(icon);
|
||||||
|
this.tray.setToolTip('Aryx');
|
||||||
|
this.tray.setContextMenu(buildContextMenu(this.options, this.runningCount));
|
||||||
|
|
||||||
|
this.tray.on('click', () => {
|
||||||
|
this.options.onShowWindow();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateRunningCount(workspace: WorkspaceState): void {
|
||||||
|
const count = workspace.sessions.filter((s) => !s.isArchived && s.status === 'running').length;
|
||||||
|
if (count === this.runningCount) return;
|
||||||
|
|
||||||
|
this.runningCount = count;
|
||||||
|
this.tray?.setContextMenu(buildContextMenu(this.options, count));
|
||||||
|
|
||||||
|
const tooltip = count > 0 ? `Aryx — ${count} running` : 'Aryx';
|
||||||
|
this.tray?.setToolTip(tooltip);
|
||||||
|
}
|
||||||
|
|
||||||
|
isMinimizeToTrayEnabled(workspace: WorkspaceState): boolean {
|
||||||
|
return workspace.settings.minimizeToTray === true;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
this.tray?.destroy();
|
||||||
|
this.tray = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Intercept window close to hide to tray instead of quitting, when the setting is enabled.
|
||||||
|
* Returns true if the close was intercepted (window hidden), false if it should proceed normally.
|
||||||
|
*/
|
||||||
|
export function setupCloseToTray(
|
||||||
|
window: Electron.BrowserWindow,
|
||||||
|
getMinimizeToTray: () => boolean,
|
||||||
|
): void {
|
||||||
|
let forceQuit = false;
|
||||||
|
|
||||||
|
// On macOS, Cmd+Q triggers before-quit before the close event
|
||||||
|
app.on('before-quit', () => {
|
||||||
|
forceQuit = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
window.on('close', (event) => {
|
||||||
|
if (forceQuit) return;
|
||||||
|
|
||||||
|
if (getMinimizeToTray()) {
|
||||||
|
event.preventDefault();
|
||||||
|
window.hide();
|
||||||
|
|
||||||
|
// On macOS, also hide from the dock when minimized to tray
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
app.dock?.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show and focus the main window, restoring from tray if hidden.
|
||||||
|
*/
|
||||||
|
export function showAndFocusWindow(): void {
|
||||||
|
const windows = BrowserWindow.getAllWindows();
|
||||||
|
const mainWindow = windows[0];
|
||||||
|
if (!mainWindow) return;
|
||||||
|
|
||||||
|
// On macOS, show the dock icon again
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
app.dock?.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mainWindow.isMinimized()) {
|
||||||
|
mainWindow.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
mainWindow.show();
|
||||||
|
mainWindow.focus();
|
||||||
|
}
|
||||||
@@ -27,6 +27,7 @@ const api: ElectronApi = {
|
|||||||
setTheme: (theme) => ipcRenderer.invoke(ipcChannels.setTheme, theme),
|
setTheme: (theme) => ipcRenderer.invoke(ipcChannels.setTheme, theme),
|
||||||
setTerminalHeight: (input) => ipcRenderer.invoke(ipcChannels.setTerminalHeight, input),
|
setTerminalHeight: (input) => ipcRenderer.invoke(ipcChannels.setTerminalHeight, input),
|
||||||
setNotificationsEnabled: (enabled) => ipcRenderer.invoke(ipcChannels.setNotificationsEnabled, enabled),
|
setNotificationsEnabled: (enabled) => ipcRenderer.invoke(ipcChannels.setNotificationsEnabled, enabled),
|
||||||
|
setMinimizeToTray: (enabled) => ipcRenderer.invoke(ipcChannels.setMinimizeToTray, enabled),
|
||||||
saveMcpServer: (input) => ipcRenderer.invoke(ipcChannels.saveMcpServer, input),
|
saveMcpServer: (input) => ipcRenderer.invoke(ipcChannels.saveMcpServer, input),
|
||||||
deleteMcpServer: (serverId) => ipcRenderer.invoke(ipcChannels.deleteMcpServer, serverId),
|
deleteMcpServer: (serverId) => ipcRenderer.invoke(ipcChannels.deleteMcpServer, serverId),
|
||||||
saveLspProfile: (input) => ipcRenderer.invoke(ipcChannels.saveLspProfile, input),
|
saveLspProfile: (input) => ipcRenderer.invoke(ipcChannels.saveLspProfile, input),
|
||||||
@@ -95,6 +96,12 @@ const api: ElectronApi = {
|
|||||||
ipcRenderer.on(ipcChannels.sessionEvent, handler);
|
ipcRenderer.on(ipcChannels.sessionEvent, handler);
|
||||||
return () => ipcRenderer.off(ipcChannels.sessionEvent, handler);
|
return () => ipcRenderer.off(ipcChannels.sessionEvent, handler);
|
||||||
},
|
},
|
||||||
|
onTrayCreateScratchpad: (listener) => {
|
||||||
|
const handler = () => listener();
|
||||||
|
|
||||||
|
ipcRenderer.on(ipcChannels.trayCreateScratchpad, handler);
|
||||||
|
return () => ipcRenderer.off(ipcChannels.trayCreateScratchpad, handler);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld('aryxApi', api);
|
contextBridge.exposeInMainWorld('aryxApi', api);
|
||||||
|
|||||||
@@ -485,6 +485,13 @@ export default function App() {
|
|||||||
}
|
}
|
||||||
}, [api, workspace]);
|
}, [api, workspace]);
|
||||||
|
|
||||||
|
// Listen for tray "Quick Scratchpad" action
|
||||||
|
const scratchpadRef = useRef(handleCreateScratchpad);
|
||||||
|
scratchpadRef.current = handleCreateScratchpad;
|
||||||
|
useEffect(() => {
|
||||||
|
return api.onTrayCreateScratchpad(() => scratchpadRef.current());
|
||||||
|
}, [api]);
|
||||||
|
|
||||||
const projectForSettings = useMemo(
|
const projectForSettings = useMemo(
|
||||||
() => workspace?.projects.find((p) => p.id === projectSettingsId),
|
() => workspace?.projects.find((p) => p.id === projectSettingsId),
|
||||||
[workspace?.projects, projectSettingsId],
|
[workspace?.projects, projectSettingsId],
|
||||||
@@ -638,6 +645,8 @@ export default function App() {
|
|||||||
onSetTheme={(theme) => void api.setTheme(theme)}
|
onSetTheme={(theme) => void api.setTheme(theme)}
|
||||||
notificationsEnabled={workspace.settings.notificationsEnabled !== false}
|
notificationsEnabled={workspace.settings.notificationsEnabled !== false}
|
||||||
onSetNotificationsEnabled={(enabled) => void api.setNotificationsEnabled(enabled)}
|
onSetNotificationsEnabled={(enabled) => void api.setNotificationsEnabled(enabled)}
|
||||||
|
minimizeToTray={workspace.settings.minimizeToTray === true}
|
||||||
|
onSetMinimizeToTray={(enabled) => void api.setMinimizeToTray(enabled)}
|
||||||
onOpenAppDataFolder={() => void api.openAppDataFolder()}
|
onOpenAppDataFolder={() => void api.openAppDataFolder()}
|
||||||
onResetLocalWorkspace={async () => {
|
onResetLocalWorkspace={async () => {
|
||||||
const fresh = await api.resetLocalWorkspace();
|
const fresh = await api.resetLocalWorkspace();
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ interface SettingsPanelProps {
|
|||||||
onSetTheme: (theme: AppearanceTheme) => void;
|
onSetTheme: (theme: AppearanceTheme) => void;
|
||||||
notificationsEnabled: boolean;
|
notificationsEnabled: boolean;
|
||||||
onSetNotificationsEnabled: (enabled: boolean) => void;
|
onSetNotificationsEnabled: (enabled: boolean) => void;
|
||||||
|
minimizeToTray: boolean;
|
||||||
|
onSetMinimizeToTray: (enabled: boolean) => void;
|
||||||
onOpenAppDataFolder: () => void;
|
onOpenAppDataFolder: () => void;
|
||||||
onResetLocalWorkspace: () => Promise<void>;
|
onResetLocalWorkspace: () => Promise<void>;
|
||||||
onResolveUserDiscoveredTooling?: (serverIds: string[], resolution: 'accept' | 'dismiss') => void;
|
onResolveUserDiscoveredTooling?: (serverIds: string[], resolution: 'accept' | 'dismiss') => void;
|
||||||
@@ -122,6 +124,8 @@ export function SettingsPanel({
|
|||||||
onSetTheme,
|
onSetTheme,
|
||||||
notificationsEnabled,
|
notificationsEnabled,
|
||||||
onSetNotificationsEnabled,
|
onSetNotificationsEnabled,
|
||||||
|
minimizeToTray,
|
||||||
|
onSetMinimizeToTray,
|
||||||
onOpenAppDataFolder,
|
onOpenAppDataFolder,
|
||||||
onResetLocalWorkspace,
|
onResetLocalWorkspace,
|
||||||
onResolveUserDiscoveredTooling,
|
onResolveUserDiscoveredTooling,
|
||||||
@@ -265,6 +269,8 @@ export function SettingsPanel({
|
|||||||
onSetTheme={onSetTheme}
|
onSetTheme={onSetTheme}
|
||||||
notificationsEnabled={notificationsEnabled}
|
notificationsEnabled={notificationsEnabled}
|
||||||
onSetNotificationsEnabled={onSetNotificationsEnabled}
|
onSetNotificationsEnabled={onSetNotificationsEnabled}
|
||||||
|
minimizeToTray={minimizeToTray}
|
||||||
|
onSetMinimizeToTray={onSetMinimizeToTray}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{activeSection === 'connection' && (
|
{activeSection === 'connection' && (
|
||||||
@@ -327,12 +333,16 @@ function AppearanceSection({
|
|||||||
onSetTheme,
|
onSetTheme,
|
||||||
notificationsEnabled,
|
notificationsEnabled,
|
||||||
onSetNotificationsEnabled,
|
onSetNotificationsEnabled,
|
||||||
|
minimizeToTray,
|
||||||
|
onSetMinimizeToTray,
|
||||||
}: {
|
}: {
|
||||||
theme: AppearanceTheme;
|
theme: AppearanceTheme;
|
||||||
onSetTheme: (theme: AppearanceTheme) => void;
|
onSetTheme: (theme: AppearanceTheme) => void;
|
||||||
notificationsEnabled: boolean;
|
notificationsEnabled: boolean;
|
||||||
onSetNotificationsEnabled: (enabled: boolean) => void;
|
onSetNotificationsEnabled: (enabled: boolean) => void;
|
||||||
}) {
|
minimizeToTray: boolean;
|
||||||
|
onSetMinimizeToTray: (enabled: boolean) => void;
|
||||||
|
}){
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="mb-1">
|
<div className="mb-1">
|
||||||
@@ -397,6 +407,30 @@ function AppearanceSection({
|
|||||||
</div>
|
</div>
|
||||||
<ToggleSwitch enabled={notificationsEnabled} />
|
<ToggleSwitch enabled={notificationsEnabled} />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
{/* System Tray */}
|
||||||
|
<div className="mt-8 mb-1">
|
||||||
|
<h3 className="font-display text-[13px] font-semibold text-[var(--color-text-primary)]">System Tray</h3>
|
||||||
|
<p className="mt-0.5 text-[12px] text-[var(--color-text-muted)]">
|
||||||
|
Control how Aryx behaves when you close the window
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="mt-4 flex w-full items-center justify-between rounded-lg border border-[var(--color-border)] px-4 py-3 text-left transition hover:bg-[var(--color-surface-3)]/40"
|
||||||
|
onClick={() => onSetMinimizeToTray(!minimizeToTray)}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<span className="text-[13px] font-medium text-[var(--color-text-primary)]">
|
||||||
|
Minimize to tray on close
|
||||||
|
</span>
|
||||||
|
<p className="text-[12px] text-[var(--color-text-muted)]">
|
||||||
|
Keep Aryx running in the system tray when you close the window instead of quitting
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<ToggleSwitch enabled={minimizeToTray} />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export const ipcChannels = {
|
|||||||
setTheme: 'settings:set-theme',
|
setTheme: 'settings:set-theme',
|
||||||
setTerminalHeight: 'settings:set-terminal-height',
|
setTerminalHeight: 'settings:set-terminal-height',
|
||||||
setNotificationsEnabled: 'settings:set-notifications-enabled',
|
setNotificationsEnabled: 'settings:set-notifications-enabled',
|
||||||
|
setMinimizeToTray: 'settings:set-minimize-to-tray',
|
||||||
saveMcpServer: 'tooling:mcp:save',
|
saveMcpServer: 'tooling:mcp:save',
|
||||||
deleteMcpServer: 'tooling:mcp:delete',
|
deleteMcpServer: 'tooling:mcp:delete',
|
||||||
saveLspProfile: 'tooling:lsp:save',
|
saveLspProfile: 'tooling:lsp:save',
|
||||||
@@ -54,4 +55,5 @@ export const ipcChannels = {
|
|||||||
workspaceUpdated: 'workspace:updated',
|
workspaceUpdated: 'workspace:updated',
|
||||||
sessionEvent: 'sessions:event',
|
sessionEvent: 'sessions:event',
|
||||||
getQuota: 'sidecar:get-quota',
|
getQuota: 'sidecar:get-quota',
|
||||||
|
trayCreateScratchpad: 'tray:create-scratchpad',
|
||||||
} as const;
|
} as const;
|
||||||
|
|||||||
@@ -195,6 +195,7 @@ export interface ElectronApi {
|
|||||||
setTheme(theme: AppearanceTheme): Promise<WorkspaceState>;
|
setTheme(theme: AppearanceTheme): Promise<WorkspaceState>;
|
||||||
setTerminalHeight(input: SetTerminalHeightInput): Promise<WorkspaceState>;
|
setTerminalHeight(input: SetTerminalHeightInput): Promise<WorkspaceState>;
|
||||||
setNotificationsEnabled(enabled: boolean): Promise<WorkspaceState>;
|
setNotificationsEnabled(enabled: boolean): Promise<WorkspaceState>;
|
||||||
|
setMinimizeToTray(enabled: boolean): Promise<WorkspaceState>;
|
||||||
describeTerminal(): Promise<TerminalSnapshot | undefined>;
|
describeTerminal(): Promise<TerminalSnapshot | undefined>;
|
||||||
createTerminal(): Promise<TerminalSnapshot>;
|
createTerminal(): Promise<TerminalSnapshot>;
|
||||||
restartTerminal(): Promise<TerminalSnapshot>;
|
restartTerminal(): Promise<TerminalSnapshot>;
|
||||||
@@ -208,6 +209,7 @@ export interface ElectronApi {
|
|||||||
onTerminalExit(listener: (info: TerminalExitInfo) => void): () => void;
|
onTerminalExit(listener: (info: TerminalExitInfo) => void): () => void;
|
||||||
onWorkspaceUpdated(listener: (workspace: WorkspaceState) => void): () => void;
|
onWorkspaceUpdated(listener: (workspace: WorkspaceState) => void): () => void;
|
||||||
onSessionEvent(listener: (event: SessionEventRecord) => void): () => void;
|
onSessionEvent(listener: (event: SessionEventRecord) => void): () => void;
|
||||||
|
onTrayCreateScratchpad(listener: () => void): () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RendererSelectionState {
|
export interface RendererSelectionState {
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ export interface WorkspaceSettings {
|
|||||||
discoveredUserTooling: DiscoveredToolingState;
|
discoveredUserTooling: DiscoveredToolingState;
|
||||||
terminalHeight?: number;
|
terminalHeight?: number;
|
||||||
notificationsEnabled?: boolean;
|
notificationsEnabled?: boolean;
|
||||||
|
minimizeToTray?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SessionToolingSelection {
|
export interface SessionToolingSelection {
|
||||||
@@ -206,6 +207,8 @@ export function normalizeWorkspaceSettings(settings?: Partial<WorkspaceSettings>
|
|||||||
},
|
},
|
||||||
discoveredUserTooling: normalizeDiscoveredToolingState(settings?.discoveredUserTooling),
|
discoveredUserTooling: normalizeDiscoveredToolingState(settings?.discoveredUserTooling),
|
||||||
...(terminalHeight !== undefined ? { terminalHeight } : {}),
|
...(terminalHeight !== undefined ? { terminalHeight } : {}),
|
||||||
|
...(settings?.notificationsEnabled !== undefined ? { notificationsEnabled: settings.notificationsEnabled } : {}),
|
||||||
|
...(settings?.minimizeToTray !== undefined ? { minimizeToTray: settings.minimizeToTray } : {}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user