mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-08-29 18:31:33 +00:00
fix: forward Android hardware keyboard keys (#1114)
This commit is contained in:
@@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user