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}