aboutsummaryrefslogblamecommitdiffstats
path: root/vendor/github.com/nsf/termbox-go/api.go
blob: b339e532f8e6f6ea14fcbf279adf41ec8a1a100a (plain) (tree)


























































                                                                                          




































































































































































































































































































                                                                                                
                                     



















                                                                                
                                                     






                                                                             





                                                          










                                                                 
  










                                                               



                                                  








                                                            


                                                               

                                             
                                                   




























                                                                                
// +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
        }
    }
    panic("unreachable")
}

// 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()
}