package main import ( "bytes" "os" "time" "unsafe" "golang.org/x/sys/unix" "golang.org/x/term" ) type dsrResult struct { row int column int extraBytes []byte } func main() { st, err := term.MakeRaw(0) if err != nil { os.Exit(1) } var res dsrResult select { case <-time.After(100 * time.Millisecond): case res = <-awaitDSR(): } term.Restore(0, st) if res.column > 1 { unix.Write(1, []byte("🚫\n")) // U+1F6AB } term, _ := unix.IoctlGetTermios(1, unix.TCGETS) if term != nil { term.Lflag = term.Lflag &^ unix.ECHO unix.IoctlSetTermios(1, unix.TCSETS, term) } for _, b := range res.extraBytes { unix.Syscall(unix.SYS_IOCTL, 0, unix.TIOCSTI, uintptr(unsafe.Pointer(&b))) } if term != nil { term.Lflag = term.Lflag | unix.ECHO unix.IoctlSetTermios(1, unix.TCSETS, term) } } func awaitDSR() <-chan dsrResult { ch := make(chan dsrResult) go func() { row, col, extra := readDSR() ch <- dsrResult{ row: row, column: col, extraBytes: extra, } }() return ch } func readDSR() (row, col int, extra []byte) { unix.Write(1, []byte{ '\033', '[', // ANSI escape code: CSI '6', 'n', // DSR / device status report }) inputBuf := bytes.NewBuffer(make([]byte, 0, 128)) termIn := make([]byte, 64) for { // read some bytes from stdin and append to our input buffer termIn = termIn[:64] n, err := unix.Read(0, termIn) inputBuf.Write(termIn[:n]) if err != nil { return 0, 0, inputBuf.Bytes() } // does the input buffer contain a valid DSR result sequence? b := inputBuf.Bytes() row, col, pos, count, ok := parseDSR(b) if ok { // replay the non-DSR sequence bytes return row, col, append(b[:pos], b[pos+count:]...) } } } func parseDSR(input []byte) (row, col, pos, count int, ok bool) { // locate the start of the result pos = bytes.Index(input, []byte{'\033', '['}) if pos == -1 { return } // parse the row and column numbers, separated by ';' p := pos + 2 rowLoop: for { if p == len(input) { return } switch input[p] { case ';': break rowLoop case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': row *= 10 row += int(input[p] - '0') default: return } p++ } p++ for { if p == len(input) { return } switch input[p] { case 'R': count = p - pos + 1 ok = true return case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': col *= 10 col += int(input[p] - '0') default: return } p++ } }