aboutsummaryrefslogblamecommitdiffstats
path: root/vendor/github.com/nsf/termbox-go/termbox.go
blob: c2d86c6584e34a19b25792c4abd68b17b5c1f280 (plain) (tree)









































































                                                





                                                                                  


































































































                                                                              
                               

                                            
                               


                                            
                                                

                                          
                                                












































                                                      
                  

















































                                                                              





                                                              
                       




                                                        
                       




                                                          


                                              

                                                


                                                              

                                              
                 
 



                                                   



















































































                                                                                       

         




                                                                  






                                                   


                                                              




























                                                        
                                                                         



























































                                                                                              
// +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()
    return err
}

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
}