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
























                                                                                

                                                                         




















































































































































                                                                                          

                                             










































                                                                                  
// +build !windows
// This file contains a simple and incomplete implementation of the terminfo
// database. Information was taken from the ncurses manpages term(5) and
// terminfo(5). Currently, only the string capabilities for special keys and for
// functions without parameters are actually used. Colors are still done with
// ANSI escape sequences. Other special features that are not (yet?) supported
// are reading from ~/.terminfo, the TERMINFO_DIRS variable, Berkeley database
// format and extended capabilities.

package termbox

import (
    "bytes"
    "encoding/binary"
    "encoding/hex"
    "errors"
    "fmt"
    "io/ioutil"
    "os"
    "strings"
)

const (
    ti_magic         = 0432
    ti_header_length = 12
    ti_mouse_enter   = "\x1b[?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h"
    ti_mouse_leave   = "\x1b[?1006l\x1b[?1015l\x1b[?1002l\x1b[?1000l"
)

func load_terminfo() ([]byte, error) {
    var data []byte
    var err error

    term := os.Getenv("TERM")
    if term == "" {
        return nil, fmt.Errorf("termbox: TERM not set")
    }

    // The following behaviour follows the one described in terminfo(5) as
    // distributed by ncurses.

    terminfo := os.Getenv("TERMINFO")
    if terminfo != "" {
        // if TERMINFO is set, no other directory should be searched
        return ti_try_path(terminfo)
    }

    // next, consider ~/.terminfo
    home := os.Getenv("HOME")
    if home != "" {
        data, err = ti_try_path(home + "/.terminfo")
        if err == nil {
            return data, nil
        }
    }

    // next, TERMINFO_DIRS
    dirs := os.Getenv("TERMINFO_DIRS")
    if dirs != "" {
        for _, dir := range strings.Split(dirs, ":") {
            if dir == "" {
                // "" -> "/usr/share/terminfo"
                dir = "/usr/share/terminfo"
            }
            data, err = ti_try_path(dir)
            if err == nil {
                return data, nil
            }
        }
    }

    // fall back to /usr/share/terminfo
    return ti_try_path("/usr/share/terminfo")
}

func ti_try_path(path string) (data []byte, err error) {
    // load_terminfo already made sure it is set
    term := os.Getenv("TERM")

    // first try, the typical *nix path
    terminfo := path + "/" + term[0:1] + "/" + term
    data, err = ioutil.ReadFile(terminfo)
    if err == nil {
        return
    }

    // fallback to darwin specific dirs structure
    terminfo = path + "/" + hex.EncodeToString([]byte(term[:1])) + "/" + term
    data, err = ioutil.ReadFile(terminfo)
    return
}

func setup_term_builtin() error {
    name := os.Getenv("TERM")
    if name == "" {
        return errors.New("termbox: TERM environment variable not set")
    }

    for _, t := range terms {
        if t.name == name {
            keys = t.keys
            funcs = t.funcs
            return nil
        }
    }

    compat_table := []struct {
        partial string
        keys    []string
        funcs   []string
    }{
        {"xterm", xterm_keys, xterm_funcs},
        {"rxvt", rxvt_unicode_keys, rxvt_unicode_funcs},
        {"linux", linux_keys, linux_funcs},
        {"Eterm", eterm_keys, eterm_funcs},
        {"screen", screen_keys, screen_funcs},
        // let's assume that 'cygwin' is xterm compatible
        {"cygwin", xterm_keys, xterm_funcs},
        {"st", xterm_keys, xterm_funcs},
    }

    // try compatibility variants
    for _, it := range compat_table {
        if strings.Contains(name, it.partial) {
            keys = it.keys
            funcs = it.funcs
            return nil
        }
    }

    return errors.New("termbox: unsupported terminal")
}

func setup_term() (err error) {
    var data []byte
    var header [6]int16
    var str_offset, table_offset int16

    data, err = load_terminfo()
    if err != nil {
        return setup_term_builtin()
    }

    rd := bytes.NewReader(data)
    // 0: magic number, 1: size of names section, 2: size of boolean section, 3:
    // size of numbers section (in integers), 4: size of the strings section (in
    // integers), 5: size of the string table

    err = binary.Read(rd, binary.LittleEndian, header[:])
    if err != nil {
        return
    }

    if (header[1]+header[2])%2 != 0 {
        // old quirk to align everything on word boundaries
        header[2] += 1
    }
    str_offset = ti_header_length + header[1] + header[2] + 2*header[3]
    table_offset = str_offset + 2*header[4]

    keys = make([]string, 0xFFFF-key_min)
    for i, _ := range keys {
        keys[i], err = ti_read_string(rd, str_offset+2*ti_keys[i], table_offset)
        if err != nil {
            return
        }
    }
    funcs = make([]string, t_max_funcs)
    // the last two entries are reserved for mouse. because the table offset is
    // not there, the two entries have to fill in manually
    for i, _ := range funcs[:len(funcs)-2] {
        funcs[i], err = ti_read_string(rd, str_offset+2*ti_funcs[i], table_offset)
        if err != nil {
            return
        }
    }
    funcs[t_max_funcs-2] = ti_mouse_enter
    funcs[t_max_funcs-1] = ti_mouse_leave
    return nil
}

func ti_read_string(rd *bytes.Reader, str_off, table int16) (string, error) {
    var off int16

    _, err := rd.Seek(int64(str_off), 0)
    if err != nil {
        return "", err
    }
    err = binary.Read(rd, binary.LittleEndian, &off)
    if err != nil {
        return "", err
    }
    _, err = rd.Seek(int64(table+off), 0)
    if err != nil {
        return "", err
    }
    var bs []byte
    for {
        b, err := rd.ReadByte()
        if err != nil {
            return "", err
        }
        if b == byte(0x00) {
            break
        }
        bs = append(bs, b)
    }
    return string(bs), nil
}

// "Maps" the function constants from termbox.go to the number of the respective
// string capability in the terminfo file. Taken from (ncurses) term.h.
var ti_funcs = []int16{
    28, 40, 16, 13, 5, 39, 36, 27, 26, 34, 89, 88,
}

// Same as above for the special keys.
var ti_keys = []int16{
    66, 68 /* apparently not a typo; 67 is F10 for whatever reason */, 69, 70,
    71, 72, 73, 74, 75, 67, 216, 217, 77, 59, 76, 164, 82, 81, 87, 61, 79, 83,
}