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}