feat: shortcut to open window

This commit is contained in:
lin onetwo 2025-10-16 04:02:17 +08:00
parent 93045cfbca
commit 756082ab2a
8 changed files with 53 additions and 36 deletions

View file

@ -141,7 +141,7 @@ const commonInit = async (): Promise<void> => {
await workspaceViewService.initializeAllWorkspaceView(); await workspaceViewService.initializeAllWorkspaceView();
const attachToMenubar = await preferenceService.get('attachToMenubar'); const attachToMenubar = await preferenceService.get('attachToMenubar');
if (attachToMenubar) { if (attachToMenubar) {
await windowService.enableMenubarWindow(); await windowService.openMenubarWindow();
} }
ipcMain.emit('request-update-pause-notifications-info'); ipcMain.emit('request-update-pause-notifications-info');

View file

@ -65,15 +65,15 @@ export class NativeService implements INativeService {
try { try {
const key = `${String(serviceName)}.${String(methodName)}`; const key = `${String(serviceName)}.${String(methodName)}`;
// Save to preferences // Save to preferences
const preferenceService = container.get<IPreferenceService>('Preference'); const preferenceService = container.get<IPreferenceService>(serviceIdentifier.Preference);
const shortcuts = await this.getKeyboardShortcuts(); const shortcuts = await this.getKeyboardShortcuts();
shortcuts[key] = shortcut; shortcuts[key] = shortcut;
await preferenceService.set('keyboardShortcuts', shortcuts); await preferenceService.set('keyboardShortcuts', shortcuts);
// Register the shortcut // Register the shortcut
await registerShortcutByKey(key, shortcut); await registerShortcutByKey(key, shortcut);
logger.info('Successfully registered new keyboard shortcut', { key, shortcut }); logger.info('Successfully registered new keyboard shortcut', { key, shortcut, function: 'NativeService.registerKeyboardShortcut' });
} catch (error) { } catch (error) {
logger.error('Failed to register keyboard shortcut', { error, serviceIdentifier: serviceName, methodName, shortcut }); logger.error('Failed to register keyboard shortcut', { error, serviceIdentifier: serviceName, methodName, shortcut, function: 'NativeService.registerKeyboardShortcut' });
throw error; throw error;
} }
} }
@ -84,7 +84,7 @@ export class NativeService implements INativeService {
const key = `${String(serviceName)}.${String(methodName)}`; const key = `${String(serviceName)}.${String(methodName)}`;
// Remove from preferences // Remove from preferences
const preferenceService = container.get<IPreferenceService>('Preference'); const preferenceService = container.get<IPreferenceService>(serviceIdentifier.Preference);
const shortcuts = await this.getKeyboardShortcuts(); const shortcuts = await this.getKeyboardShortcuts();
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete shortcuts[key]; delete shortcuts[key];

View file

@ -25,9 +25,12 @@ export function getShortcutCallback(key: string): (() => void) | undefined {
try { try {
// Get the service instance from container lazily // Get the service instance from container lazily
const service = container.get<Record<string, (...arguments_: unknown[]) => unknown>>(serviceSymbol); const service = container.get<Record<string, (...arguments_: unknown[]) => unknown>>(serviceSymbol);
logger.debug('Shortcut triggered', { key, service: serviceIdentifierName, method: methodName, function: 'getShortcutCallback' });
if (service && typeof service[methodName] === 'function') { if (service && typeof service[methodName] === 'function') {
// Call the method with await if it's an async method // Call the method with await if it's an async method
await service[methodName](); await service[methodName]();
} else {
logger.warn('Shortcut target method not found', { key, service: serviceIdentifierName, method: methodName, function: 'getShortcutCallback' });
} }
} catch (error) { } catch (error) {
logger.error(`Failed to execute shortcut callback for ${key}`, { error, function: 'getShortcutCallback' }); logger.error(`Failed to execute shortcut callback for ${key}`, { error, function: 'getShortcutCallback' });
@ -57,9 +60,9 @@ export async function registerShortcutByKey(key: string, shortcut: string): Prom
const success = globalShortcut.register(shortcut, callback); const success = globalShortcut.register(shortcut, callback);
if (success) { if (success) {
logger.info('Successfully registered shortcut', { key, shortcut }); logger.info('Successfully registered shortcut', { key, shortcut, function: 'registerShortcutByKey' });
} else { } else {
logger.error('Failed to register shortcut', { key, shortcut }); logger.error('Failed to register shortcut', { key, shortcut, function: 'registerShortcutByKey' });
throw new Error(`Failed to register shortcut: ${shortcut}`); throw new Error(`Failed to register shortcut: ${shortcut}`);
} }
} }

View file

@ -92,9 +92,9 @@ export class Preference implements IPreferenceService {
switch (key) { switch (key) {
case 'attachToMenubar': { case 'attachToMenubar': {
if (value) { if (value) {
await windowService.enableMenubarWindow(); await windowService.openMenubarWindow();
} else { } else {
await windowService.disableMenubarWindow(); await windowService.closeMenubarWindow(true);
} }
return; return;
} }

View file

@ -344,11 +344,11 @@ export class Window implements IWindowService {
const isOpen = await this.isMenubarOpen(); const isOpen = await this.isMenubarOpen();
if (isOpen) { if (isOpen) {
await this.disableMenubarWindow(); await this.closeMenubarWindow();
} else { } else {
const attachToMenubar = await preferenceService.get('attachToMenubar'); const attachToMenubar = await preferenceService.get('attachToMenubar');
if (attachToMenubar) { if (attachToMenubar) {
await this.enableMenubarWindow(); await this.openMenubarWindow();
} else { } else {
logger.warn('Cannot open menubar window: attachToMenubar preference is disabled', { function: 'toggleMenubarWindow' }); logger.warn('Cannot open menubar window: attachToMenubar preference is disabled', { function: 'toggleMenubarWindow' });
} }
@ -358,43 +358,57 @@ export class Window implements IWindowService {
} }
} }
public async enableMenubarWindow(): Promise<void> { public async openMenubarWindow(enableIt = true): Promise<void> {
try { try {
// Check if menubar is already enabled // Check if menubar is already enabled
if (this.mainWindowMenuBar !== undefined) { if (this.mainWindowMenuBar?.window !== undefined) {
logger.debug('Menubar is already enabled'); logger.debug('Menubar is already enabled, bring it to front', { function: 'openMenubarWindow' });
const existingWin = this.mainWindowMenuBar.window;
if (existingWin !== undefined) {
if (existingWin.isMinimized?.()) {
existingWin.restore();
}
existingWin.show();
existingWin.focus();
}
return; return;
} }
// Create menubar window // Create menubar window (create and open when enableIt is true)
await this.open(WindowNames.menuBar); await this.open(WindowNames.menuBar);
logger.info('Menubar enabled', { function: 'enableMenubarWindow' }); if (enableIt) {
logger.debug('Menubar enabled', { function: 'openMenubarWindow' });
}
} catch (error) { } catch (error) {
logger.error('Failed to enable menubar', { error, function: 'enableMenubarWindow' }); logger.error('Failed to open menubar', { error, function: 'openMenubarWindow' });
throw error; throw error;
} }
} }
public async disableMenubarWindow(): Promise<void> { public async closeMenubarWindow(disableIt = false): Promise<void> {
try { try {
// Check if menubar exists // Check if menubar exists
if (this.mainWindowMenuBar === undefined) { if (this.mainWindowMenuBar === undefined) {
logger.debug('Menubar is already disabled'); logger.debug('Menubar is already disabled', { function: 'closeMenubarWindow' });
return; return;
} }
// Close menubar window and clean up // Close menubar window and clean up
await this.close(WindowNames.menuBar); await this.close(WindowNames.menuBar);
// Clean up tray icon // Clean up tray icon if disableIt is true (meaning fully disable), otherwise keep it
if (this.mainWindowMenuBar.tray && !this.mainWindowMenuBar.tray.isDestroyed()) { if (disableIt) {
this.mainWindowMenuBar.tray.destroy(); if (this.mainWindowMenuBar.tray && !this.mainWindowMenuBar.tray.isDestroyed()) {
this.mainWindowMenuBar.tray.destroy();
}
this.mainWindowMenuBar = undefined;
logger.debug('Menubar disabled successfully without restart', { function: 'closeMenubarWindow' });
} else {
// Keep mainWindowMenuBar reference for quicker re-open
logger.debug('Menubar closed (kept enabled)', { function: 'closeMenubarWindow' });
} }
this.mainWindowMenuBar = undefined;
logger.info('Menubar disabled successfully without restart');
} catch (error) { } catch (error) {
logger.error('Failed to disable menubar', { error }); logger.error('Failed to close menubar', { error });
throw error; throw error;
} }
} }

View file

@ -57,10 +57,10 @@ export interface IWindowService {
stopFindInPage(close?: boolean, windowName?: WindowNames): Promise<void>; stopFindInPage(close?: boolean, windowName?: WindowNames): Promise<void>;
toggleMenubarWindow(): Promise<void>; toggleMenubarWindow(): Promise<void>;
updateWindowMeta<N extends WindowNames>(windowName: N, meta?: WindowMeta[N]): Promise<void>; updateWindowMeta<N extends WindowNames>(windowName: N, meta?: WindowMeta[N]): Promise<void>;
/** Enable menubar without restart - hot reload */ /** Open menubar window without restart - hot reload. enableIt=true means fully enable and open. */
enableMenubarWindow(): Promise<void>; openMenubarWindow(enableIt?: boolean): Promise<void>;
/** Disable menubar without restart - hot reload */ /** Close menubar window. disableIt=true means fully disable and cleanup tray. */
disableMenubarWindow(): Promise<void>; closeMenubarWindow(disableIt?: boolean): Promise<void>;
/** Update window properties without restart - hot reload */ /** Update window properties without restart - hot reload */
updateWindowProperties(windowName: WindowNames, properties: { alwaysOnTop?: boolean }): Promise<void>; updateWindowProperties(windowName: WindowNames, properties: { alwaysOnTop?: boolean }): Promise<void>;
} }
@ -87,8 +87,8 @@ export const WindowServiceIPCDescriptor = {
stopFindInPage: ProxyPropertyType.Function, stopFindInPage: ProxyPropertyType.Function,
toggleMenubarWindow: ProxyPropertyType.Function, toggleMenubarWindow: ProxyPropertyType.Function,
updateWindowMeta: ProxyPropertyType.Function, updateWindowMeta: ProxyPropertyType.Function,
enableMenubarWindow: ProxyPropertyType.Function, openMenubarWindow: ProxyPropertyType.Function,
disableMenubarWindow: ProxyPropertyType.Function, closeMenubarWindow: ProxyPropertyType.Function,
updateWindowProperties: ProxyPropertyType.Function, updateWindowProperties: ProxyPropertyType.Function,
}, },
}; };

View file

@ -129,12 +129,12 @@ export function TidGiMenubarWindow(props: Partial<ISectionProps>): React.JSX.Ele
<Box sx={{ p: 2 }}> <Box sx={{ p: 2 }}>
<KeyboardShortcutRegister <KeyboardShortcutRegister
label={t('Preference.MenubarShortcutKey')} label={t('Preference.MenubarShortcutKey')}
value={preference.keyboardShortcuts?.['NativeService.toggleMenubarWindow'] || ''} value={preference.keyboardShortcuts?.['Window.toggleMenubarWindow'] || ''}
onChange={async (value) => { onChange={async (value) => {
if (value && value.trim() !== '') { if (value && value.trim() !== '') {
await window.service.native.registerKeyboardShortcut<IWindowService>('NativeService', 'toggleMenubarWindow', value); await window.service.native.registerKeyboardShortcut<IWindowService>('Window', 'toggleMenubarWindow', value);
} else { } else {
await window.service.native.unregisterKeyboardShortcut<IWindowService>('NativeService', 'toggleMenubarWindow'); await window.service.native.unregisterKeyboardShortcut<IWindowService>('Window', 'toggleMenubarWindow');
} }
}} }}
/> />

@ -1 +1 @@
Subproject commit a7d3f4c939f6b939e2c63e2d2ece440e0367080a Subproject commit 1ea80618e04b848572535a827b5a1fb663fdaa1d