diff --git a/ui/desktop/src/App.tsx b/ui/desktop/src/App.tsx index 7956a5ad..08740bbb 100644 --- a/ui/desktop/src/App.tsx +++ b/ui/desktop/src/App.tsx @@ -551,6 +551,20 @@ export default function App() { }; }, []); + // Focus the first found input field + useEffect(() => { + const handleFocusInput = (_event: IpcRendererEvent) => { + const inputField = document.querySelector('input[type="text"], textarea') as HTMLInputElement; + if (inputField) { + inputField.focus(); + } + }; + window.electron.on('focus-input', handleFocusInput); + return () => { + window.electron.off('focus-input', handleFocusInput); + }; + }, []); + // TODO: modify const handleConfirm = async () => { if (pendingLink) { diff --git a/ui/desktop/src/main.ts b/ui/desktop/src/main.ts index 82c3c5b3..ee377d0c 100644 --- a/ui/desktop/src/main.ts +++ b/ui/desktop/src/main.ts @@ -10,6 +10,7 @@ import { powerSaveBlocker, Tray, App, + globalShortcut, } from 'electron'; import { Buffer } from 'node:buffer'; import started from 'electron-squirrel-startup'; @@ -720,7 +721,48 @@ ipcMain.handle('get-allowed-extensions', async () => { } }); +const createNewWindow = async (app: App, dir?: string | null) => { + const recentDirs = loadRecentDirs(); + const openDir = dir || (recentDirs.length > 0 ? recentDirs[0] : undefined); + createChat(app, undefined, openDir); +}; + +const focusWindow = () => { + const windows = BrowserWindow.getAllWindows(); + if (windows.length > 0) { + windows.forEach((win) => { + win.show(); + }); + windows[windows.length - 1].webContents.send('focus-input'); + } else { + createNewWindow(app); + } +}; + +const registerGlobalHotkey = (accelerator: string) => { + // Unregister any existing shortcuts first + globalShortcut.unregisterAll(); + + try { + const ret = globalShortcut.register(accelerator, () => { + focusWindow(); + }); + + if (!ret) { + console.error('Failed to register global hotkey'); + return false; + } + return true; + } catch (e) { + console.error('Error registering global hotkey:', e); + return false; + } +}; + app.whenReady().then(async () => { + // Register the default global hotkey + registerGlobalHotkey('CommandOrControl+Alt+Shift+G'); + session.defaultSession.webRequest.onBeforeSendHeaders((details, callback) => { details.requestHeaders['Origin'] = 'http://localhost:5173'; callback({ cancel: false, requestHeaders: details.requestHeaders }); @@ -739,9 +781,7 @@ app.whenReady().then(async () => { const { dirPath } = parseArgs(); createTray(); - const recentDirs = loadRecentDirs(); - let openDir = dirPath || (recentDirs.length > 0 ? recentDirs[0] : null); - createChat(app, undefined, openDir); + createNewWindow(app, dirPath); // Get the existing menu const menu = Menu.getApplicationMenu(); @@ -817,6 +857,17 @@ app.whenReady().then(async () => { }, }) ); + + // Add menu item for hotkey + fileMenu?.submenu.append( + new MenuItem({ + label: 'Focus Goose Window', + accelerator: 'CmdOrCtrl+Alt+Shift+G', + click() { + focusWindow(); + }, + }) + ); } if (menu) { @@ -978,6 +1029,11 @@ async function getAllowList(): Promise { } } +app.on('will-quit', () => { + // Unregister all shortcuts when quitting + globalShortcut.unregisterAll(); +}); + // Quit when all windows are closed, except on macOS or if we have a tray icon. app.on('window-all-closed', () => { // Only quit if we're not on macOS or don't have a tray icon