fix: forward Android hardware keyboard keys (#1114)

This commit is contained in:
ZacharyZcR
2026-07-28 01:48:35 +08:00
committed by GitHub
parent 8743e6daa2
commit 066d7e77b3
3 changed files with 99 additions and 0 deletions
+19
View File
@@ -47,6 +47,7 @@ import { globalShortcutHandler } from "@/lib/global-shortcut-handler";
import { useCommandTracker } from "@/features/terminal/command-history/useCommandTracker.ts"; import { useCommandTracker } from "@/features/terminal/command-history/useCommandTracker.ts";
import { highlightTerminalOutput } from "@/lib/terminal-syntax-highlighter.ts"; import { highlightTerminalOutput } from "@/lib/terminal-syntax-highlighter.ts";
import { useCommandHistory } from "@/features/terminal/command-history/CommandHistoryContext.tsx"; import { useCommandHistory } from "@/features/terminal/command-history/CommandHistoryContext.tsx";
import { getAndroidHardwareKeySequence } from "@/features/terminal/android-hardware-keyboard.ts";
import { CommandAutocomplete } from "./command-history/CommandAutocomplete.tsx"; import { CommandAutocomplete } from "./command-history/CommandAutocomplete.tsx";
import { SimpleLoader } from "@/lib/SimpleLoader.tsx"; import { SimpleLoader } from "@/lib/SimpleLoader.tsx";
import { useConfirmation } from "@/hooks/use-confirmation.ts"; import { useConfirmation } from "@/hooks/use-confirmation.ts";
@@ -2479,6 +2480,24 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
} }
} }
if (navigator.userAgent.includes("Android")) {
const sequence = getAndroidHardwareKeySequence(
e,
terminal.modes.applicationCursorKeysMode,
hostConfig.terminalConfig?.backspaceMode,
);
if (sequence) {
e.preventDefault();
e.stopPropagation();
if (webSocketRef.current?.readyState === WebSocket.OPEN) {
webSocketRef.current.send(
JSON.stringify({ type: "input", data: sequence }),
);
}
return false;
}
}
// Forward global app shortcuts to AppShell directly — xterm swallows // Forward global app shortcuts to AppShell directly — xterm swallows
// all keydown events and synthetic re-dispatch is unreliable. // all keydown events and synthetic re-dispatch is unreliable.
// stopPropagation prevents the same event from also firing the window listener. // stopPropagation prevents the same event from also firing the window listener.
@@ -0,0 +1,30 @@
import type { HostBackspaceMode } from "@/sidebar/HostEditorData";
const CURSOR_SEQUENCES: Record<string, [normal: string, application: string]> =
{
ArrowUp: ["\x1b[A", "\x1bOA"],
ArrowDown: ["\x1b[B", "\x1bOB"],
ArrowRight: ["\x1b[C", "\x1bOC"],
ArrowLeft: ["\x1b[D", "\x1bOD"],
};
export function getAndroidHardwareKeySequence(
event: Pick<
KeyboardEvent,
"key" | "ctrlKey" | "altKey" | "metaKey" | "shiftKey"
>,
applicationCursorKeys: boolean,
backspaceMode: HostBackspaceMode | undefined,
): string | null {
if (event.ctrlKey || event.altKey || event.metaKey || event.shiftKey) {
return null;
}
const cursor = CURSOR_SEQUENCES[event.key];
if (cursor) return cursor[applicationCursorKeys ? 1 : 0];
if (event.key === "Delete") return "\x1b[3~";
if (event.key === "Backspace" && backspaceMode !== "control-h") {
return "\x7f";
}
return null;
}
@@ -0,0 +1,50 @@
import { describe, expect, it } from "vitest";
import { getAndroidHardwareKeySequence } from "@/features/terminal/android-hardware-keyboard";
const key = (
value: string,
modifiers: Partial<KeyboardEvent> = {},
): Pick<
KeyboardEvent,
"key" | "ctrlKey" | "altKey" | "metaKey" | "shiftKey"
> => ({
key: value,
ctrlKey: false,
altKey: false,
metaKey: false,
shiftKey: false,
...modifiers,
});
describe("getAndroidHardwareKeySequence", () => {
it("maps cursor keys in normal and application modes", () => {
expect(
getAndroidHardwareKeySequence(key("ArrowUp"), false, undefined),
).toBe("\x1b[A");
expect(getAndroidHardwareKeySequence(key("ArrowUp"), true, undefined)).toBe(
"\x1bOA",
);
});
it("maps Delete and the default Backspace mode", () => {
expect(getAndroidHardwareKeySequence(key("Delete"), false, undefined)).toBe(
"\x1b[3~",
);
expect(
getAndroidHardwareKeySequence(key("Backspace"), false, undefined),
).toBe("\x7f");
});
it("leaves control-h Backspace and modified keys to existing handlers", () => {
expect(
getAndroidHardwareKeySequence(key("Backspace"), false, "control-h"),
).toBeNull();
expect(
getAndroidHardwareKeySequence(
key("ArrowLeft", { ctrlKey: true }),
false,
undefined,
),
).toBeNull();
});
});