// +build !windows package termbox import "unicode/utf8" import "bytes" import "syscall" import "unsafe" import "strings" import "strconv" import "os" import "io" // private API const ( t_enter_ca = iota t_exit_ca t_show_cursor t_hide_cursor t_clear_screen t_sgr0 t_underline t_bold t_blink t_reverse t_enter_keypad t_exit_keypad t_enter_mouse t_exit_mouse t_max_funcs ) const ( coord_invalid = -2 attr_invalid = Attribute(0xFFFF) ) type input_event struct { data []byte err error } var ( // term specific sequences keys []string funcs []string // termbox inner state orig_tios syscall_Termios back_buffer cellbuf front_buffer cellbuf termw int termh int input_mode = InputEsc output_mode = OutputNormal out *os.File in int lastfg = attr_invalid lastbg = attr_invalid lastx = coord_invalid lasty = coord_invalid cursor_x = cursor_hidden cursor_y = cursor_hidden foreground = ColorDefault background = ColorDefault inbuf = make([]byte, 0, 64) outbuf bytes.Buffer sigwinch = make(chan os.Signal, 1) sigio = make(chan os.Signal, 1) quit = make(chan int) input_comm = make(chan input_event) interrupt_comm = make(chan struct{}) intbuf = make([]byte, 0, 16) // grayscale indexes grayscale = []Attribute{ 0, 17, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 232, } ) func write_cursor(x, y int) { outbuf.WriteString("\033[") outbuf.Write(strconv.AppendUint(intbuf, uint64(y+1), 10)) outbuf.WriteString(";") outbuf.Write(strconv.AppendUint(intbuf, uint64(x+1), 10)) outbuf.WriteString("H") } func write_sgr_fg(a Attribute) { switch output_mode { case Output256, Output216, OutputGrayscale: outbuf.WriteString("\033[38;5;") outbuf.Write(strconv.AppendUint(intbuf, uint64(a-1), 10)) outbuf.WriteString("m") default: outbuf.WriteString("\033[3") outbuf.Write(strconv.AppendUint(intbuf, uint64(a-1), 10)) outbuf.WriteString("m") } } func write_sgr_bg(a Attribute) { switch output_mode { case Output256, Output216, OutputGrayscale: outbuf.WriteString("\033[48;5;") outbuf.Write(strconv.AppendUint(intbuf, uint64(a-1), 10)) outbuf.WriteString("m") default: outbuf.WriteString("\033[4") outbuf.Write(strconv.AppendUint(intbuf, uint64(a-1), 10)) outbuf.WriteString("m") } } func write_sgr(fg, bg Attribute) { switch output_mode { case Output256, Output216, OutputGrayscale: outbuf.WriteString("\033[38;5;") outbuf.Write(strconv.AppendUint(intbuf, uint64(fg-1), 10)) outbuf.WriteString("m") outbuf.WriteString("\033[48;5;") outbuf.Write(strconv.AppendUint(intbuf, uint64(bg-1), 10)) outbuf.WriteString("m") default: outbuf.WriteString("\033[3") outbuf.Write(strconv.AppendUint(intbuf, uint64(fg-1), 10)) outbuf.WriteString(";4") outbuf.Write(strconv.AppendUint(intbuf, uint64(bg-1), 10)) outbuf.WriteString("m") } } type winsize struct { rows uint16 cols uint16 xpixels uint16 ypixels uint16 } func get_term_size(fd uintptr) (int, int) { var sz winsize _, _, _ = syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&sz))) return int(sz.cols), int(sz.rows) } func send_attr(fg, bg Attribute) { if fg == lastfg && bg == lastbg { return } outbuf.WriteString(funcs[t_sgr0]) var fgcol, bgcol Attribute switch output_mode { case Output256: fgcol = fg & 0x1FF bgcol = bg & 0x1FF case Output216: fgcol = fg & 0xFF bgcol = bg & 0xFF if fgcol > 216 { fgcol = ColorDefault } if bgcol > 216 { bgcol = ColorDefault } if fgcol != ColorDefault { fgcol += 0x10 } if bgcol != ColorDefault { bgcol += 0x10 } case OutputGrayscale: fgcol = fg & 0x1F bgcol = bg & 0x1F if fgcol > 26 { fgcol = ColorDefault } if bgcol > 26 { bgcol = ColorDefault } if fgcol != ColorDefault { fgcol = grayscale[fgcol] } if bgcol != ColorDefault { bgcol = grayscale[bgcol] } default: fgcol = fg & 0x0F bgcol = bg & 0x0F } if fgcol != ColorDefault { if bgcol != ColorDefault { write_sgr(fgcol, bgcol) } else { write_sgr_fg(fgcol) } } else if bgcol != ColorDefault { write_sgr_bg(bgcol) } if fg&AttrBold != 0 { outbuf.WriteString(funcs[t_bold]) } if bg&AttrBold != 0 { outbuf.WriteString(funcs[t_blink]) } if fg&AttrUnderline != 0 { outbuf.WriteString(funcs[t_underline]) } if fg&AttrReverse|bg&AttrReverse != 0 { outbuf.WriteString(funcs[t_reverse]) } lastfg, lastbg = fg, bg } func send_char(x, y int, ch rune) { var buf [8]byte n := utf8.EncodeRune(buf[:], ch) if x-1 != lastx || y != lasty { write_cursor(x, y) } lastx, lasty = x, y outbuf.Write(buf[:n]) } func flush() error { _, err := io.Copy(out, &outbuf) outbuf.Reset() if err != nil { return err } return nil } func send_clear() error { send_attr(foreground, background) outbuf.WriteString(funcs[t_clear_screen]) if !is_cursor_hidden(cursor_x, cursor_y) { write_cursor(cursor_x, cursor_y) } // we need to invalidate cursor position too and these two vars are // used only for simple cursor positioning optimization, cursor // actually may be in the correct place, but we simply discard // optimization once and it gives us simple solution for the case when // cursor moved lastx = coord_invalid lasty = coord_invalid return flush() } func update_size_maybe() error { w, h := get_term_size(out.Fd()) if w != termw || h != termh { termw, termh = w, h back_buffer.resize(termw, termh) front_buffer.resize(termw, termh) front_buffer.clear() return send_clear() } return nil } func tcsetattr(fd uintptr, termios *syscall_Termios) error { r, _, e := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall_TCSETS), uintptr(unsafe.Pointer(termios))) if r != 0 { return os.NewSyscallError("SYS_IOCTL", e) } return nil } func tcgetattr(fd uintptr, termios *syscall_Termios) error { r, _, e := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall_TCGETS), uintptr(unsafe.Pointer(termios))) if r != 0 { return os.NewSyscallError("SYS_IOCTL", e) } return nil } func parse_mouse_event(event *Event, buf string) (int, bool) { if strings.HasPrefix(buf, "\033[M") && len(buf) >= 6 { // X10 mouse encoding, the simplest one // \033 [ M Cb Cx Cy b := buf[3] - 32 switch b & 3 { case 0: if b&64 != 0 { event.Key = MouseWheelUp } else { event.Key = MouseLeft } case 1: if b&64 != 0 { event.Key = MouseWheelDown } else { event.Key = MouseMiddle } case 2: event.Key = MouseRight case 3: event.Key = MouseRelease default: return 6, false } event.Type = EventMouse // KeyEvent by default if b&32 != 0 { event.Mod |= ModMotion } // the coord is 1,1 for upper left event.MouseX = int(buf[4]) - 1 - 32 event.MouseY = int(buf[5]) - 1 - 32 return 6, true } else if strings.HasPrefix(buf, "\033[<") || strings.HasPrefix(buf, "\033[") { // xterm 1006 extended mode or urxvt 1015 extended mode // xterm: \033 [ < Cb ; Cx ; Cy (M or m) // urxvt: \033 [ Cb ; Cx ; Cy M // find the first M or m, that's where we stop mi := strings.IndexAny(buf, "Mm") if mi == -1 { return 0, false } // whether it's a capital M or not isM := buf[mi] == 'M' // whether it's urxvt or not isU := false // buf[2] is safe here, because having M or m found means we have at // least 3 bytes in a string if buf[2] == '<' { buf = buf[3:mi] } else { isU = true buf = buf[2:mi] } s1 := strings.Index(buf, ";") s2 := strings.LastIndex(buf, ";") // not found or only one ';' if s1 == -1 || s2 == -1 || s1 == s2 { return 0, false } n1, err := strconv.ParseInt(buf[0:s1], 10, 64) if err != nil { return 0, false } n2, err := strconv.ParseInt(buf[s1+1:s2], 10, 64) if err != nil { return 0, false } n3, err := strconv.ParseInt(buf[s2+1:], 10, 64) if err != nil { return 0, false } // on urxvt, first number is encoded exactly as in X10, but we need to // make it zero-based, on xterm it is zero-based already if isU { n1 -= 32 } switch n1 & 3 { case 0: if n1&64 != 0 { event.Key = MouseWheelUp } else { event.Key = MouseLeft } case 1: if n1&64 != 0 { event.Key = MouseWheelDown } else { event.Key = MouseMiddle } case 2: event.Key = MouseRight case 3: event.Key = MouseRelease default: return mi + 1, false } if !isM { // on xterm mouse release is signaled by lowercase m event.Key = MouseRelease } event.Type = EventMouse // KeyEvent by default if n1&32 != 0 { event.Mod |= ModMotion } event.MouseX = int(n2) - 1 event.MouseY = int(n3) - 1 return mi + 1, true } return 0, false } func parse_escape_sequence(event *Event, buf []byte) (int, bool) { bufstr := string(buf) for i, key := range keys { if strings.HasPrefix(bufstr, key) { event.Ch = 0 event.Key = Key(0xFFFF - i) return len(key), true } } // if none of the keys match, let's try mouse seqences return parse_mouse_event(event, bufstr) } func extract_raw_event(data []byte, event *Event) bool { if len(inbuf) == 0 { return false } n := len(data) if n == 0 { return false } n = copy(data, inbuf) copy(inbuf, inbuf[n:]) inbuf = inbuf[:len(inbuf)-n] event.N = n event.Type = EventRaw return true } func extract_event(inbuf []byte, event *Event) bool { if len(inbuf) == 0 { event.N = 0 return false } if inbuf[0] == '\033' { // possible escape sequence if n, ok := parse_escape_sequence(event, inbuf); n != 0 { event.N = n return ok } // it's not escape sequence, then it's Alt or Esc, check input_mode switch { case input_mode&InputEsc != 0: // if we're in escape mode, fill Esc event, pop buffer, return success event.Ch = 0 event.Key = KeyEsc event.Mod = 0 event.N = 1 return true case input_mode&InputAlt != 0: // if we're in alt mode, set Alt modifier to event and redo parsing event.Mod = ModAlt ok := extract_event(inbuf[1:], event) if ok { event.N++ } else { event.N = 0 } return ok default: panic("unreachable") } } // if we're here, this is not an escape sequence and not an alt sequence // so, it's a FUNCTIONAL KEY or a UNICODE character // first of all check if it's a functional key if Key(inbuf[0]) <= KeySpace || Key(inbuf[0]) == KeyBackspace2 { // fill event, pop buffer, return success event.Ch = 0 event.Key = Key(inbuf[0]) event.N = 1 return true } // the only possible option is utf8 rune if r, n := utf8.DecodeRune(inbuf); r != utf8.RuneError { event.Ch = r event.Key = 0 event.N = n return true } return false } func fcntl(fd int, cmd int, arg int) (val int, err error) { r, _, e := syscall.Syscall(syscall.SYS_FCNTL, uintptr(fd), uintptr(cmd), uintptr(arg)) val = int(r) if e != 0 { err = e } return }