// SPDX-FileCopyrightText: (C) Copyright 2026 Pim van Pelt // SPDX-License-Identifier: Apache-2.0 // Package keypress lets a streaming command stop on any keystroke. A // watch-style command runs WaitForKey in a goroutine with a cancellable // context; the first key pressed cancels the context, tearing down the stream. // // ctx, cancel := context.WithCancel(ctx) // defer cancel() // go keypress.WaitForKey(ctx, cancel) // for { ev, err := stream.Recv(); ... } // returns when ctx is cancelled // // When standard input is not a terminal (piped, redirected, backgrounded) there // is no keystroke to wait for, so WaitForKey just blocks until ctx ends and // never cancels on its own. package keypress import ( "context" "os" ) // WaitForKey blocks until a key is pressed on standard input or ctx is done, // whichever comes first; on a keypress it calls cancel. If stdin is not a // terminal it waits on ctx only (it neither reads input nor calls cancel). The // terminal is placed in cbreak mode for the duration and restored on return. // // Run it in its own goroutine. If ctx ends first, WaitForKey returns and // restores the terminal; a read already blocked on stdin is left to be reaped // when the process exits (there is no portable way to interrupt it). func WaitForKey(ctx context.Context, cancel context.CancelFunc) { fd := int(os.Stdin.Fd()) old, err := cbreak(fd) if err != nil { <-ctx.Done() // stdin is not a tty: nothing to read, just honor ctx return } defer func() { _ = restore(fd, old) }() readDone := make(chan struct{}) go func() { defer close(readDone) buf := make([]byte, 1) _, _ = os.Stdin.Read(buf) }() select { case <-readDone: cancel() case <-ctx.Done(): } }