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