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