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