From 23a2a6b57c81362bf1a0a6100b9f733a17a7facf Mon Sep 17 00:00:00 2001 From: icinggslits Date: Sun, 16 Feb 2025 05:17:42 +0800 Subject: [PATCH] improvement(shortcut): Support more keyboard shortcuts --- src/main/services/ShortcutService.ts | 66 +++++++++- .../src/pages/settings/ShortcutSettings.tsx | 124 ++++++++++++++---- 2 files changed, 165 insertions(+), 25 deletions(-) diff --git a/src/main/services/ShortcutService.ts b/src/main/services/ShortcutService.ts index ffe882f0..5f03ac51 100644 --- a/src/main/services/ShortcutService.ts +++ b/src/main/services/ShortcutService.ts @@ -56,6 +56,59 @@ function handleZoom(delta: number) { } } +const convertShortcutRecordedByKeyboardEventKeyValueToElectronGlobalShortcutFormat = ( + shortcut: string | string[] +): string => { + const accelerator = (() => { + if (Array.isArray(shortcut)) { + return shortcut + } else { + return shortcut.split('+').map((key) => key.trim()) + } + })() + + return accelerator + .map((key) => { + switch (key) { + case 'Control': + return 'CommandOrControl' + case 'Ctrl': + return 'CommandOrControl' + case 'ArrowUp': + return 'Up' + case 'ArrowDown': + return 'Down' + case 'ArrowLeft': + return 'Left' + case 'ArrowRight': + return 'Right' + case 'AltGraph': + return 'Alt' + case 'Slash': + return '/' + case 'Semicolon': + return ';' + case 'BracketLeft': + return '[' + case 'BracketRight': + return ']' + case 'Backslash': + return '\\' + case 'Quote': + return "'" + case 'Comma': + return ',' + case 'Minus': + return '-' + case 'Equal': + return '=' + default: + return key + } + }) + .join('+') +} + export function registerShortcuts(window: BrowserWindow) { window.webContents.setZoomFactor(configManager.getZoomFactor()) @@ -104,7 +157,10 @@ export function registerShortcuts(window: BrowserWindow) { } if (shortcut.enabled) { - globalShortcut.register(formatShortcutKey(shortcut.shortcut), () => handler(window)) + const accelerator = convertShortcutRecordedByKeyboardEventKeyValueToElectronGlobalShortcutFormat( + shortcut.shortcut + ) + globalShortcut.register(accelerator, () => handler(window)) } } catch (error) { Logger.error(`[ShortcutService] Failed to register shortcut ${shortcut.key}`) @@ -120,12 +176,16 @@ export function registerShortcuts(window: BrowserWindow) { if (showAppAccelerator) { const handler = getShortcutHandler({ key: 'show_app' } as Shortcut) - handler && globalShortcut.register(showAppAccelerator, () => handler(window)) + const accelerator = + convertShortcutRecordedByKeyboardEventKeyValueToElectronGlobalShortcutFormat(showAppAccelerator) + handler && globalShortcut.register(accelerator, () => handler(window)) } if (showMiniWindowAccelerator) { const handler = getShortcutHandler({ key: 'mini_window' } as Shortcut) - handler && globalShortcut.register(showMiniWindowAccelerator, () => handler(window)) + const accelerator = + convertShortcutRecordedByKeyboardEventKeyValueToElectronGlobalShortcutFormat(showMiniWindowAccelerator) + handler && globalShortcut.register(accelerator, () => handler(window)) } } catch (error) { Logger.error('[ShortcutService] Failed to unregister shortcuts') diff --git a/src/renderer/src/pages/settings/ShortcutSettings.tsx b/src/renderer/src/pages/settings/ShortcutSettings.tsx index 8a667045..7d0a2e41 100644 --- a/src/renderer/src/pages/settings/ShortcutSettings.tsx +++ b/src/renderer/src/pages/settings/ShortcutSettings.tsx @@ -8,7 +8,7 @@ import { initialState, resetShortcuts, toggleShortcut, updateShortcut } from '@r import { Shortcut } from '@renderer/types' import { Button, Input, InputRef, Switch, Table as AntTable, Tooltip } from 'antd' import type { ColumnsType } from 'antd/es/table' -import { FC, useRef, useState } from 'react' +import React, { FC, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' @@ -92,8 +92,32 @@ const ShortcutSettings: FC = () => { return isMac ? '⇧' : 'Shift' case 'CommandOrControl': return isMac ? '⌘' : 'Ctrl' - case ' ': - return 'Space' + case 'ArrowUp': + return '↑' + case 'ArrowDown': + return '↓' + case 'ArrowLeft': + return '←' + case 'ArrowRight': + return '→' + case 'Slash': + return '/' + case 'Semicolon': + return ';' + case 'BracketLeft': + return '[' + case 'BracketRight': + return ']' + case 'Backslash': + return '\\' + case 'Quote': + return "'" + case 'Comma': + return ',' + case 'Minus': + return '-' + case 'Equal': + return '=' default: return key.charAt(0).toUpperCase() + key.slice(1) } @@ -101,13 +125,61 @@ const ShortcutSettings: FC = () => { .join(' + ') } - const usableEndKeys = (key: string): string | null => { - if (key.length === 1) { - return key.toUpperCase() - } - switch (key) { + const usableEndKeys = (event: React.KeyboardEvent): string | null => { + const { code } = event + // No lock keys + // Among the commonly used keys, not including: Escape, NumpadMultiply, NumpadDivide, NumpadSubtract, NumpadAdd, NumpadDecimal + // The react-hotkeys-hook library does not differentiate between `Digit` and `Numpad` + switch (code) { + case 'KeyA': + case 'KeyB': + case 'KeyC': + case 'KeyD': + case 'KeyE': + case 'KeyF': + case 'KeyG': + case 'KeyH': + case 'KeyI': + case 'KeyJ': + case 'KeyK': + case 'KeyL': + case 'KeyM': + case 'KeyN': + case 'KeyO': + case 'KeyP': + case 'KeyQ': + case 'KeyR': + case 'KeyS': + case 'KeyT': + case 'KeyU': + case 'KeyV': + case 'KeyW': + case 'KeyX': + case 'KeyY': + case 'KeyZ': + case 'Digit0': + case 'Digit1': + case 'Digit2': + case 'Digit3': + case 'Digit4': + case 'Digit5': + case 'Digit6': + case 'Digit7': + case 'Digit8': + case 'Digit9': + case 'Numpad0': + case 'Numpad1': + case 'Numpad2': + case 'Numpad3': + case 'Numpad4': + case 'Numpad5': + case 'Numpad6': + case 'Numpad7': + case 'Numpad8': + case 'Numpad9': + return code.slice(-1) + case 'Space': case 'Enter': - case 'Escape': case 'Backspace': case 'Tab': case 'Delete': @@ -139,10 +211,24 @@ const ShortcutSettings: FC = () => { case 'F17': case 'F18': case 'F19': - case 'F20': - return key - case ' ': - return 'Space' + return code + case 'Backquote': + return '`' + case 'Period': + return '.' + case 'NumpadEnter': + return 'Enter' + // The react-hotkeys-hook library does not handle the symbol strings for the following keys + case 'Slash': + case 'Semicolon': + case 'BracketLeft': + case 'BracketRight': + case 'Backslash': + case 'Quote': + case 'Comma': + case 'Minus': + case 'Equal': + return code default: return null } @@ -156,15 +242,9 @@ const ShortcutSettings: FC = () => { if (e.metaKey) keys.push('Command') if (e.altKey) keys.push('Alt') if (e.shiftKey) keys.push('Shift') - - const key = e.key - - if (key.length === 1 && !['Control', 'Alt', 'Shift', 'Meta'].includes(key)) { - if (key === ' ') { - keys.push('Space') - } else { - keys.push(key.toUpperCase()) - } + const endKey = usableEndKeys(e) + if (endKey) { + keys.push(endKey) } if (!isValidShortcut(keys)) {