package main import ( "bufio" "fmt" "golang.org/x/term" "os" ) func isAscii(char rune) bool { ord := int(char) return ord < 0x80 } func isCtrl(char rune) bool { if ! isAscii(char) { return false } ord:= int(char) return ord == 0x7f || ord < 0x20 } // Put the terminal in func goRaw() *term.State { oldState, err := term.MakeRaw(int(os.Stdin.Fd())) if err != nil { panic(err) } return oldState } // Print a formatted string to stdout, with CRLF line endings for proper terminal formatting func outLn(format string, a ...interface{}) { formatted := fmt.Sprintf(format, a...) fmt.Printf("%s\r\n", formatted) } func main() { // Go to proper raw mode, but restore canonical mode at extit oldState := goRaw() defer term.Restore(int(os.Stdin.Fd()), oldState) reader := bufio.NewReader(os.Stdin) for { char, _, err := reader.ReadRune() if err != nil { panic(err) } // Ugliest syntax structure ever? switch { case char == 'q': outLn("bye!") return case isCtrl(char): outLn("%d", char) default: outLn("%d ('%c')", char, char) } } }