api.go 12 KB

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