123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458 |
- // +build !windows
- package termbox
- import "github.com/mattn/go-runewidth"
- import "fmt"
- import "os"
- import "os/signal"
- import "syscall"
- import "runtime"
- // public API
- // Initializes termbox library. This function should be called before any other functions.
- // After successful initialization, the library must be finalized using 'Close' function.
- //
- // Example usage:
- // err := termbox.Init()
- // if err != nil {
- // panic(err)
- // }
- // defer termbox.Close()
- func Init() error {
- var err error
- out, err = os.OpenFile("/dev/tty", syscall.O_WRONLY, 0)
- if err != nil {
- return err
- }
- in, err = syscall.Open("/dev/tty", syscall.O_RDONLY, 0)
- if err != nil {
- return err
- }
- err = setup_term()
- if err != nil {
- return fmt.Errorf("termbox: error while reading terminfo data: %v", err)
- }
- signal.Notify(sigwinch, syscall.SIGWINCH)
- signal.Notify(sigio, syscall.SIGIO)
- _, err = fcntl(in, syscall.F_SETFL, syscall.O_ASYNC|syscall.O_NONBLOCK)
- if err != nil {
- return err
- }
- _, err = fcntl(in, syscall.F_SETOWN, syscall.Getpid())
- if runtime.GOOS != "darwin" && err != nil {
- return err
- }
- err = tcgetattr(out.Fd(), &orig_tios)
- if err != nil {
- return err
- }
- tios := orig_tios
- tios.Iflag &^= syscall_IGNBRK | syscall_BRKINT | syscall_PARMRK |
- syscall_ISTRIP | syscall_INLCR | syscall_IGNCR |
- syscall_ICRNL | syscall_IXON
- tios.Lflag &^= syscall_ECHO | syscall_ECHONL | syscall_ICANON |
- syscall_ISIG | syscall_IEXTEN
- tios.Cflag &^= syscall_CSIZE | syscall_PARENB
- tios.Cflag |= syscall_CS8
- tios.Cc[syscall_VMIN] = 1
- tios.Cc[syscall_VTIME] = 0
- err = tcsetattr(out.Fd(), &tios)
- if err != nil {
- return err
- }
- out.WriteString(funcs[t_enter_ca])
- out.WriteString(funcs[t_enter_keypad])
- out.WriteString(funcs[t_hide_cursor])
- out.WriteString(funcs[t_clear_screen])
- termw, termh = get_term_size(out.Fd())
- back_buffer.init(termw, termh)
- front_buffer.init(termw, termh)
- back_buffer.clear()
- front_buffer.clear()
- go func() {
- buf := make([]byte, 128)
- for {
- select {
- case <-sigio:
- for {
- n, err := syscall.Read(in, buf)
- if err == syscall.EAGAIN || err == syscall.EWOULDBLOCK {
- break
- }
- select {
- case input_comm <- input_event{buf[:n], err}:
- ie := <-input_comm
- buf = ie.data[:128]
- case <-quit:
- return
- }
- }
- case <-quit:
- return
- }
- }
- }()
- IsInit = true
- return nil
- }
- // Interrupt an in-progress call to PollEvent by causing it to return
- // EventInterrupt. Note that this function will block until the PollEvent
- // function has successfully been interrupted.
- func Interrupt() {
- interrupt_comm <- struct{}{}
- }
- // Finalizes termbox library, should be called after successful initialization
- // when termbox's functionality isn't required anymore.
- func Close() {
- quit <- 1
- out.WriteString(funcs[t_show_cursor])
- out.WriteString(funcs[t_sgr0])
- out.WriteString(funcs[t_clear_screen])
- out.WriteString(funcs[t_exit_ca])
- out.WriteString(funcs[t_exit_keypad])
- out.WriteString(funcs[t_exit_mouse])
- tcsetattr(out.Fd(), &orig_tios)
- out.Close()
- syscall.Close(in)
- // reset the state, so that on next Init() it will work again
- termw = 0
- termh = 0
- input_mode = InputEsc
- out = nil
- in = 0
- lastfg = attr_invalid
- lastbg = attr_invalid
- lastx = coord_invalid
- lasty = coord_invalid
- cursor_x = cursor_hidden
- cursor_y = cursor_hidden
- foreground = ColorDefault
- background = ColorDefault
- IsInit = false
- }
- // Synchronizes the internal back buffer with the terminal.
- func Flush() error {
- // invalidate cursor position
- lastx = coord_invalid
- lasty = coord_invalid
- update_size_maybe()
- for y := 0; y < front_buffer.height; y++ {
- line_offset := y * front_buffer.width
- for x := 0; x < front_buffer.width; {
- cell_offset := line_offset + x
- back := &back_buffer.cells[cell_offset]
- front := &front_buffer.cells[cell_offset]
- if back.Ch < ' ' {
- back.Ch = ' '
- }
- w := runewidth.RuneWidth(back.Ch)
- if w == 0 || w == 2 && runewidth.IsAmbiguousWidth(back.Ch) {
- w = 1
- }
- if *back == *front {
- x += w
- continue
- }
- *front = *back
- send_attr(back.Fg, back.Bg)
- if w == 2 && x == front_buffer.width-1 {
- // there's not enough space for 2-cells rune,
- // let's just put a space in there
- send_char(x, y, ' ')
- } else {
- send_char(x, y, back.Ch)
- if w == 2 {
- next := cell_offset + 1
- front_buffer.cells[next] = Cell{
- Ch: 0,
- Fg: back.Fg,
- Bg: back.Bg,
- }
- }
- }
- x += w
- }
- }
- if !is_cursor_hidden(cursor_x, cursor_y) {
- write_cursor(cursor_x, cursor_y)
- }
- return flush()
- }
- // Sets the position of the cursor. See also HideCursor().
- func SetCursor(x, y int) {
- if is_cursor_hidden(cursor_x, cursor_y) && !is_cursor_hidden(x, y) {
- outbuf.WriteString(funcs[t_show_cursor])
- }
- if !is_cursor_hidden(cursor_x, cursor_y) && is_cursor_hidden(x, y) {
- outbuf.WriteString(funcs[t_hide_cursor])
- }
- cursor_x, cursor_y = x, y
- if !is_cursor_hidden(cursor_x, cursor_y) {
- write_cursor(cursor_x, cursor_y)
- }
- }
- // The shortcut for SetCursor(-1, -1).
- func HideCursor() {
- SetCursor(cursor_hidden, cursor_hidden)
- }
- // Changes cell's parameters in the internal back buffer at the specified
- // position.
- func SetCell(x, y int, ch rune, fg, bg Attribute) {
- if x < 0 || x >= back_buffer.width {
- return
- }
- if y < 0 || y >= back_buffer.height {
- return
- }
- back_buffer.cells[y*back_buffer.width+x] = Cell{ch, fg, bg}
- }
- // Returns a slice into the termbox's back buffer. You can get its dimensions
- // using 'Size' function. The slice remains valid as long as no 'Clear' or
- // 'Flush' function calls were made after call to this function.
- func CellBuffer() []Cell {
- return back_buffer.cells
- }
- // After getting a raw event from PollRawEvent function call, you can parse it
- // again into an ordinary one using termbox logic. That is parse an event as
- // termbox would do it. Returned event in addition to usual Event struct fields
- // sets N field to the amount of bytes used within 'data' slice. If the length
- // of 'data' slice is zero or event cannot be parsed for some other reason, the
- // function will return a special event type: EventNone.
- //
- // IMPORTANT: EventNone may contain a non-zero N, which means you should skip
- // these bytes, because termbox cannot recognize them.
- //
- // NOTE: This API is experimental and may change in future.
- func ParseEvent(data []byte) Event {
- event := Event{Type: EventKey}
- ok := extract_event(data, &event)
- if !ok {
- return Event{Type: EventNone, N: event.N}
- }
- return event
- }
- // Wait for an event and return it. This is a blocking function call. Instead
- // of EventKey and EventMouse it returns EventRaw events. Raw event is written
- // into `data` slice and Event's N field is set to the amount of bytes written.
- // The minimum required length of the 'data' slice is 1. This requirement may
- // vary on different platforms.
- //
- // NOTE: This API is experimental and may change in future.
- func PollRawEvent(data []byte) Event {
- if len(data) == 0 {
- panic("len(data) >= 1 is a requirement")
- }
- var event Event
- if extract_raw_event(data, &event) {
- return event
- }
- for {
- select {
- case ev := <-input_comm:
- if ev.err != nil {
- return Event{Type: EventError, Err: ev.err}
- }
- inbuf = append(inbuf, ev.data...)
- input_comm <- ev
- if extract_raw_event(data, &event) {
- return event
- }
- case <-interrupt_comm:
- event.Type = EventInterrupt
- return event
- case <-sigwinch:
- event.Type = EventResize
- event.Width, event.Height = get_term_size(out.Fd())
- return event
- }
- }
- }
- // Wait for an event and return it. This is a blocking function call.
- func PollEvent() Event {
- var event Event
- // try to extract event from input buffer, return on success
- event.Type = EventKey
- ok := extract_event(inbuf, &event)
- if event.N != 0 {
- copy(inbuf, inbuf[event.N:])
- inbuf = inbuf[:len(inbuf)-event.N]
- }
- if ok {
- return event
- }
- for {
- select {
- case ev := <-input_comm:
- if ev.err != nil {
- return Event{Type: EventError, Err: ev.err}
- }
- inbuf = append(inbuf, ev.data...)
- input_comm <- ev
- ok := extract_event(inbuf, &event)
- if event.N != 0 {
- copy(inbuf, inbuf[event.N:])
- inbuf = inbuf[:len(inbuf)-event.N]
- }
- if ok {
- return event
- }
- case <-interrupt_comm:
- event.Type = EventInterrupt
- return event
- case <-sigwinch:
- event.Type = EventResize
- event.Width, event.Height = get_term_size(out.Fd())
- return event
- }
- }
- }
- // Returns the size of the internal back buffer (which is mostly the same as
- // terminal's window size in characters). But it doesn't always match the size
- // of the terminal window, after the terminal size has changed, the internal
- // back buffer will get in sync only after Clear or Flush function calls.
- func Size() (width int, height int) {
- return termw, termh
- }
- // Clears the internal back buffer.
- func Clear(fg, bg Attribute) error {
- foreground, background = fg, bg
- err := update_size_maybe()
- back_buffer.clear()
- return err
- }
- // Sets termbox input mode. Termbox has two input modes:
- //
- // 1. Esc input mode. When ESC sequence is in the buffer and it doesn't match
- // any known sequence. ESC means KeyEsc. This is the default input mode.
- //
- // 2. Alt input mode. When ESC sequence is in the buffer and it doesn't match
- // any known sequence. ESC enables ModAlt modifier for the next keyboard event.
- //
- // Both input modes can be OR'ed with Mouse mode. Setting Mouse mode bit up will
- // enable mouse button press/release and drag events.
- //
- // If 'mode' is InputCurrent, returns the current input mode. See also Input*
- // constants.
- func SetInputMode(mode InputMode) InputMode {
- if mode == InputCurrent {
- return input_mode
- }
- if mode&(InputEsc|InputAlt) == 0 {
- mode |= InputEsc
- }
- if mode&(InputEsc|InputAlt) == InputEsc|InputAlt {
- mode &^= InputAlt
- }
- if mode&InputMouse != 0 {
- out.WriteString(funcs[t_enter_mouse])
- } else {
- out.WriteString(funcs[t_exit_mouse])
- }
- input_mode = mode
- return input_mode
- }
- // Sets the termbox output mode. Termbox has four output options:
- //
- // 1. OutputNormal => [1..8]
- // This mode provides 8 different colors:
- // black, red, green, yellow, blue, magenta, cyan, white
- // Shortcut: ColorBlack, ColorRed, ...
- // Attributes: AttrBold, AttrUnderline, AttrReverse
- //
- // Example usage:
- // SetCell(x, y, '@', ColorBlack | AttrBold, ColorRed);
- //
- // 2. Output256 => [1..256]
- // In this mode you can leverage the 256 terminal mode:
- // 0x01 - 0x08: the 8 colors as in OutputNormal
- // 0x09 - 0x10: Color* | AttrBold
- // 0x11 - 0xe8: 216 different colors
- // 0xe9 - 0x1ff: 24 different shades of grey
- //
- // Example usage:
- // SetCell(x, y, '@', 184, 240);
- // SetCell(x, y, '@', 0xb8, 0xf0);
- //
- // 3. Output216 => [1..216]
- // This mode supports the 3rd range of the 256 mode only.
- // But you dont need to provide an offset.
- //
- // 4. OutputGrayscale => [1..26]
- // This mode supports the 4th range of the 256 mode
- // and black and white colors from 3th range of the 256 mode
- // But you dont need to provide an offset.
- //
- // In all modes, 0x00 represents the default color.
- //
- // `go run _demos/output.go` to see its impact on your terminal.
- //
- // If 'mode' is OutputCurrent, it returns the current output mode.
- //
- // Note that this may return a different OutputMode than the one requested,
- // as the requested mode may not be available on the target platform.
- func SetOutputMode(mode OutputMode) OutputMode {
- if mode == OutputCurrent {
- return output_mode
- }
- output_mode = mode
- return output_mode
- }
- // Sync comes handy when something causes desync between termbox's understanding
- // of a terminal buffer and the reality. Such as a third party process. Sync
- // forces a complete resync between the termbox and a terminal, it may not be
- // visually pretty though.
- func Sync() error {
- front_buffer.clear()
- err := send_clear()
- if err != nil {
- return err
- }
- return Flush()
- }
|