diff options
Diffstat (limited to 'packages/cli/src/ui/utils/kittyProtocolDetector.ts')
| -rw-r--r-- | packages/cli/src/ui/utils/kittyProtocolDetector.ts | 105 |
1 files changed, 105 insertions, 0 deletions
diff --git a/packages/cli/src/ui/utils/kittyProtocolDetector.ts b/packages/cli/src/ui/utils/kittyProtocolDetector.ts new file mode 100644 index 00000000..5d77943a --- /dev/null +++ b/packages/cli/src/ui/utils/kittyProtocolDetector.ts @@ -0,0 +1,105 @@ +/** + * @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; +} |
