1// +build !windows
2
3package termbox
4
5import "unicode/utf8"
6import "bytes"
7import "syscall"
8import "unsafe"
9import "strings"
10import "strconv"
11import "os"
12import "io"
13
14// private API
15
16const (
17 t_enter_ca = iota
18 t_exit_ca
19 t_show_cursor
20 t_hide_cursor
21 t_clear_screen
22 t_sgr0
23 t_underline
24 t_bold
25 t_blink
26 t_reverse
27 t_enter_keypad
28 t_exit_keypad
29 t_enter_mouse
30 t_exit_mouse
31 t_max_funcs
32)
33
34const (
35 coord_invalid = -2
36 attr_invalid = Attribute(0xFFFF)
37)
38
39type input_event struct {
40 data []byte
41 err error
42}
43
44type extract_event_res int
45
46const (
47 event_not_extracted extract_event_res = iota
48 event_extracted
49 esc_wait
50)
51
52var (
53 // term specific sequences
54 keys []string
55 funcs []string
56
57 // termbox inner state
58 orig_tios syscall_Termios
59 back_buffer cellbuf
60 front_buffer cellbuf
61 termw int
62 termh int
63 input_mode = InputEsc
64 output_mode = OutputNormal
65 out *os.File
66 in int
67 lastfg = attr_invalid
68 lastbg = attr_invalid
69 lastx = coord_invalid
70 lasty = coord_invalid
71 cursor_x = cursor_hidden
72 cursor_y = cursor_hidden
73 foreground = ColorDefault
74 background = ColorDefault
75 inbuf = make([]byte, 0, 64)
76 outbuf bytes.Buffer
77 sigwinch = make(chan os.Signal, 1)
78 sigio = make(chan os.Signal, 1)
79 quit = make(chan int)
80 input_comm = make(chan input_event)
81 interrupt_comm = make(chan struct{})
82 intbuf = make([]byte, 0, 16)
83
84 // grayscale indexes
85 grayscale = []Attribute{
86 0, 17, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244,
87 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 232,
88 }
89)
90
91func write_cursor(x, y int) {
92 outbuf.WriteString("\033[")
93 outbuf.Write(strconv.AppendUint(intbuf, uint64(y+1), 10))
94 outbuf.WriteString(";")
95 outbuf.Write(strconv.AppendUint(intbuf, uint64(x+1), 10))
96 outbuf.WriteString("H")
97}
98
99func write_sgr_fg(a Attribute) {
100 switch output_mode {
101 case Output256, Output216, OutputGrayscale:
102 outbuf.WriteString("\033[38;5;")
103 outbuf.Write(strconv.AppendUint(intbuf, uint64(a-1), 10))
104 outbuf.WriteString("m")
105 default:
106 outbuf.WriteString("\033[3")
107 outbuf.Write(strconv.AppendUint(intbuf, uint64(a-1), 10))
108 outbuf.WriteString("m")
109 }
110}
111
112func write_sgr_bg(a Attribute) {
113 switch output_mode {
114 case Output256, Output216, OutputGrayscale:
115 outbuf.WriteString("\033[48;5;")
116 outbuf.Write(strconv.AppendUint(intbuf, uint64(a-1), 10))
117 outbuf.WriteString("m")
118 default:
119 outbuf.WriteString("\033[4")
120 outbuf.Write(strconv.AppendUint(intbuf, uint64(a-1), 10))
121 outbuf.WriteString("m")
122 }
123}
124
125func write_sgr(fg, bg Attribute) {
126 switch output_mode {
127 case Output256, Output216, OutputGrayscale:
128 outbuf.WriteString("\033[38;5;")
129 outbuf.Write(strconv.AppendUint(intbuf, uint64(fg-1), 10))
130 outbuf.WriteString("m")
131 outbuf.WriteString("\033[48;5;")
132 outbuf.Write(strconv.AppendUint(intbuf, uint64(bg-1), 10))
133 outbuf.WriteString("m")
134 default:
135 outbuf.WriteString("\033[3")
136 outbuf.Write(strconv.AppendUint(intbuf, uint64(fg-1), 10))
137 outbuf.WriteString(";4")
138 outbuf.Write(strconv.AppendUint(intbuf, uint64(bg-1), 10))
139 outbuf.WriteString("m")
140 }
141}
142
143type winsize struct {
144 rows uint16
145 cols uint16
146 xpixels uint16
147 ypixels uint16
148}
149
150func get_term_size(fd uintptr) (int, int) {
151 var sz winsize
152 _, _, _ = syscall.Syscall(syscall.SYS_IOCTL,
153 fd, uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&sz)))
154 return int(sz.cols), int(sz.rows)
155}
156
157func send_attr(fg, bg Attribute) {
158 if fg == lastfg && bg == lastbg {
159 return
160 }
161
162 outbuf.WriteString(funcs[t_sgr0])
163
164 var fgcol, bgcol Attribute
165
166 switch output_mode {
167 case Output256:
168 fgcol = fg & 0x1FF
169 bgcol = bg & 0x1FF
170 case Output216:
171 fgcol = fg & 0xFF
172 bgcol = bg & 0xFF
173 if fgcol > 216 {
174 fgcol = ColorDefault
175 }
176 if bgcol > 216 {
177 bgcol = ColorDefault
178 }
179 if fgcol != ColorDefault {
180 fgcol += 0x10
181 }
182 if bgcol != ColorDefault {
183 bgcol += 0x10
184 }
185 case OutputGrayscale:
186 fgcol = fg & 0x1F
187 bgcol = bg & 0x1F
188 if fgcol > 26 {
189 fgcol = ColorDefault
190 }
191 if bgcol > 26 {
192 bgcol = ColorDefault
193 }
194 if fgcol != ColorDefault {
195 fgcol = grayscale[fgcol]
196 }
197 if bgcol != ColorDefault {
198 bgcol = grayscale[bgcol]
199 }
200 default:
201 fgcol = fg & 0x0F
202 bgcol = bg & 0x0F
203 }
204
205 if fgcol != ColorDefault {
206 if bgcol != ColorDefault {
207 write_sgr(fgcol, bgcol)
208 } else {
209 write_sgr_fg(fgcol)
210 }
211 } else if bgcol != ColorDefault {
212 write_sgr_bg(bgcol)
213 }
214
215 if fg&AttrBold != 0 {
216 outbuf.WriteString(funcs[t_bold])
217 }
218 if bg&AttrBold != 0 {
219 outbuf.WriteString(funcs[t_blink])
220 }
221 if fg&AttrUnderline != 0 {
222 outbuf.WriteString(funcs[t_underline])
223 }
224 if fg&AttrReverse|bg&AttrReverse != 0 {
225 outbuf.WriteString(funcs[t_reverse])
226 }
227
228 lastfg, lastbg = fg, bg
229}
230
231func send_char(x, y int, ch rune) {
232 var buf [8]byte
233 n := utf8.EncodeRune(buf[:], ch)
234 if x-1 != lastx || y != lasty {
235 write_cursor(x, y)
236 }
237 lastx, lasty = x, y
238 outbuf.Write(buf[:n])
239}
240
241func flush() error {
242 _, err := io.Copy(out, &outbuf)
243 outbuf.Reset()
244 return err
245}
246
247func send_clear() error {
248 send_attr(foreground, background)
249 outbuf.WriteString(funcs[t_clear_screen])
250 if !is_cursor_hidden(cursor_x, cursor_y) {
251 write_cursor(cursor_x, cursor_y)
252 }
253
254 // we need to invalidate cursor position too and these two vars are
255 // used only for simple cursor positioning optimization, cursor
256 // actually may be in the correct place, but we simply discard
257 // optimization once and it gives us simple solution for the case when
258 // cursor moved
259 lastx = coord_invalid
260 lasty = coord_invalid
261
262 return flush()
263}
264
265func update_size_maybe() error {
266 w, h := get_term_size(out.Fd())
267 if w != termw || h != termh {
268 termw, termh = w, h
269 back_buffer.resize(termw, termh)
270 front_buffer.resize(termw, termh)
271 front_buffer.clear()
272 return send_clear()
273 }
274 return nil
275}
276
277func tcsetattr(fd uintptr, termios *syscall_Termios) error {
278 r, _, e := syscall.Syscall(syscall.SYS_IOCTL,
279 fd, uintptr(syscall_TCSETS), uintptr(unsafe.Pointer(termios)))
280 if r != 0 {
281 return os.NewSyscallError("SYS_IOCTL", e)
282 }
283 return nil
284}
285
286func tcgetattr(fd uintptr, termios *syscall_Termios) error {
287 r, _, e := syscall.Syscall(syscall.SYS_IOCTL,
288 fd, uintptr(syscall_TCGETS), uintptr(unsafe.Pointer(termios)))
289 if r != 0 {
290 return os.NewSyscallError("SYS_IOCTL", e)
291 }
292 return nil
293}
294
295func parse_mouse_event(event *Event, buf string) (int, bool) {
296 if strings.HasPrefix(buf, "\033[M") && len(buf) >= 6 {
297 // X10 mouse encoding, the simplest one
298 // \033 [ M Cb Cx Cy
299 b := buf[3] - 32
300 switch b & 3 {
301 case 0:
302 if b&64 != 0 {
303 event.Key = MouseWheelUp
304 } else {
305 event.Key = MouseLeft
306 }
307 case 1:
308 if b&64 != 0 {
309 event.Key = MouseWheelDown
310 } else {
311 event.Key = MouseMiddle
312 }
313 case 2:
314 event.Key = MouseRight
315 case 3:
316 event.Key = MouseRelease
317 default:
318 return 6, false
319 }
320 event.Type = EventMouse // KeyEvent by default
321 if b&32 != 0 {
322 event.Mod |= ModMotion
323 }
324
325 // the coord is 1,1 for upper left
326 event.MouseX = int(buf[4]) - 1 - 32
327 event.MouseY = int(buf[5]) - 1 - 32
328 return 6, true
329 } else if strings.HasPrefix(buf, "\033[<") || strings.HasPrefix(buf, "\033[") {
330 // xterm 1006 extended mode or urxvt 1015 extended mode
331 // xterm: \033 [ < Cb ; Cx ; Cy (M or m)
332 // urxvt: \033 [ Cb ; Cx ; Cy M
333
334 // find the first M or m, that's where we stop
335 mi := strings.IndexAny(buf, "Mm")
336 if mi == -1 {
337 return 0, false
338 }
339
340 // whether it's a capital M or not
341 isM := buf[mi] == 'M'
342
343 // whether it's urxvt or not
344 isU := false
345
346 // buf[2] is safe here, because having M or m found means we have at
347 // least 3 bytes in a string
348 if buf[2] == '<' {
349 buf = buf[3:mi]
350 } else {
351 isU = true
352 buf = buf[2:mi]
353 }
354
355 s1 := strings.Index(buf, ";")
356 s2 := strings.LastIndex(buf, ";")
357 // not found or only one ';'
358 if s1 == -1 || s2 == -1 || s1 == s2 {
359 return 0, false
360 }
361
362 n1, err := strconv.ParseInt(buf[0:s1], 10, 64)
363 if err != nil {
364 return 0, false
365 }
366 n2, err := strconv.ParseInt(buf[s1+1:s2], 10, 64)
367 if err != nil {
368 return 0, false
369 }
370 n3, err := strconv.ParseInt(buf[s2+1:], 10, 64)
371 if err != nil {
372 return 0, false
373 }
374
375 // on urxvt, first number is encoded exactly as in X10, but we need to
376 // make it zero-based, on xterm it is zero-based already
377 if isU {
378 n1 -= 32
379 }
380 switch n1 & 3 {
381 case 0:
382 if n1&64 != 0 {
383 event.Key = MouseWheelUp
384 } else {
385 event.Key = MouseLeft
386 }
387 case 1:
388 if n1&64 != 0 {
389 event.Key = MouseWheelDown
390 } else {
391 event.Key = MouseMiddle
392 }
393 case 2:
394 event.Key = MouseRight
395 case 3:
396 event.Key = MouseRelease
397 default:
398 return mi + 1, false
399 }
400 if !isM {
401 // on xterm mouse release is signaled by lowercase m
402 event.Key = MouseRelease
403 }
404
405 event.Type = EventMouse // KeyEvent by default
406 if n1&32 != 0 {
407 event.Mod |= ModMotion
408 }
409
410 event.MouseX = int(n2) - 1
411 event.MouseY = int(n3) - 1
412 return mi + 1, true
413 }
414
415 return 0, false
416}
417
418func parse_escape_sequence(event *Event, buf []byte) (int, bool) {
419 bufstr := string(buf)
420 for i, key := range keys {
421 if strings.HasPrefix(bufstr, key) {
422 event.Ch = 0
423 event.Key = Key(0xFFFF - i)
424 return len(key), true
425 }
426 }
427
428 // if none of the keys match, let's try mouse sequences
429 return parse_mouse_event(event, bufstr)
430}
431
432func extract_raw_event(data []byte, event *Event) bool {
433 if len(inbuf) == 0 {
434 return false
435 }
436
437 n := len(data)
438 if n == 0 {
439 return false
440 }
441
442 n = copy(data, inbuf)
443 copy(inbuf, inbuf[n:])
444 inbuf = inbuf[:len(inbuf)-n]
445
446 event.N = n
447 event.Type = EventRaw
448 return true
449}
450
451func extract_event(inbuf []byte, event *Event, allow_esc_wait bool) extract_event_res {
452 if len(inbuf) == 0 {
453 event.N = 0
454 return event_not_extracted
455 }
456
457 if inbuf[0] == '\033' {
458 // possible escape sequence
459 if n, ok := parse_escape_sequence(event, inbuf); n != 0 {
460 event.N = n
461 if ok {
462 return event_extracted
463 } else {
464 return event_not_extracted
465 }
466 }
467
468 // possible partially read escape sequence; trigger a wait if appropriate
469 if enable_wait_for_escape_sequence() && allow_esc_wait {
470 event.N = 0
471 return esc_wait
472 }
473
474 // it's not escape sequence, then it's Alt or Esc, check input_mode
475 switch {
476 case input_mode&InputEsc != 0:
477 // if we're in escape mode, fill Esc event, pop buffer, return success
478 event.Ch = 0
479 event.Key = KeyEsc
480 event.Mod = 0
481 event.N = 1
482 return event_extracted
483 case input_mode&InputAlt != 0:
484 // if we're in alt mode, set Alt modifier to event and redo parsing
485 event.Mod = ModAlt
486 status := extract_event(inbuf[1:], event, false)
487 if status == event_extracted {
488 event.N++
489 } else {
490 event.N = 0
491 }
492 return status
493 default:
494 panic("unreachable")
495 }
496 }
497
498 // if we're here, this is not an escape sequence and not an alt sequence
499 // so, it's a FUNCTIONAL KEY or a UNICODE character
500
501 // first of all check if it's a functional key
502 if Key(inbuf[0]) <= KeySpace || Key(inbuf[0]) == KeyBackspace2 {
503 // fill event, pop buffer, return success
504 event.Ch = 0
505 event.Key = Key(inbuf[0])
506 event.N = 1
507 return event_extracted
508 }
509
510 // the only possible option is utf8 rune
511 if r, n := utf8.DecodeRune(inbuf); r != utf8.RuneError {
512 event.Ch = r
513 event.Key = 0
514 event.N = n
515 return event_extracted
516 }
517
518 return event_not_extracted
519}
520
521func fcntl(fd int, cmd int, arg int) (val int, err error) {
522 r, _, e := syscall.Syscall(syscall.SYS_FCNTL, uintptr(fd), uintptr(cmd),
523 uintptr(arg))
524 val = int(r)
525 if e != 0 {
526 err = e
527 }
528 return
529}