From 275fa86ecddbf5c9b51cb89f74734af49f7549d2 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Fri, 20 Mar 2026 20:22:37 +0200 Subject: [PATCH] feat(desktop): add system tray with clipboard-to-chat support --- surfsense_desktop/src/main.ts | 4 ++ surfsense_desktop/src/modules/clipboard.ts | 14 +++++ surfsense_desktop/src/modules/tray.ts | 73 ++++++++++++++++++++++ 3 files changed, 91 insertions(+) create mode 100644 surfsense_desktop/src/modules/clipboard.ts create mode 100644 surfsense_desktop/src/modules/tray.ts diff --git a/surfsense_desktop/src/main.ts b/surfsense_desktop/src/main.ts index aff64db22..10f442c08 100644 --- a/surfsense_desktop/src/main.ts +++ b/surfsense_desktop/src/main.ts @@ -5,7 +5,9 @@ import { createMainWindow } from './modules/window'; import { setupDeepLinks, handlePendingDeepLink } from './modules/deep-links'; import { setupAutoUpdater } from './modules/auto-updater'; import { setupMenu } from './modules/menu'; +import { setupTray } from './modules/tray'; import { registerIpcHandlers } from './ipc/handlers'; +import { registerClipboardHandlers } from './modules/clipboard'; registerGlobalErrorHandlers(); @@ -14,6 +16,7 @@ if (!setupDeepLinks()) { } registerIpcHandlers(); +registerClipboardHandlers(); // App lifecycle app.whenReady().then(async () => { @@ -26,6 +29,7 @@ app.whenReady().then(async () => { return; } createMainWindow(); + setupTray(); setupAutoUpdater(); handlePendingDeepLink(); diff --git a/surfsense_desktop/src/modules/clipboard.ts b/surfsense_desktop/src/modules/clipboard.ts new file mode 100644 index 000000000..4f9d7b802 --- /dev/null +++ b/surfsense_desktop/src/modules/clipboard.ts @@ -0,0 +1,14 @@ +import { ipcMain } from 'electron'; +import { IPC_CHANNELS } from '../ipc/channels'; + +let lastClipboardContent = ''; + +export function setClipboardContent(text: string): void { + lastClipboardContent = text; +} + +export function registerClipboardHandlers(): void { + ipcMain.handle(IPC_CHANNELS.GET_CLIPBOARD_CONTENT, () => { + return lastClipboardContent; + }); +} diff --git a/surfsense_desktop/src/modules/tray.ts b/surfsense_desktop/src/modules/tray.ts new file mode 100644 index 000000000..3527cf691 --- /dev/null +++ b/surfsense_desktop/src/modules/tray.ts @@ -0,0 +1,73 @@ +import { app, BrowserWindow, clipboard, Menu, Tray } from 'electron'; +import path from 'path'; +import { getServerPort } from './server'; +import { setClipboardContent } from './clipboard'; + +let tray: Tray | null = null; +let clipWindow: BrowserWindow | null = null; + +function getIconPath(): string { + if (app.isPackaged) { + return path.join(process.resourcesPath, 'icon.png'); + } + return path.join(__dirname, '..', 'assets', 'icon.png'); +} + +function createClipWindow(): BrowserWindow { + if (clipWindow && !clipWindow.isDestroyed()) { + clipWindow.focus(); + return clipWindow; + } + + clipWindow = new BrowserWindow({ + width: 420, + height: 620, + resizable: true, + minimizable: false, + maximizable: false, + fullscreenable: false, + webPreferences: { + preload: path.join(__dirname, 'preload.js'), + contextIsolation: true, + nodeIntegration: false, + sandbox: true, + }, + show: false, + titleBarStyle: 'hiddenInset', + }); + + clipWindow.loadURL(`http://localhost:${getServerPort()}/dashboard`); + + clipWindow.once('ready-to-show', () => { + clipWindow?.show(); + }); + + clipWindow.on('closed', () => { + clipWindow = null; + }); + + return clipWindow; +} + +export function setupTray(): void { + tray = new Tray(getIconPath()); + tray.setToolTip('SurfSense'); + + const contextMenu = Menu.buildFromTemplate([ + { + label: 'Ask about clipboard', + click: () => { + const text = clipboard.readText(); + setClipboardContent(text); + createClipWindow(); + }, + }, + { type: 'separator' }, + { + label: 'Quit', + click: () => app.quit(), + }, + ]); + + tray.setContextMenu(contextMenu); +}