2026-04-24 19:14:37 +02:00
|
|
|
import { app, globalShortcut, Menu, nativeImage, Tray, type NativeImage } from 'electron';
|
2026-04-07 03:42:46 -07:00
|
|
|
import path from 'path';
|
2026-04-24 19:14:37 +02:00
|
|
|
import { runGeneralAssistShortcut } from './general-assist';
|
2026-04-27 20:39:03 +02:00
|
|
|
import { runScreenshotAssistShortcut } from './screen-capture';
|
2026-04-24 19:14:37 +02:00
|
|
|
import { showMainWindow } from './window';
|
2026-04-07 03:42:46 -07:00
|
|
|
import { getShortcuts } from './shortcuts';
|
2026-04-18 14:35:14 -07:00
|
|
|
import { trackEvent } from './analytics';
|
2026-04-07 03:42:46 -07:00
|
|
|
|
|
|
|
|
let tray: Tray | null = null;
|
2026-04-27 18:49:30 +02:00
|
|
|
let registeredGeneralAssist: string | null = null;
|
|
|
|
|
let registeredScreenshotAssist: string | null = null;
|
2026-04-07 03:42:46 -07:00
|
|
|
|
2026-04-24 19:14:37 +02:00
|
|
|
function getTrayIcon(): NativeImage {
|
2026-04-07 03:42:46 -07:00
|
|
|
const iconName = process.platform === 'win32' ? 'icon.ico' : 'icon.png';
|
|
|
|
|
const iconPath = app.isPackaged
|
|
|
|
|
? path.join(process.resourcesPath, 'assets', iconName)
|
|
|
|
|
: path.join(__dirname, '..', 'assets', iconName);
|
|
|
|
|
const img = nativeImage.createFromPath(iconPath);
|
|
|
|
|
return img.resize({ width: 16, height: 16 });
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-27 18:49:30 +02:00
|
|
|
function registerOne(
|
|
|
|
|
previous: string | null,
|
|
|
|
|
accelerator: string,
|
|
|
|
|
onFire: () => void | Promise<void>,
|
|
|
|
|
label: string
|
|
|
|
|
): string | null {
|
|
|
|
|
if (previous) {
|
|
|
|
|
globalShortcut.unregister(previous);
|
2026-04-07 03:42:46 -07:00
|
|
|
}
|
2026-04-27 18:49:30 +02:00
|
|
|
if (!accelerator) return null;
|
2026-04-07 03:42:46 -07:00
|
|
|
try {
|
2026-04-24 19:14:37 +02:00
|
|
|
const ok = globalShortcut.register(accelerator, () => {
|
2026-04-27 18:49:30 +02:00
|
|
|
void Promise.resolve(onFire());
|
2026-04-24 19:14:37 +02:00
|
|
|
});
|
2026-04-07 03:42:46 -07:00
|
|
|
if (ok) {
|
2026-04-27 18:49:30 +02:00
|
|
|
console.log(`[hotkeys] Register ${label} ${accelerator}: OK`);
|
|
|
|
|
return accelerator;
|
2026-04-07 03:42:46 -07:00
|
|
|
}
|
2026-04-27 18:49:30 +02:00
|
|
|
console.warn(`[hotkeys] Register ${label} ${accelerator}: FAILED (OS or another app may own this chord)`);
|
2026-04-07 03:42:46 -07:00
|
|
|
} catch (err) {
|
2026-04-27 18:49:30 +02:00
|
|
|
console.error(`[tray] Error registering ${label} shortcut:`, err);
|
2026-04-07 03:42:46 -07:00
|
|
|
}
|
2026-04-27 18:49:30 +02:00
|
|
|
return null;
|
2026-04-07 03:42:46 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function createTray(): Promise<void> {
|
|
|
|
|
if (tray) return;
|
|
|
|
|
|
|
|
|
|
tray = new Tray(getTrayIcon());
|
|
|
|
|
tray.setToolTip('SurfSense');
|
|
|
|
|
|
|
|
|
|
const contextMenu = Menu.buildFromTemplate([
|
2026-04-18 14:35:14 -07:00
|
|
|
{ label: 'Open SurfSense', click: () => showMainWindow('tray_menu') },
|
2026-04-07 03:42:46 -07:00
|
|
|
{ type: 'separator' },
|
2026-04-18 14:35:14 -07:00
|
|
|
{
|
|
|
|
|
label: 'Quit',
|
|
|
|
|
click: () => {
|
|
|
|
|
trackEvent('desktop_tray_quit_clicked');
|
|
|
|
|
app.exit(0);
|
|
|
|
|
},
|
|
|
|
|
},
|
2026-04-07 03:42:46 -07:00
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
tray.setContextMenu(contextMenu);
|
2026-04-18 14:35:14 -07:00
|
|
|
tray.on('double-click', () => showMainWindow('tray_click'));
|
2026-04-07 03:42:46 -07:00
|
|
|
|
|
|
|
|
const shortcuts = await getShortcuts();
|
2026-04-27 18:49:30 +02:00
|
|
|
registeredGeneralAssist = registerOne(
|
|
|
|
|
null,
|
|
|
|
|
shortcuts.generalAssist,
|
|
|
|
|
runGeneralAssistShortcut,
|
|
|
|
|
'General Assist'
|
|
|
|
|
);
|
|
|
|
|
registeredScreenshotAssist = registerOne(
|
|
|
|
|
null,
|
|
|
|
|
shortcuts.screenshotAssist,
|
|
|
|
|
runScreenshotAssistShortcut,
|
|
|
|
|
'Screenshot Assist'
|
|
|
|
|
);
|
2026-04-07 03:42:46 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function reregisterGeneralAssist(): Promise<void> {
|
|
|
|
|
const shortcuts = await getShortcuts();
|
2026-04-27 18:49:30 +02:00
|
|
|
registeredGeneralAssist = registerOne(
|
|
|
|
|
registeredGeneralAssist,
|
|
|
|
|
shortcuts.generalAssist,
|
|
|
|
|
runGeneralAssistShortcut,
|
|
|
|
|
'General Assist'
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function reregisterScreenshotAssist(): Promise<void> {
|
|
|
|
|
const shortcuts = await getShortcuts();
|
|
|
|
|
registeredScreenshotAssist = registerOne(
|
|
|
|
|
registeredScreenshotAssist,
|
|
|
|
|
shortcuts.screenshotAssist,
|
|
|
|
|
runScreenshotAssistShortcut,
|
|
|
|
|
'Screenshot Assist'
|
|
|
|
|
);
|
2026-04-07 03:42:46 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function destroyTray(): void {
|
2026-04-27 18:49:30 +02:00
|
|
|
if (registeredGeneralAssist) {
|
|
|
|
|
globalShortcut.unregister(registeredGeneralAssist);
|
|
|
|
|
registeredGeneralAssist = null;
|
|
|
|
|
}
|
|
|
|
|
if (registeredScreenshotAssist) {
|
|
|
|
|
globalShortcut.unregister(registeredScreenshotAssist);
|
|
|
|
|
registeredScreenshotAssist = null;
|
2026-04-07 03:42:46 -07:00
|
|
|
}
|
|
|
|
|
tray?.destroy();
|
|
|
|
|
tray = null;
|
|
|
|
|
}
|