From 8ade3e7ee26c1dedd105a20fa769c68e4103b75d Mon Sep 17 00:00:00 2001 From: Keith Lyons Date: Thu, 17 Jul 2025 20:45:42 -0400 Subject: feat(ui): hide cursor when terminal is unfocused (#4012) --- packages/cli/src/ui/hooks/useFocus.ts | 48 +++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 packages/cli/src/ui/hooks/useFocus.ts (limited to 'packages/cli/src/ui/hooks/useFocus.ts') diff --git a/packages/cli/src/ui/hooks/useFocus.ts b/packages/cli/src/ui/hooks/useFocus.ts new file mode 100644 index 00000000..6c9a6daa --- /dev/null +++ b/packages/cli/src/ui/hooks/useFocus.ts @@ -0,0 +1,48 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { useStdin, useStdout } from 'ink'; +import { useEffect, useState } from 'react'; + +// ANSI escape codes to enable/disable terminal focus reporting +const ENABLE_FOCUS_REPORTING = '\x1b[?1004h'; +const DISABLE_FOCUS_REPORTING = '\x1b[?1004l'; + +// ANSI escape codes for focus events +const FOCUS_IN = '\x1b[I'; +const FOCUS_OUT = '\x1b[O'; + +export const useFocus = () => { + const { stdin } = useStdin(); + const { stdout } = useStdout(); + const [isFocused, setIsFocused] = useState(true); + + useEffect(() => { + const handleData = (data: Buffer) => { + const sequence = data.toString(); + const lastFocusIn = sequence.lastIndexOf(FOCUS_IN); + const lastFocusOut = sequence.lastIndexOf(FOCUS_OUT); + + if (lastFocusIn > lastFocusOut) { + setIsFocused(true); + } else if (lastFocusOut > lastFocusIn) { + setIsFocused(false); + } + }; + + // Enable focus reporting + stdout?.write(ENABLE_FOCUS_REPORTING); + stdin?.on('data', handleData); + + return () => { + // Disable focus reporting on cleanup + stdout?.write(DISABLE_FOCUS_REPORTING); + stdin?.removeListener('data', handleData); + }; + }, [stdin, stdout]); + + return isFocused; +}; -- cgit v1.2.3