mirror of
https://github.com/block/goose.git
synced 2026-07-17 12:56:20 +02:00
Keyboard short cuts
This commit is contained in:
@@ -9,9 +9,10 @@ import ConfigSettings from './config/ConfigSettings';
|
||||
import PromptsSettingsSection from './PromptsSettingsSection';
|
||||
import { ExtensionConfig } from '../../api';
|
||||
import { MainPanelLayout } from '../Layout/MainPanelLayout';
|
||||
import { Bot, Share2, Monitor, MessageSquare, FileText } from 'lucide-react';
|
||||
import { Bot, Share2, Monitor, MessageSquare, FileText, Keyboard } from 'lucide-react';
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import ChatSettingsSection from './chat/ChatSettingsSection';
|
||||
import KeyboardShortcutsSection from './keyboard/KeyboardShortcutsSection';
|
||||
import { CONFIGURATION_ENABLED } from '../../updates';
|
||||
import { trackSettingsTabViewed } from '../../utils/analytics';
|
||||
|
||||
@@ -52,6 +53,7 @@ export default function SettingsView({
|
||||
app: 'app',
|
||||
chat: 'chat',
|
||||
prompts: 'prompts',
|
||||
keyboard: 'keyboard',
|
||||
};
|
||||
|
||||
const targetTab = sectionToTab[viewOptions.section];
|
||||
@@ -130,6 +132,14 @@ export default function SettingsView({
|
||||
<FileText className="h-4 w-4" />
|
||||
Prompts
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="keyboard"
|
||||
className="flex gap-2"
|
||||
data-testid="settings-keyboard-tab"
|
||||
>
|
||||
<Keyboard className="h-4 w-4" />
|
||||
Keyboard
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="app" className="flex gap-2" data-testid="settings-app-tab">
|
||||
<Monitor className="h-4 w-4" />
|
||||
App
|
||||
@@ -169,6 +179,13 @@ export default function SettingsView({
|
||||
<PromptsSettingsSection />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent
|
||||
value="keyboard"
|
||||
className="mt-0 focus-visible:outline-none focus-visible:ring-0"
|
||||
>
|
||||
<KeyboardShortcutsSection />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent
|
||||
value="app"
|
||||
className="mt-0 focus-visible:outline-none focus-visible:ring-0"
|
||||
|
||||
+147
-99
@@ -35,6 +35,7 @@ import {
|
||||
loadSettings,
|
||||
saveSettings,
|
||||
updateEnvironmentVariables,
|
||||
getKeyboardShortcuts,
|
||||
} from './utils/settings';
|
||||
import * as crypto from 'crypto';
|
||||
// import electron from "electron";
|
||||
@@ -1266,11 +1267,23 @@ ipcMain.handle('get-settings', () => {
|
||||
|
||||
ipcMain.handle('save-settings', (_event, settings) => {
|
||||
try {
|
||||
const oldSettings = loadSettings();
|
||||
saveSettings(settings);
|
||||
return true;
|
||||
|
||||
// Check if keyboard shortcuts changed
|
||||
const oldShortcuts = getKeyboardShortcuts(oldSettings);
|
||||
const newShortcuts = getKeyboardShortcuts(settings);
|
||||
const shortcutsChanged = JSON.stringify(oldShortcuts) !== JSON.stringify(newShortcuts);
|
||||
|
||||
// Re-register global shortcuts if they changed
|
||||
if (shortcutsChanged) {
|
||||
registerGlobalShortcuts();
|
||||
}
|
||||
|
||||
return { success: true, shortcutsChanged };
|
||||
} catch (error) {
|
||||
console.error('Error saving settings:', error);
|
||||
return false;
|
||||
return { success: false, shortcutsChanged: false };
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1806,6 +1819,37 @@ const focusWindow = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// Register global shortcuts based on settings
|
||||
const registerGlobalShortcuts = () => {
|
||||
// Unregister all existing shortcuts first
|
||||
globalShortcut.unregisterAll();
|
||||
|
||||
const settings = loadSettings();
|
||||
const shortcuts = getKeyboardShortcuts(settings);
|
||||
|
||||
// Register focus window shortcut (global)
|
||||
if (shortcuts.focusWindow) {
|
||||
try {
|
||||
globalShortcut.register(shortcuts.focusWindow, () => {
|
||||
focusWindow();
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Error registering focus window hotkey:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// Register quick launcher shortcut (global)
|
||||
if (shortcuts.quickLauncher) {
|
||||
try {
|
||||
globalShortcut.register(shortcuts.quickLauncher, () => {
|
||||
createLauncher();
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Error registering launcher hotkey:', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
async function appMain() {
|
||||
await configureProxy();
|
||||
|
||||
@@ -1872,21 +1916,8 @@ async function appMain() {
|
||||
});
|
||||
});
|
||||
|
||||
try {
|
||||
globalShortcut.register('CommandOrControl+Alt+Shift+G', () => {
|
||||
createLauncher();
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Error registering launcher hotkey:', e);
|
||||
}
|
||||
|
||||
try {
|
||||
globalShortcut.register('CommandOrControl+Alt+G', () => {
|
||||
focusWindow();
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Error registering focus window hotkey:', e);
|
||||
}
|
||||
// Register global shortcuts based on settings
|
||||
registerGlobalShortcuts();
|
||||
|
||||
session.defaultSession.webRequest.onBeforeSendHeaders((details, callback) => {
|
||||
details.requestHeaders['Origin'] = 'http://localhost:5173';
|
||||
@@ -1936,20 +1967,24 @@ async function appMain() {
|
||||
|
||||
const menu = Menu.getApplicationMenu();
|
||||
|
||||
const shortcuts = getKeyboardShortcuts(settings);
|
||||
|
||||
const appMenu = menu?.items.find((item) => item.label === 'Goose');
|
||||
if (appMenu?.submenu) {
|
||||
appMenu.submenu.insert(1, new MenuItem({ type: 'separator' }));
|
||||
appMenu.submenu.insert(
|
||||
1,
|
||||
new MenuItem({
|
||||
label: 'Settings',
|
||||
accelerator: 'CmdOrCtrl+,',
|
||||
click() {
|
||||
const focusedWindow = BrowserWindow.getFocusedWindow();
|
||||
if (focusedWindow) focusedWindow.webContents.send('set-view', 'settings');
|
||||
},
|
||||
})
|
||||
);
|
||||
if (shortcuts.settings) {
|
||||
appMenu.submenu.insert(
|
||||
1,
|
||||
new MenuItem({
|
||||
label: 'Settings',
|
||||
accelerator: shortcuts.settings,
|
||||
click() {
|
||||
const focusedWindow = BrowserWindow.getFocusedWindow();
|
||||
if (focusedWindow) focusedWindow.webContents.send('set-view', 'settings');
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
appMenu.submenu.insert(1, new MenuItem({ type: 'separator' }));
|
||||
}
|
||||
|
||||
@@ -1960,7 +1995,7 @@ async function appMain() {
|
||||
const findSubmenu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: 'Find…',
|
||||
accelerator: process.platform === 'darwin' ? 'Command+F' : 'Control+F',
|
||||
accelerator: shortcuts.find || undefined,
|
||||
click() {
|
||||
const focusedWindow = BrowserWindow.getFocusedWindow();
|
||||
if (focusedWindow) focusedWindow.webContents.send('find-command');
|
||||
@@ -1968,7 +2003,7 @@ async function appMain() {
|
||||
},
|
||||
{
|
||||
label: 'Find Next',
|
||||
accelerator: process.platform === 'darwin' ? 'Command+G' : 'Control+G',
|
||||
accelerator: shortcuts.findNext || undefined,
|
||||
click() {
|
||||
const focusedWindow = BrowserWindow.getFocusedWindow();
|
||||
if (focusedWindow) focusedWindow.webContents.send('find-next');
|
||||
@@ -1976,7 +2011,7 @@ async function appMain() {
|
||||
},
|
||||
{
|
||||
label: 'Find Previous',
|
||||
accelerator: process.platform === 'darwin' ? 'Shift+Command+G' : 'Shift+Control+G',
|
||||
accelerator: shortcuts.findPrevious || undefined,
|
||||
click() {
|
||||
const focusedWindow = BrowserWindow.getFocusedWindow();
|
||||
if (focusedWindow) focusedWindow.webContents.send('find-previous');
|
||||
@@ -2005,37 +2040,43 @@ async function appMain() {
|
||||
const fileMenu = menu?.items.find((item) => item.label === 'File');
|
||||
|
||||
if (fileMenu?.submenu) {
|
||||
fileMenu.submenu.insert(
|
||||
0,
|
||||
new MenuItem({
|
||||
label: 'New Chat',
|
||||
accelerator: 'CmdOrCtrl+T',
|
||||
click() {
|
||||
const focusedWindow = BrowserWindow.getFocusedWindow();
|
||||
if (focusedWindow) focusedWindow.webContents.send('new-chat');
|
||||
},
|
||||
})
|
||||
);
|
||||
if (shortcuts.newChat) {
|
||||
fileMenu.submenu.insert(
|
||||
0,
|
||||
new MenuItem({
|
||||
label: 'New Chat',
|
||||
accelerator: shortcuts.newChat,
|
||||
click() {
|
||||
const focusedWindow = BrowserWindow.getFocusedWindow();
|
||||
if (focusedWindow) focusedWindow.webContents.send('new-chat');
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
fileMenu.submenu.insert(
|
||||
1,
|
||||
new MenuItem({
|
||||
label: 'New Chat Window',
|
||||
accelerator: process.platform === 'darwin' ? 'Cmd+N' : 'Ctrl+N',
|
||||
click() {
|
||||
ipcMain.emit('create-chat-window');
|
||||
},
|
||||
})
|
||||
);
|
||||
if (shortcuts.newChatWindow) {
|
||||
fileMenu.submenu.insert(
|
||||
1,
|
||||
new MenuItem({
|
||||
label: 'New Chat Window',
|
||||
accelerator: shortcuts.newChatWindow,
|
||||
click() {
|
||||
ipcMain.emit('create-chat-window');
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
fileMenu.submenu.insert(
|
||||
2,
|
||||
new MenuItem({
|
||||
label: 'Open Directory...',
|
||||
accelerator: 'CmdOrCtrl+O',
|
||||
click: () => openDirectoryDialog(),
|
||||
})
|
||||
);
|
||||
if (shortcuts.openDirectory) {
|
||||
fileMenu.submenu.insert(
|
||||
2,
|
||||
new MenuItem({
|
||||
label: 'Open Directory...',
|
||||
accelerator: shortcuts.openDirectory,
|
||||
click: () => openDirectoryDialog(),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const recentFilesSubmenu = buildRecentFilesMenu();
|
||||
if (recentFilesSubmenu.length > 0) {
|
||||
@@ -2050,25 +2091,30 @@ async function appMain() {
|
||||
|
||||
fileMenu.submenu.insert(4, new MenuItem({ type: 'separator' }));
|
||||
|
||||
fileMenu.submenu.append(
|
||||
new MenuItem({
|
||||
label: 'Focus Goose Window',
|
||||
accelerator: 'CmdOrCtrl+Alt+G',
|
||||
click() {
|
||||
focusWindow();
|
||||
},
|
||||
})
|
||||
);
|
||||
// Add global shortcuts from settings
|
||||
if (shortcuts.focusWindow) {
|
||||
fileMenu.submenu.append(
|
||||
new MenuItem({
|
||||
label: 'Focus Goose Window',
|
||||
accelerator: shortcuts.focusWindow,
|
||||
click() {
|
||||
focusWindow();
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
fileMenu.submenu.append(
|
||||
new MenuItem({
|
||||
label: 'Quick Launcher',
|
||||
accelerator: 'CmdOrCtrl+Alt+Shift+G',
|
||||
click() {
|
||||
createLauncher();
|
||||
},
|
||||
})
|
||||
);
|
||||
if (shortcuts.quickLauncher) {
|
||||
fileMenu.submenu.append(
|
||||
new MenuItem({
|
||||
label: 'Quick Launcher',
|
||||
accelerator: shortcuts.quickLauncher,
|
||||
click() {
|
||||
createLauncher();
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (menu) {
|
||||
@@ -2089,29 +2135,31 @@ async function appMain() {
|
||||
}
|
||||
|
||||
if (windowMenu.submenu) {
|
||||
windowMenu.submenu.append(
|
||||
new MenuItem({
|
||||
label: 'Always on Top',
|
||||
type: 'checkbox',
|
||||
accelerator: process.platform === 'darwin' ? 'Cmd+Shift+T' : 'Ctrl+Shift+T',
|
||||
click(menuItem) {
|
||||
const focusedWindow = BrowserWindow.getFocusedWindow();
|
||||
if (focusedWindow) {
|
||||
const isAlwaysOnTop = menuItem.checked;
|
||||
if (shortcuts.alwaysOnTop) {
|
||||
windowMenu.submenu.append(
|
||||
new MenuItem({
|
||||
label: 'Always on Top',
|
||||
type: 'checkbox',
|
||||
accelerator: shortcuts.alwaysOnTop,
|
||||
click(menuItem) {
|
||||
const focusedWindow = BrowserWindow.getFocusedWindow();
|
||||
if (focusedWindow) {
|
||||
const isAlwaysOnTop = menuItem.checked;
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
focusedWindow.setAlwaysOnTop(isAlwaysOnTop, 'floating');
|
||||
} else {
|
||||
focusedWindow.setAlwaysOnTop(isAlwaysOnTop);
|
||||
if (process.platform === 'darwin') {
|
||||
focusedWindow.setAlwaysOnTop(isAlwaysOnTop, 'floating');
|
||||
} else {
|
||||
focusedWindow.setAlwaysOnTop(isAlwaysOnTop);
|
||||
}
|
||||
|
||||
console.log(
|
||||
`[Main] Set always-on-top to ${isAlwaysOnTop} for window ${focusedWindow.id}`
|
||||
);
|
||||
}
|
||||
|
||||
console.log(
|
||||
`[Main] Set always-on-top to ${isAlwaysOnTop} for window ${focusedWindow.id}`
|
||||
);
|
||||
}
|
||||
},
|
||||
})
|
||||
);
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { app } from 'electron';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { defaultKeyboardShortcuts } from './keyboardShortcutDefaults';
|
||||
|
||||
export interface EnvToggles {
|
||||
GOOSE_SERVER__MEMORY: boolean;
|
||||
@@ -13,6 +14,19 @@ export interface ExternalGoosedConfig {
|
||||
secret: string;
|
||||
}
|
||||
|
||||
export interface KeyboardShortcuts {
|
||||
focusWindow: string | null; // null means disabled
|
||||
quickLauncher: string | null;
|
||||
newChat: string | null;
|
||||
newChatWindow: string | null;
|
||||
openDirectory: string | null;
|
||||
settings: string | null;
|
||||
find: string | null;
|
||||
findNext: string | null;
|
||||
findPrevious: string | null;
|
||||
alwaysOnTop: string | null;
|
||||
}
|
||||
|
||||
export interface Settings {
|
||||
envToggles: EnvToggles;
|
||||
showMenuBarIcon: boolean;
|
||||
@@ -20,6 +34,8 @@ export interface Settings {
|
||||
enableWakelock: boolean;
|
||||
spellcheckEnabled: boolean;
|
||||
externalGoosed?: ExternalGoosedConfig;
|
||||
globalShortcut?: string | null; // Deprecated: use keyboardShortcuts.focusWindow
|
||||
keyboardShortcuts?: KeyboardShortcuts;
|
||||
}
|
||||
|
||||
const SETTINGS_FILE = path.join(app.getPath('userData'), 'settings.json');
|
||||
@@ -33,6 +49,8 @@ const defaultSettings: Settings = {
|
||||
showDockIcon: true,
|
||||
enableWakelock: false,
|
||||
spellcheckEnabled: true,
|
||||
globalShortcut: 'CommandOrControl+Alt+G', // Deprecated: kept for backwards compatibility
|
||||
keyboardShortcuts: defaultKeyboardShortcuts,
|
||||
};
|
||||
|
||||
// Settings management
|
||||
@@ -69,3 +87,17 @@ export function updateEnvironmentVariables(envToggles: EnvToggles): void {
|
||||
delete process.env.GOOSE_SERVER__COMPUTER_CONTROLLER;
|
||||
}
|
||||
}
|
||||
|
||||
export function getKeyboardShortcuts(settings: Settings): KeyboardShortcuts {
|
||||
// Migrate from old globalShortcut field if needed
|
||||
if (!settings.keyboardShortcuts && settings.globalShortcut !== undefined) {
|
||||
return {
|
||||
...defaultKeyboardShortcuts,
|
||||
focusWindow: settings.globalShortcut,
|
||||
quickLauncher: settings.globalShortcut
|
||||
? settings.globalShortcut.replace(/\+G$/i, '+Shift+G')
|
||||
: null,
|
||||
};
|
||||
}
|
||||
return settings.keyboardShortcuts || defaultKeyboardShortcuts;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user