From bb5da84c82b110f739080d46a498f79a24ab0824 Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Mon, 13 Jan 2025 12:10:23 -0800 Subject: [PATCH] Fix Windows/Linux CmdCtrl hotkey --- src-tauri/gen/schemas/linux-schema.json | 373 +++++++++++++++++- src-web/components/RecentRequestsDropdown.tsx | 2 + src-web/hooks/useHotKey.ts | 35 +- 3 files changed, 383 insertions(+), 27 deletions(-) diff --git a/src-tauri/gen/schemas/linux-schema.json b/src-tauri/gen/schemas/linux-schema.json index 07a78793..0b678935 100644 --- a/src-tauri/gen/schemas/linux-schema.json +++ b/src-tauri/gen/schemas/linux-schema.json @@ -37,7 +37,7 @@ ], "definitions": { "Capability": { - "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, \"platforms\": [\"macOS\",\"windows\"] } ```", + "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```", "type": "object", "required": [ "identifier", @@ -84,7 +84,7 @@ } }, "permissions": { - "description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ```", + "description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```", "type": "array", "items": { "$ref": "#/definitions/PermissionEntry" @@ -140,7 +140,7 @@ "identifier": { "anyOf": [ { - "description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n", + "description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n#### Included permissions within this default permission set:\n", "type": "string", "const": "fs:default" }, @@ -984,6 +984,11 @@ "type": "string", "const": "fs:allow-seek" }, + { + "description": "Enables the size command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-size" + }, { "description": "Enables the stat command without any pre-configured scope.", "type": "string", @@ -1109,6 +1114,11 @@ "type": "string", "const": "fs:deny-seek" }, + { + "description": "Denies the size command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-size" + }, { "description": "Denies the stat command without any pre-configured scope.", "type": "string", @@ -1581,7 +1591,7 @@ "description": "FS scope entry.", "anyOf": [ { - "description": "FS scope path.", + "description": "A path that can be accessed by the webview when using the fs APIs. FS scope path pattern.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", "type": "string" }, { @@ -1591,7 +1601,7 @@ ], "properties": { "path": { - "description": "FS scope path.", + "description": "A path that can be accessed by the webview when using the fs APIs.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", "type": "string" } } @@ -1605,7 +1615,7 @@ "description": "FS scope entry.", "anyOf": [ { - "description": "FS scope path.", + "description": "A path that can be accessed by the webview when using the fs APIs. FS scope path pattern.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", "type": "string" }, { @@ -1615,7 +1625,167 @@ ], "properties": { "path": { - "description": "FS scope path.", + "description": "A path that can be accessed by the webview when using the fs APIs.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", + "type": "string" + } + } + } + ] + } + } + } + }, + "properties": { + "identifier": { + "description": "Identifier of the permission or permission set.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + } + } + }, + { + "if": { + "properties": { + "identifier": { + "anyOf": [ + { + "description": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer", + "type": "string", + "const": "opener:default" + }, + { + "description": "This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application.", + "type": "string", + "const": "opener:allow-default-urls" + }, + { + "description": "Enables the open_path command without any pre-configured scope.", + "type": "string", + "const": "opener:allow-open-path" + }, + { + "description": "Enables the open_url command without any pre-configured scope.", + "type": "string", + "const": "opener:allow-open-url" + }, + { + "description": "Enables the reveal_item_in_dir command without any pre-configured scope.", + "type": "string", + "const": "opener:allow-reveal-item-in-dir" + }, + { + "description": "Denies the open_path command without any pre-configured scope.", + "type": "string", + "const": "opener:deny-open-path" + }, + { + "description": "Denies the open_url command without any pre-configured scope.", + "type": "string", + "const": "opener:deny-open-url" + }, + { + "description": "Denies the reveal_item_in_dir command without any pre-configured scope.", + "type": "string", + "const": "opener:deny-reveal-item-in-dir" + } + ] + } + } + }, + "then": { + "properties": { + "allow": { + "items": { + "title": "OpenerScopeEntry", + "description": "Opener scope entry.", + "anyOf": [ + { + "type": "object", + "required": [ + "url" + ], + "properties": { + "app": { + "description": "An application to open this url with, for example: firefox.", + "allOf": [ + { + "$ref": "#/definitions/Application" + } + ] + }, + "url": { + "description": "A URL that can be opened by the webview when using the Opener APIs.\n\nWildcards can be used following the UNIX glob pattern.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"", + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "path" + ], + "properties": { + "app": { + "description": "An application to open this path with, for example: xdg-open.", + "allOf": [ + { + "$ref": "#/definitions/Application" + } + ] + }, + "path": { + "description": "A path that can be opened by the webview when using the Opener APIs.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", + "type": "string" + } + } + } + ] + } + }, + "deny": { + "items": { + "title": "OpenerScopeEntry", + "description": "Opener scope entry.", + "anyOf": [ + { + "type": "object", + "required": [ + "url" + ], + "properties": { + "app": { + "description": "An application to open this url with, for example: firefox.", + "allOf": [ + { + "$ref": "#/definitions/Application" + } + ] + }, + "url": { + "description": "A URL that can be opened by the webview when using the Opener APIs.\n\nWildcards can be used following the UNIX glob pattern.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"", + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "path" + ], + "properties": { + "app": { + "description": "An application to open this path with, for example: xdg-open.", + "allOf": [ + { + "$ref": "#/definitions/Application" + } + ] + }, + "path": { + "description": "A path that can be opened by the webview when using the Opener APIs.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", "type": "string" } } @@ -2602,6 +2772,11 @@ "type": "string", "const": "core:webview:allow-reparent" }, + { + "description": "Enables the set_webview_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-background-color" + }, { "description": "Enables the set_webview_focus command without any pre-configured scope.", "type": "string", @@ -2682,6 +2857,11 @@ "type": "string", "const": "core:webview:deny-reparent" }, + { + "description": "Denies the set_webview_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-background-color" + }, { "description": "Denies the set_webview_focus command without any pre-configured scope.", "type": "string", @@ -2897,6 +3077,21 @@ "type": "string", "const": "core:window:allow-set-always-on-top" }, + { + "description": "Enables the set_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-background-color" + }, + { + "description": "Enables the set_badge_count command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-badge-count" + }, + { + "description": "Enables the set_badge_label command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-badge-label" + }, { "description": "Enables the set_closable command without any pre-configured scope.", "type": "string", @@ -2982,6 +3177,11 @@ "type": "string", "const": "core:window:allow-set-minimizable" }, + { + "description": "Enables the set_overlay_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-overlay-icon" + }, { "description": "Enables the set_position command without any pre-configured scope.", "type": "string", @@ -3242,6 +3442,21 @@ "type": "string", "const": "core:window:deny-set-always-on-top" }, + { + "description": "Denies the set_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-background-color" + }, + { + "description": "Denies the set_badge_count command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-badge-count" + }, + { + "description": "Denies the set_badge_label command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-badge-label" + }, { "description": "Denies the set_closable command without any pre-configured scope.", "type": "string", @@ -3327,6 +3542,11 @@ "type": "string", "const": "core:window:deny-set-minimizable" }, + { + "description": "Denies the set_overlay_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-overlay-icon" + }, { "description": "Denies the set_position command without any pre-configured scope.", "type": "string", @@ -3478,7 +3698,7 @@ "const": "dialog:deny-save" }, { - "description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n", + "description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n#### Included permissions within this default permission set:\n", "type": "string", "const": "fs:default" }, @@ -4322,6 +4542,11 @@ "type": "string", "const": "fs:allow-seek" }, + { + "description": "Enables the size command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-size" + }, { "description": "Enables the stat command without any pre-configured scope.", "type": "string", @@ -4447,6 +4672,11 @@ "type": "string", "const": "fs:deny-seek" }, + { + "description": "Denies the size command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-size" + }, { "description": "Denies the stat command without any pre-configured scope.", "type": "string", @@ -4922,6 +5152,46 @@ "type": "string", "const": "log:deny-log" }, + { + "description": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer", + "type": "string", + "const": "opener:default" + }, + { + "description": "This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application.", + "type": "string", + "const": "opener:allow-default-urls" + }, + { + "description": "Enables the open_path command without any pre-configured scope.", + "type": "string", + "const": "opener:allow-open-path" + }, + { + "description": "Enables the open_url command without any pre-configured scope.", + "type": "string", + "const": "opener:allow-open-url" + }, + { + "description": "Enables the reveal_item_in_dir command without any pre-configured scope.", + "type": "string", + "const": "opener:allow-reveal-item-in-dir" + }, + { + "description": "Denies the open_path command without any pre-configured scope.", + "type": "string", + "const": "opener:deny-open-path" + }, + { + "description": "Denies the open_url command without any pre-configured scope.", + "type": "string", + "const": "opener:deny-open-url" + }, + { + "description": "Denies the reveal_item_in_dir command without any pre-configured scope.", + "type": "string", + "const": "opener:deny-reveal-item-in-dir" + }, { "description": "This permission set configures which\noperating system information are available\nto gather from the frontend.\n\n#### Granted Permissions\n\nAll information except the host name are available.\n\n", "type": "string", @@ -5141,6 +5411,76 @@ "description": "Denies the save_window_state command without any pre-configured scope.", "type": "string", "const": "window-state:deny-save-window-state" + }, + { + "description": "Default permissions for the plugin", + "type": "string", + "const": "yaak-license:default" + }, + { + "description": "Enables the activate command without any pre-configured scope.", + "type": "string", + "const": "yaak-license:allow-activate" + }, + { + "description": "Enables the check command without any pre-configured scope.", + "type": "string", + "const": "yaak-license:allow-check" + }, + { + "description": "Denies the activate command without any pre-configured scope.", + "type": "string", + "const": "yaak-license:deny-activate" + }, + { + "description": "Denies the check command without any pre-configured scope.", + "type": "string", + "const": "yaak-license:deny-check" + }, + { + "description": "Default permissions for the plugin", + "type": "string", + "const": "yaak-sync:default" + }, + { + "description": "Enables the apply command without any pre-configured scope.", + "type": "string", + "const": "yaak-sync:allow-apply" + }, + { + "description": "Enables the calculate command without any pre-configured scope.", + "type": "string", + "const": "yaak-sync:allow-calculate" + }, + { + "description": "Enables the calculate_fs command without any pre-configured scope.", + "type": "string", + "const": "yaak-sync:allow-calculate-fs" + }, + { + "description": "Enables the watch command without any pre-configured scope.", + "type": "string", + "const": "yaak-sync:allow-watch" + }, + { + "description": "Denies the apply command without any pre-configured scope.", + "type": "string", + "const": "yaak-sync:deny-apply" + }, + { + "description": "Denies the calculate command without any pre-configured scope.", + "type": "string", + "const": "yaak-sync:deny-calculate" + }, + { + "description": "Denies the calculate_fs command without any pre-configured scope.", + "type": "string", + "const": "yaak-sync:deny-calculate-fs" + }, + { + "description": "Denies the watch command without any pre-configured scope.", + "type": "string", + "const": "yaak-sync:deny-watch" } ] }, @@ -5238,6 +5578,23 @@ } ] }, + "Application": { + "description": "Opener scope application.", + "anyOf": [ + { + "description": "Open in default application.", + "type": "null" + }, + { + "description": "If true, allow open with any application.", + "type": "boolean" + }, + { + "description": "Allow specific application to open with.", + "type": "string" + } + ] + }, "ShellScopeEntryAllowedArg": { "description": "A command argument allowed to be executed by the webview API.", "anyOf": [ diff --git a/src-web/components/RecentRequestsDropdown.tsx b/src-web/components/RecentRequestsDropdown.tsx index 087ae330..7367d8f5 100644 --- a/src-web/components/RecentRequestsDropdown.tsx +++ b/src-web/components/RecentRequestsDropdown.tsx @@ -25,6 +25,8 @@ export function RecentRequestsDropdown({ className }: Props) { const [recentRequestIds] = useRecentRequests(); // Handle key-up + // TODO: Somehow make useHotKey have this functionality. Note: e.key does not work + // on Linux, for example, when Control is mapped to CAPS. This will never fire. useKeyPressEvent('Control', undefined, () => { if (!dropdownRef.current?.isOpen) return; dropdownRef.current?.select?.(); diff --git a/src-web/hooks/useHotKey.ts b/src-web/hooks/useHotKey.ts index 12732f28..08d073e5 100644 --- a/src-web/hooks/useHotKey.ts +++ b/src-web/hooks/useHotKey.ts @@ -1,4 +1,4 @@ -import type { OsType } from '@tauri-apps/plugin-os'; +import { type } from '@tauri-apps/plugin-os'; import { useEffect, useRef } from 'react'; import { capitalize } from '../lib/capitalize'; import { useOsInfo } from './useOsInfo'; @@ -81,8 +81,6 @@ export function useHotKey( ) { const currentKeys = useRef>(new Set()); const callbackRef = useRef(callback); - const osInfo = useOsInfo(); - const os = osInfo?.osType ?? null; useEffect(() => { callbackRef.current = callback; @@ -98,25 +96,24 @@ export function useHotKey( } // Don't add key if not holding modifier - const isValidKeymapKey = e.altKey || e.ctrlKey || e.metaKey || e.shiftKey || e.key === 'Backspace'; + const isValidKeymapKey = + e.altKey || e.ctrlKey || e.metaKey || e.shiftKey || e.key === 'Backspace'; if (!isValidKeymapKey) { return; } - const key = normalizeKey(e.key, os); - // Don't add hold keys - if (HOLD_KEYS.includes(key)) { + if (HOLD_KEYS.includes(e.key)) { return; } - currentKeys.current.add(key); + currentKeys.current.add(e.key); const currentKeysWithModifiers = new Set(currentKeys.current); - if (e.altKey) currentKeysWithModifiers.add(normalizeKey('Alt', os)); - if (e.ctrlKey) currentKeysWithModifiers.add(normalizeKey('Control', os)); - if (e.metaKey) currentKeysWithModifiers.add(normalizeKey('Meta', os)); - if (e.shiftKey) currentKeysWithModifiers.add(normalizeKey('Shift', os)); + if (e.altKey) currentKeysWithModifiers.add('Alt'); + if (e.ctrlKey) currentKeysWithModifiers.add('Control'); + if (e.metaKey) currentKeysWithModifiers.add('Meta'); + if (e.shiftKey) currentKeysWithModifiers.add('Shift'); for (const [hkAction, hkKeys] of Object.entries(hotkeys) as [HotkeyAction, string[]][]) { for (const hkKey of hkKeys) { @@ -124,7 +121,7 @@ export function useHotKey( continue; } - const keys = hkKey.split('+'); + const keys = hkKey.split('+').map(resolveHotkeyKey); if ( keys.length === currentKeysWithModifiers.size && keys.every((key) => currentKeysWithModifiers.has(key)) @@ -144,8 +141,7 @@ export function useHotKey( if (options.enable === false) { return; } - const key = normalizeKey(e.key, os); - currentKeys.current.delete(key); + currentKeys.current.delete(e.key); // Clear all keys if no longer holding modifier // HACK: This is to get around the case of DOWN SHIFT -> DOWN : -> UP SHIFT -> UP ; @@ -162,7 +158,7 @@ export function useHotKey( document.removeEventListener('keydown', down, { capture: true }); document.removeEventListener('keyup', up, { capture: true }); }; - }, [action, options.enable, os]); + }, [action, options.enable]); } export function useHotKeyLabel(action: HotkeyAction): string { @@ -213,8 +209,9 @@ export function useFormattedHotkey(action: HotkeyAction | null): string[] | null } } -const normalizeKey = (key: string, os: OsType | null) => { - if (key === 'Meta' && os === 'macos') return 'CmdCtrl'; - else if (key === 'Control' && os !== 'macos') return 'CmdCtrl'; +const resolveHotkeyKey = (key: string) => { + const os = type(); + if (key === 'CmdCtrl' && os === 'macos') return 'Meta'; + else if (key === 'CmdCtrl') return 'Control'; else return key; };