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