-
-
Notifications
You must be signed in to change notification settings - Fork 23
Scope DevTools shortcuts to Harnss window to stop global Cmd+Alt+I interception #38
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| import { describe, expect, it } from "vitest"; | ||
| import { isDevToolsShortcut } from "../devtools-shortcuts"; | ||
|
|
||
| describe("isDevToolsShortcut", () => { | ||
| it("matches F12 on keyDown", () => { | ||
| expect(isDevToolsShortcut({ type: "keyDown", key: "F12" }, "darwin")).toBe(true); | ||
| expect(isDevToolsShortcut({ type: "keyUp", key: "F12" }, "darwin")).toBe(false); | ||
| }); | ||
|
|
||
| it("matches Cmd+Alt+I on macOS only when meta is pressed", () => { | ||
| expect(isDevToolsShortcut({ type: "keyDown", key: "i", meta: true, alt: true }, "darwin")).toBe(true); | ||
| expect(isDevToolsShortcut({ type: "keyDown", key: "i", control: true, alt: true }, "darwin")).toBe(false); | ||
| }); | ||
|
|
||
| it("matches Ctrl+Alt+I on non-macOS", () => { | ||
| expect(isDevToolsShortcut({ type: "keyDown", key: "I", control: true, alt: true }, "linux")).toBe(true); | ||
| expect(isDevToolsShortcut({ type: "keyDown", key: "I", meta: true, alt: true }, "linux")).toBe(false); | ||
| }); | ||
|
|
||
| it("matches Cmd/Ctrl+Shift+J", () => { | ||
| expect(isDevToolsShortcut({ type: "keyDown", key: "j", meta: true, shift: true }, "darwin")).toBe(true); | ||
| expect(isDevToolsShortcut({ type: "keyDown", key: "j", control: true, shift: true }, "win32")).toBe(true); | ||
| }); | ||
|
|
||
| it("does not match unrelated combos", () => { | ||
| expect(isDevToolsShortcut({ type: "keyDown", key: "i", meta: true }, "darwin")).toBe(false); | ||
| expect(isDevToolsShortcut({ type: "keyDown", key: "j", meta: true }, "darwin")).toBe(false); | ||
| expect(isDevToolsShortcut({ type: "keyDown", key: "k", meta: true, shift: true }, "darwin")).toBe(false); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| type ShortcutInput = { | ||
| type?: string; | ||
| key?: string; | ||
| control?: boolean; | ||
| alt?: boolean; | ||
| shift?: boolean; | ||
| meta?: boolean; | ||
| }; | ||
|
|
||
| /** | ||
| * Matches the app's DevTools shortcuts from keyboard input events. | ||
| * Scoped to window input handling so it never captures OS-global shortcuts. | ||
| */ | ||
| export function isDevToolsShortcut(input: ShortcutInput, platform: NodeJS.Platform): boolean { | ||
| if (input.type !== "keyDown") return false; | ||
|
|
||
| const key = (input.key ?? "").toLowerCase(); | ||
| if (key === "f12") return true; | ||
|
|
||
| const commandOrControl = platform === "darwin" ? !!input.meta : !!input.control; | ||
| if (!commandOrControl) return false; | ||
|
|
||
| if (input.alt && key === "i") return true; | ||
| if (input.shift && key === "j") return true; | ||
|
|
||
| return false; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| import { execSync } from "child_process"; | ||
| import { app, BrowserWindow, clipboard, globalShortcut, ipcMain, Menu, session, shell, systemPreferences } from "electron"; | ||
| import { app, BrowserWindow, clipboard, ipcMain, Menu, session, shell, systemPreferences } from "electron"; | ||
| import path from "path"; | ||
| import http from "http"; | ||
| import contextMenu from "electron-context-menu"; | ||
|
|
@@ -25,6 +25,7 @@ import { migrateFromOpenAcpUi } from "./lib/migration"; | |
| import { glassEnabled, liquidGlass } from "./lib/glass"; | ||
| import { initAutoUpdater, getIsInstallingUpdate } from "./lib/updater"; | ||
| import { initPostHog, shutdownPostHog, reinitPostHog, captureEvent } from "./lib/posthog"; | ||
| import { isDevToolsShortcut } from "./lib/devtools-shortcuts"; | ||
| import { sessions } from "./ipc/claude-sessions"; | ||
| import { acpSessions, getAcpAnalyticsPropertiesForSession } from "./ipc/acp-sessions"; | ||
| import { terminals } from "./ipc/terminal"; | ||
|
|
@@ -149,6 +150,13 @@ function createWindow(): void { | |
| } | ||
| }); | ||
| } | ||
|
|
||
| mainWindow.webContents.on("before-input-event", (event, input) => { | ||
| if (!isDevToolsShortcut(input, process.platform)) return; | ||
| event.preventDefault(); | ||
| log("DEVTOOLS", `Shortcut ${input.key} triggered`); | ||
| openDevToolsWindow(); | ||
| }); | ||
|
Comment on lines
+154
to
+159
|
||
| } | ||
|
|
||
| // Renderer uses this to decide whether the transparency toggle is available. | ||
|
|
@@ -345,19 +353,9 @@ app.whenReady().then(() => { | |
| app.dock.setIcon(path.join(__dirname, "../../build/icon.png")); | ||
| } | ||
|
|
||
| const shortcuts = ["CommandOrControl+Alt+I", "F12", "CommandOrControl+Shift+J"]; | ||
| for (const shortcut of shortcuts) { | ||
| const ok = globalShortcut.register(shortcut, () => { | ||
| log("DEVTOOLS", `Shortcut ${shortcut} triggered`); | ||
| openDevToolsWindow(); | ||
| }); | ||
| log("DEVTOOLS", `Register ${shortcut}: ${ok ? "OK" : "FAILED"}`); | ||
| } | ||
| }); | ||
|
|
||
| app.on("will-quit", (event) => { | ||
| globalShortcut.unregisterAll(); | ||
|
|
||
| // When an update is being installed, let the updater control the quit lifecycle. | ||
| // In that case, fire-and-forget PostHog shutdown and do not delay quit. | ||
| if (getIsInstallingUpdate()) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This listener is attached only to
mainWindow.webContents, so shortcuts are processed only when the main renderer has focus. In this app, the Browser panel uses an Electron<webview>(a separate guestWebContents), and key events in that guest do not route through the host page’sbefore-input-eventhandler; as a result,F12/CmdOrCtrl+Alt+I/CmdOrCtrl+Shift+Jstop working while focus is inside the webview, which regresses the prior in-app shortcut behavior. Consider also wiring the same shortcut handler to guest webcontents (e.g., viadid-attach-webview/web-contents-created).Useful? React with 👍 / 👎.