1// +build !windows 2 3package termbox 4 5import ( 6 "fmt" 7 "os" 8 "os/signal" 9 "runtime" 10 "syscall" 11 "time" 12 13 "github.com/mattn/go-runewidth" 14) 15 16// public API 17 18// Initializes termbox library. This function should be called before any other functions. 19// After successful initialization, the library must be finalized using 'Close' function. 20// 21// Example usage: 22// err := termbox.Init() 23// if err != nil { 24// panic(err) 25// } 26// defer termbox.Close() 27func Init() error { 28 if IsInit { 29 return nil 30 } 31 32 var err error 33 34 if runtime.GOOS == "openbsd" || runtime.GOOS == "freebsd" { 35 out, err = os.OpenFile("/dev/tty", os.O_RDWR, 0) 36 if err != nil { 37 return err 38 } 39 in = int(out.Fd()) 40 } else { 41 out, err = os.OpenFile("/dev/tty", os.O_WRONLY, 0) 42 if err != nil { 43 return err 44 } 45 in, err = syscall.Open("/dev/tty", syscall.O_RDONLY, 0) 46 if err != nil { 47 return err 48 } 49 } 50 51 err = setup_term() 52 if err != nil { 53 return fmt.Errorf("termbox: error while reading terminfo data: %v", err) 54 } 55 56 signal.Notify(sigwinch, syscall.SIGWINCH) 57 signal.Notify(sigio, syscall.SIGIO) 58 59 _, err = fcntl(in, syscall.F_SETFL, syscall.O_ASYNC|syscall.O_NONBLOCK) 60 if err != nil { 61 return err 62 } 63 _, err = fcntl(in, syscall.F_SETOWN, syscall.Getpid()) 64 if runtime.GOOS != "darwin" && err != nil { 65 return err 66 } 67 err = tcgetattr(out.Fd(), &orig_tios) 68 if err != nil { 69 return err 70 } 71 72 tios := orig_tios 73 tios.Iflag &^= syscall_IGNBRK | syscall_BRKINT | syscall_PARMRK | 74 syscall_ISTRIP | syscall_INLCR | syscall_IGNCR | 75 syscall_ICRNL | syscall_IXON 76 tios.Lflag &^= syscall_ECHO | syscall_ECHONL | syscall_ICANON | 77 syscall_ISIG | syscall_IEXTEN 78 tios.Cflag &^= syscall_CSIZE | syscall_PARENB 79 tios.Cflag |= syscall_CS8 80 tios.Cc[syscall_VMIN] = 1 81 tios.Cc[syscall_VTIME] = 0 82 83 err = tcsetattr(out.Fd(), &tios) 84 if err != nil { 85 return err 86 } 87 88 out.WriteString(funcs[t_enter_ca]) 89 out.WriteString(funcs[t_enter_keypad]) 90 out.WriteString(funcs[t_hide_cursor]) 91 out.WriteString(funcs[t_clear_screen]) 92 93 termw, termh = get_term_size(out.Fd()) 94 back_buffer.init(termw, termh) 95 front_buffer.init(termw, termh) 96 back_buffer.clear() 97 front_buffer.clear() 98 99 go func() { 100 buf := make([]byte, 128) 101 for { 102 select { 103 case <-sigio: 104 for { 105 n, err := syscall.Read(in, buf) 106 if err == syscall.EAGAIN || err == syscall.EWOULDBLOCK { 107 break 108 } 109 select { 110 case input_comm <- input_event{buf[:n], err}: 111 ie := <-input_comm 112 buf = ie.data[:128] 113 case <-quit: 114 return 115 } 116 } 117 case <-quit: 118 return 119 } 120 } 121 }() 122 123 IsInit = true 124 return nil 125} 126 127// Interrupt an in-progress call to PollEvent by causing it to return 128// EventInterrupt. Note that this function will block until the PollEvent 129// function has successfully been interrupted. 130func Interrupt() { 131 interrupt_comm <- struct{}{} 132} 133 134// Finalizes termbox library, should be called after successful initialization 135// when termbox's functionality isn't required anymore. 136func Close() { 137 if !IsInit { 138 return 139 } 140 141 quit <- 1 142 out.WriteString(funcs[t_show_cursor]) 143 out.WriteString(funcs[t_sgr0]) 144 out.WriteString(funcs[t_clear_screen]) 145 out.WriteString(funcs[t_exit_ca]) 146 out.WriteString(funcs[t_exit_keypad]) 147 out.WriteString(funcs[t_exit_mouse]) 148 tcsetattr(out.Fd(), &orig_tios) 149 150 out.Close() 151 syscall.Close(in) 152 153 // reset the state, so that on next Init() it will work again 154 termw = 0 155 termh = 0 156 input_mode = InputEsc 157 out = nil 158 in = 0 159 lastfg = attr_invalid 160 lastbg = attr_invalid 161 lastx = coord_invalid 162 lasty = coord_invalid 163 cursor_x = cursor_hidden 164 cursor_y = cursor_hidden 165 foreground = ColorDefault 166 background = ColorDefault 167 IsInit = false 168} 169 170// Synchronizes the internal back buffer with the terminal. 171func Flush() error { 172 // invalidate cursor position 173 lastx = coord_invalid 174 lasty = coord_invalid 175 176 update_size_maybe() 177 178 for y := 0; y < front_buffer.height; y++ { 179 line_offset := y * front_buffer.width 180 for x := 0; x < front_buffer.width; { 181 cell_offset := line_offset + x 182 back := &back_buffer.cells[cell_offset] 183 front := &front_buffer.cells[cell_offset] 184 if back.Ch < ' ' { 185 back.Ch = ' ' 186 } 187 w := runewidth.RuneWidth(back.Ch) 188 if w == 0 || w == 2 && runewidth.IsAmbiguousWidth(back.Ch) { 189 w = 1 190 } 191 if *back == *front { 192 x += w 193 continue 194 } 195 *front = *back 196 send_attr(back.Fg, back.Bg) 197 198 if w == 2 && x == front_buffer.width-1 { 199 // there's not enough space for 2-cells rune, 200 // let's just put a space in there 201 send_char(x, y, ' ') 202 } else { 203 send_char(x, y, back.Ch) 204 if w == 2 { 205 next := cell_offset + 1 206 front_buffer.cells[next] = Cell{ 207 Ch: 0, 208 Fg: back.Fg, 209 Bg: back.Bg, 210 } 211 } 212 } 213 x += w 214 } 215 } 216 if !is_cursor_hidden(cursor_x, cursor_y) { 217 write_cursor(cursor_x, cursor_y) 218 } 219 return flush() 220} 221 222// Sets the position of the cursor. See also HideCursor(). 223func SetCursor(x, y int) { 224 if is_cursor_hidden(cursor_x, cursor_y) && !is_cursor_hidden(x, y) { 225 outbuf.WriteString(funcs[t_show_cursor]) 226 } 227 228 if !is_cursor_hidden(cursor_x, cursor_y) && is_cursor_hidden(x, y) { 229 outbuf.WriteString(funcs[t_hide_cursor]) 230 } 231 232 cursor_x, cursor_y = x, y 233 if !is_cursor_hidden(cursor_x, cursor_y) { 234 write_cursor(cursor_x, cursor_y) 235 } 236} 237 238// The shortcut for SetCursor(-1, -1). 239func HideCursor() { 240 SetCursor(cursor_hidden, cursor_hidden) 241} 242 243// Changes cell's parameters in the internal back buffer at the specified 244// position. 245func SetCell(x, y int, ch rune, fg, bg Attribute) { 246 if x < 0 || x >= back_buffer.width { 247 return 248 } 249 if y < 0 || y >= back_buffer.height { 250 return 251 } 252 253 back_buffer.cells[y*back_buffer.width+x] = Cell{ch, fg, bg} 254} 255 256// Returns a slice into the termbox's back buffer. You can get its dimensions 257// using 'Size' function. The slice remains valid as long as no 'Clear' or 258// 'Flush' function calls were made after call to this function. 259func CellBuffer() []Cell { 260 return back_buffer.cells 261} 262 263// After getting a raw event from PollRawEvent function call, you can parse it 264// again into an ordinary one using termbox logic. That is parse an event as 265// termbox would do it. Returned event in addition to usual Event struct fields 266// sets N field to the amount of bytes used within 'data' slice. If the length 267// of 'data' slice is zero or event cannot be parsed for some other reason, the 268// function will return a special event type: EventNone. 269// 270// IMPORTANT: EventNone may contain a non-zero N, which means you should skip 271// these bytes, because termbox cannot recognize them. 272// 273// NOTE: This API is experimental and may change in future. 274func ParseEvent(data []byte) Event { 275 event := Event{Type: EventKey} 276 status := extract_event(data, &event, false) 277 if status != event_extracted { 278 return Event{Type: EventNone, N: event.N} 279 } 280 return event 281} 282 283// Wait for an event and return it. This is a blocking function call. Instead 284// of EventKey and EventMouse it returns EventRaw events. Raw event is written 285// into `data` slice and Event's N field is set to the amount of bytes written. 286// The minimum required length of the 'data' slice is 1. This requirement may 287// vary on different platforms. 288// 289// NOTE: This API is experimental and may change in future. 290func PollRawEvent(data []byte) Event { 291 if len(data) == 0 { 292 panic("len(data) >= 1 is a requirement") 293 } 294 295 var event Event 296 if extract_raw_event(data, &event) { 297 return event 298 } 299 300 for { 301 select { 302 case ev := <-input_comm: 303 if ev.err != nil { 304 return Event{Type: EventError, Err: ev.err} 305 } 306 307 inbuf = append(inbuf, ev.data...) 308 input_comm <- ev 309 if extract_raw_event(data, &event) { 310 return event 311 } 312 case <-interrupt_comm: 313 event.Type = EventInterrupt 314 return event 315 316 case <-sigwinch: 317 event.Type = EventResize 318 event.Width, event.Height = get_term_size(out.Fd()) 319 return event 320 } 321 } 322} 323 324// Wait for an event and return it. This is a blocking function call. 325func PollEvent() Event { 326 // Constant governing macOS specific behavior. See https://github.com/nsf/termbox-go/issues/132 327 // This is an arbitrary delay which hopefully will be enough time for any lagging 328 // partial escape sequences to come through. 329 const esc_wait_delay = 100 * time.Millisecond 330 331 var event Event 332 var esc_wait_timer *time.Timer 333 var esc_timeout <-chan time.Time 334 335 // try to extract event from input buffer, return on success 336 event.Type = EventKey 337 status := extract_event(inbuf, &event, true) 338 if event.N != 0 { 339 copy(inbuf, inbuf[event.N:]) 340 inbuf = inbuf[:len(inbuf)-event.N] 341 } 342 if status == event_extracted { 343 return event 344 } else if status == esc_wait { 345 esc_wait_timer = time.NewTimer(esc_wait_delay) 346 esc_timeout = esc_wait_timer.C 347 } 348 349 for { 350 select { 351 case ev := <-input_comm: 352 if esc_wait_timer != nil { 353 if !esc_wait_timer.Stop() { 354 <-esc_wait_timer.C 355 } 356 esc_wait_timer = nil 357 } 358 359 if ev.err != nil { 360 return Event{Type: EventError, Err: ev.err} 361 } 362 363 inbuf = append(inbuf, ev.data...) 364 input_comm <- ev 365 status := extract_event(inbuf, &event, true) 366 if event.N != 0 { 367 copy(inbuf, inbuf[event.N:]) 368 inbuf = inbuf[:len(inbuf)-event.N] 369 } 370 if status == event_extracted { 371 return event 372 } else if status == esc_wait { 373 esc_wait_timer = time.NewTimer(esc_wait_delay) 374 esc_timeout = esc_wait_timer.C 375 } 376 case <-esc_timeout: 377 esc_wait_timer = nil 378 379 status := extract_event(inbuf, &event, false) 380 if event.N != 0 { 381 copy(inbuf, inbuf[event.N:]) 382 inbuf = inbuf[:len(inbuf)-event.N] 383 } 384 if status == event_extracted { 385 return event 386 } 387 case <-interrupt_comm: 388 event.Type = EventInterrupt 389 return event 390 391 case <-sigwinch: 392 event.Type = EventResize 393 event.Width, event.Height = get_term_size(out.Fd()) 394 return event 395 } 396 } 397} 398 399// Returns the size of the internal back buffer (which is mostly the same as 400// terminal's window size in characters). But it doesn't always match the size 401// of the terminal window, after the terminal size has changed, the internal 402// back buffer will get in sync only after Clear or Flush function calls. 403func Size() (width int, height int) { 404 return termw, termh 405} 406 407// Clears the internal back buffer. 408func Clear(fg, bg Attribute) error { 409 foreground, background = fg, bg 410 err := update_size_maybe() 411 back_buffer.clear() 412 return err 413} 414 415// Sets termbox input mode. Termbox has two input modes: 416// 417// 1. Esc input mode. When ESC sequence is in the buffer and it doesn't match 418// any known sequence. ESC means KeyEsc. This is the default input mode. 419// 420// 2. Alt input mode. When ESC sequence is in the buffer and it doesn't match 421// any known sequence. ESC enables ModAlt modifier for the next keyboard event. 422// 423// Both input modes can be OR'ed with Mouse mode. Setting Mouse mode bit up will 424// enable mouse button press/release and drag events. 425// 426// If 'mode' is InputCurrent, returns the current input mode. See also Input* 427// constants. 428func SetInputMode(mode InputMode) InputMode { 429 if mode == InputCurrent { 430 return input_mode 431 } 432 if mode&(InputEsc|InputAlt) == 0 { 433 mode |= InputEsc 434 } 435 if mode&(InputEsc|InputAlt) == InputEsc|InputAlt { 436 mode &^= InputAlt 437 } 438 if mode&InputMouse != 0 { 439 out.WriteString(funcs[t_enter_mouse]) 440 } else { 441 out.WriteString(funcs[t_exit_mouse]) 442 } 443 444 input_mode = mode 445 return input_mode 446} 447 448// Sets the termbox output mode. Termbox has four output options: 449// 450// 1. OutputNormal => [1..8] 451// This mode provides 8 different colors: 452// black, red, green, yellow, blue, magenta, cyan, white 453// Shortcut: ColorBlack, ColorRed, ... 454// Attributes: AttrBold, AttrUnderline, AttrReverse 455// 456// Example usage: 457// SetCell(x, y, '@', ColorBlack | AttrBold, ColorRed); 458// 459// 2. Output256 => [1..256] 460// In this mode you can leverage the 256 terminal mode: 461// 0x01 - 0x08: the 8 colors as in OutputNormal 462// 0x09 - 0x10: Color* | AttrBold 463// 0x11 - 0xe8: 216 different colors 464// 0xe9 - 0x1ff: 24 different shades of grey 465// 466// Example usage: 467// SetCell(x, y, '@', 184, 240); 468// SetCell(x, y, '@', 0xb8, 0xf0); 469// 470// 3. Output216 => [1..216] 471// This mode supports the 3rd range of the 256 mode only. 472// But you don't need to provide an offset. 473// 474// 4. OutputGrayscale => [1..26] 475// This mode supports the 4th range of the 256 mode 476// and black and white colors from 3th range of the 256 mode 477// But you don't need to provide an offset. 478// 479// In all modes, 0x00 represents the default color. 480// 481// `go run _demos/output.go` to see its impact on your terminal. 482// 483// If 'mode' is OutputCurrent, it returns the current output mode. 484// 485// Note that this may return a different OutputMode than the one requested, 486// as the requested mode may not be available on the target platform. 487func SetOutputMode(mode OutputMode) OutputMode { 488 if mode == OutputCurrent { 489 return output_mode 490 } 491 492 output_mode = mode 493 return output_mode 494} 495 496// Sync comes handy when something causes desync between termbox's understanding 497// of a terminal buffer and the reality. Such as a third party process. Sync 498// forces a complete resync between the termbox and a terminal, it may not be 499// visually pretty though. 500func Sync() error { 501 front_buffer.clear() 502 err := send_clear() 503 if err != nil { 504 return err 505 } 506 507 return Flush() 508} 509