diff --git a/packages/app/src/shell/titlebar/windows-menu.test.ts b/packages/app/src/shell/titlebar/windows-menu.test.ts new file mode 100644 index 00000000000..d641657baf4 --- /dev/null +++ b/packages/app/src/shell/titlebar/windows-menu.test.ts @@ -0,0 +1,14 @@ +import { describe, expect, test } from "bun:test" +import { windowsMenuAccelerator } from "./windows-menu" + +describe("Windows app menu", () => { + test("resolves the new window accelerator", () => { + expect(windowsMenuAccelerator(new KeyboardEvent("keydown", { key: "N", ctrlKey: true, shiftKey: true }))).toBe( + "window.new", + ) + }) + + test("ignores the accelerator without its modifiers", () => { + expect(windowsMenuAccelerator(new KeyboardEvent("keydown", { key: "N" }))).toBeUndefined() + }) +}) diff --git a/packages/app/src/shell/titlebar/windows-menu.tsx b/packages/app/src/shell/titlebar/windows-menu.tsx index 9d3d91c66c2..73ae13dd4fc 100644 --- a/packages/app/src/shell/titlebar/windows-menu.tsx +++ b/packages/app/src/shell/titlebar/windows-menu.tsx @@ -1,9 +1,10 @@ -import { For, type JSX } from "solid-js" +import { makeEventListener } from "@solid-primitives/event-listener" +import { For, onMount, type JSX } from "solid-js" import { Menu } from "@opencode-ai/ui/menu" import { Icon } from "@opencode-ai/ui/icon" import { IconButton } from "@opencode-ai/ui/icon-button" -import { useCommand } from "@/shell/commands/command" +import { matchKeybind, parseKeybind, useCommand } from "@/shell/commands/command" import { DESKTOP_MENU, desktopMenuVisible, @@ -13,6 +14,15 @@ import { import { usePlatform } from "@/runtime/platform/platform" import { useLanguage } from "@/runtime/i18n/language" +const accelerators = DESKTOP_MENU.flatMap((menu) => menu.items ?? []).flatMap((entry) => { + if (entry.type === "separator" || !entry.action || !entry.accelerator?.windows) return [] + return [{ action: entry.action, keybind: parseKeybind(entry.accelerator.windows) }] +}) + +export function windowsMenuAccelerator(event: KeyboardEvent) { + return accelerators.find((entry) => matchKeybind(entry.keybind, event))?.action +} + export function WindowsAppMenu(props: { command: ReturnType platform: ReturnType @@ -50,6 +60,22 @@ export function WindowsAppMenu(props: { if (entry.href) props.platform.openExternal(entry.href) } + onMount(() => { + makeEventListener( + document, + "keydown", + (event) => { + if (event.defaultPrevented) return + const action = windowsMenuAccelerator(event) + if (!action) return + event.preventDefault() + event.stopPropagation() + runAction(action) + }, + { capture: true }, + ) + }) + return (