1// +build !windows 2// This file contains a simple and incomplete implementation of the terminfo 3// database. Information was taken from the ncurses manpages term(5) and 4// terminfo(5). Currently, only the string capabilities for special keys and for 5// functions without parameters are actually used. Colors are still done with 6// ANSI escape sequences. Other special features that are not (yet?) supported 7// are reading from ~/.terminfo, the TERMINFO_DIRS variable, Berkeley database 8// format and extended capabilities. 9 10package termbox 11 12import ( 13 "bytes" 14 "encoding/binary" 15 "encoding/hex" 16 "errors" 17 "fmt" 18 "io/ioutil" 19 "os" 20 "strings" 21) 22 23const ( 24 ti_magic = 0432 25 ti_header_length = 12 26 ti_mouse_enter = "\x1b[?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h" 27 ti_mouse_leave = "\x1b[?1006l\x1b[?1015l\x1b[?1002l\x1b[?1000l" 28) 29 30func load_terminfo() ([]byte, error) { 31 var data []byte 32 var err error 33 34 term := os.Getenv("TERM") 35 if term == "" { 36 return nil, fmt.Errorf("termbox: TERM not set") 37 } 38 39 // The following behaviour follows the one described in terminfo(5) as 40 // distributed by ncurses. 41 42 terminfo := os.Getenv("TERMINFO") 43 if terminfo != "" { 44 // if TERMINFO is set, no other directory should be searched 45 return ti_try_path(terminfo) 46 } 47 48 // next, consider ~/.terminfo 49 home := os.Getenv("HOME") 50 if home != "" { 51 data, err = ti_try_path(home + "/.terminfo") 52 if err == nil { 53 return data, nil 54 } 55 } 56 57 // next, TERMINFO_DIRS 58 dirs := os.Getenv("TERMINFO_DIRS") 59 if dirs != "" { 60 for _, dir := range strings.Split(dirs, ":") { 61 if dir == "" { 62 // "" -> "/usr/share/terminfo" 63 dir = "/usr/share/terminfo" 64 } 65 data, err = ti_try_path(dir) 66 if err == nil { 67 return data, nil 68 } 69 } 70 } 71 72 // next, /lib/terminfo 73 data, err = ti_try_path("/lib/terminfo") 74 if err == nil { 75 return data, nil 76 } 77 78 // fall back to /usr/share/terminfo 79 return ti_try_path("/usr/share/terminfo") 80} 81 82func ti_try_path(path string) (data []byte, err error) { 83 // load_terminfo already made sure it is set 84 term := os.Getenv("TERM") 85 86 // first try, the typical *nix path 87 terminfo := path + "/" + term[0:1] + "/" + term 88 data, err = ioutil.ReadFile(terminfo) 89 if err == nil { 90 return 91 } 92 93 // fallback to darwin specific dirs structure 94 terminfo = path + "/" + hex.EncodeToString([]byte(term[:1])) + "/" + term 95 data, err = ioutil.ReadFile(terminfo) 96 return 97} 98 99func setup_term_builtin() error { 100 name := os.Getenv("TERM") 101 if name == "" { 102 return errors.New("termbox: TERM environment variable not set") 103 } 104 105 for _, t := range terms { 106 if t.name == name { 107 keys = t.keys 108 funcs = t.funcs 109 return nil 110 } 111 } 112 113 compat_table := []struct { 114 partial string 115 keys []string 116 funcs []string 117 }{ 118 {"xterm", xterm_keys, xterm_funcs}, 119 {"rxvt", rxvt_unicode_keys, rxvt_unicode_funcs}, 120 {"linux", linux_keys, linux_funcs}, 121 {"Eterm", eterm_keys, eterm_funcs}, 122 {"screen", screen_keys, screen_funcs}, 123 // let's assume that 'cygwin' is xterm compatible 124 {"cygwin", xterm_keys, xterm_funcs}, 125 {"st", xterm_keys, xterm_funcs}, 126 } 127 128 // try compatibility variants 129 for _, it := range compat_table { 130 if strings.Contains(name, it.partial) { 131 keys = it.keys 132 funcs = it.funcs 133 return nil 134 } 135 } 136 137 return errors.New("termbox: unsupported terminal") 138} 139 140func setup_term() (err error) { 141 var data []byte 142 var header [6]int16 143 var str_offset, table_offset int16 144 145 data, err = load_terminfo() 146 if err != nil { 147 return setup_term_builtin() 148 } 149 150 rd := bytes.NewReader(data) 151 // 0: magic number, 1: size of names section, 2: size of boolean section, 3: 152 // size of numbers section (in integers), 4: size of the strings section (in 153 // integers), 5: size of the string table 154 155 err = binary.Read(rd, binary.LittleEndian, header[:]) 156 if err != nil { 157 return 158 } 159 160 number_sec_len := int16(2) 161 if header[0] == 542 { // doc says it should be octal 0542, but what I see it terminfo files is 542, learn to program please... thank you.. 162 number_sec_len = 4 163 } 164 165 if (header[1]+header[2])%2 != 0 { 166 // old quirk to align everything on word boundaries 167 header[2] += 1 168 } 169 str_offset = ti_header_length + header[1] + header[2] + number_sec_len*header[3] 170 table_offset = str_offset + 2*header[4] 171 172 keys = make([]string, 0xFFFF-key_min) 173 for i, _ := range keys { 174 keys[i], err = ti_read_string(rd, str_offset+2*ti_keys[i], table_offset) 175 if err != nil { 176 return 177 } 178 } 179 funcs = make([]string, t_max_funcs) 180 // the last two entries are reserved for mouse. because the table offset is 181 // not there, the two entries have to fill in manually 182 for i, _ := range funcs[:len(funcs)-2] { 183 funcs[i], err = ti_read_string(rd, str_offset+2*ti_funcs[i], table_offset) 184 if err != nil { 185 return 186 } 187 } 188 funcs[t_max_funcs-2] = ti_mouse_enter 189 funcs[t_max_funcs-1] = ti_mouse_leave 190 return nil 191} 192 193func ti_read_string(rd *bytes.Reader, str_off, table int16) (string, error) { 194 var off int16 195 196 _, err := rd.Seek(int64(str_off), 0) 197 if err != nil { 198 return "", err 199 } 200 err = binary.Read(rd, binary.LittleEndian, &off) 201 if err != nil { 202 return "", err 203 } 204 _, err = rd.Seek(int64(table+off), 0) 205 if err != nil { 206 return "", err 207 } 208 var bs []byte 209 for { 210 b, err := rd.ReadByte() 211 if err != nil { 212 return "", err 213 } 214 if b == byte(0x00) { 215 break 216 } 217 bs = append(bs, b) 218 } 219 return string(bs), nil 220} 221 222// "Maps" the function constants from termbox.go to the number of the respective 223// string capability in the terminfo file. Taken from (ncurses) term.h. 224var ti_funcs = []int16{ 225 28, 40, 16, 13, 5, 39, 36, 27, 26, 34, 89, 88, 226} 227 228// Same as above for the special keys. 229var ti_keys = []int16{ 230 66, 68 /* apparently not a typo; 67 is F10 for whatever reason */, 69, 70, 231 71, 72, 73, 74, 75, 67, 216, 217, 77, 59, 76, 164, 82, 81, 87, 61, 79, 83, 232} 233