Files
gemini-cli/packages/cli/src/ui/utils/kittyProtocolDetector.ts
2025-08-13 17:32:54 +00:00

106 lines
2.8 KiB
TypeScript

/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
let detectionComplete = false;
let protocolSupported = false;
let protocolEnabled = false;
/**
* Detects Kitty keyboard protocol support.
* Definitive document about this protocol lives at https://sw.kovidgoyal.net/kitty/keyboard-protocol/
* This function should be called once at app startup.
*/
export async function detectAndEnableKittyProtocol(): Promise<boolean> {
if (detectionComplete) {
return protocolSupported;
}
return new Promise((resolve) => {
if (!process.stdin.isTTY || !process.stdout.isTTY) {
detectionComplete = true;
resolve(false);
return;
}
const originalRawMode = process.stdin.isRaw;
if (!originalRawMode) {
process.stdin.setRawMode(true);
}
let responseBuffer = '';
let progressiveEnhancementReceived = false;
let checkFinished = false;
const handleData = (data: Buffer) => {
responseBuffer += data.toString();
// Check for progressive enhancement response (CSI ? <flags> u)
if (responseBuffer.includes('\x1b[?') && responseBuffer.includes('u')) {
progressiveEnhancementReceived = true;
}
// Check for device attributes response (CSI ? <attrs> c)
if (responseBuffer.includes('\x1b[?') && responseBuffer.includes('c')) {
if (!checkFinished) {
checkFinished = true;
process.stdin.removeListener('data', handleData);
if (!originalRawMode) {
process.stdin.setRawMode(false);
}
if (progressiveEnhancementReceived) {
// Enable the protocol
process.stdout.write('\x1b[>1u');
protocolSupported = true;
protocolEnabled = true;
// Set up cleanup on exit
process.on('exit', disableProtocol);
process.on('SIGTERM', disableProtocol);
}
detectionComplete = true;
resolve(protocolSupported);
}
}
};
process.stdin.on('data', handleData);
// Send queries
process.stdout.write('\x1b[?u'); // Query progressive enhancement
process.stdout.write('\x1b[c'); // Query device attributes
// Timeout after 50ms
setTimeout(() => {
if (!checkFinished) {
process.stdin.removeListener('data', handleData);
if (!originalRawMode) {
process.stdin.setRawMode(false);
}
detectionComplete = true;
resolve(false);
}
}, 50);
});
}
function disableProtocol() {
if (protocolEnabled) {
process.stdout.write('\x1b[<u');
protocolEnabled = false;
}
}
export function isKittyProtocolEnabled(): boolean {
return protocolEnabled;
}
export function isKittyProtocolSupported(): boolean {
return protocolSupported;
}