1package termbox 2 3import ( 4 "syscall" 5 6 "github.com/mattn/go-runewidth" 7) 8 9// public API 10 11// Initializes termbox library. This function should be called before any other functions. 12// After successful initialization, the library must be finalized using 'Close' function. 13// 14// Example usage: 15// err := termbox.Init() 16// if err != nil { 17// panic(err) 18// } 19// defer termbox.Close() 20func Init() error { 21 var err error 22 23 interrupt, err = create_event() 24 if err != nil { 25 return err 26 } 27 28 in, err = syscall.Open("CONIN$", syscall.O_RDWR, 0) 29 if err != nil { 30 return err 31 } 32 out, err = syscall.Open("CONOUT$", syscall.O_RDWR, 0) 33 if err != nil { 34 return err 35 } 36 37 err = get_console_mode(in, &orig_mode) 38 if err != nil { 39 return err 40 } 41 42 err = set_console_mode(in, enable_window_input) 43 if err != nil { 44 return err 45 } 46 47 orig_size, orig_window = get_term_size(out) 48 win_size := get_win_size(out) 49 50 err = set_console_screen_buffer_size(out, win_size) 51 if err != nil { 52 return err 53 } 54 55 err = fix_win_size(out, win_size) 56 if err != nil { 57 return err 58 } 59 60 err = get_console_cursor_info(out, &orig_cursor_info) 61 if err != nil { 62 return err 63 } 64 65 show_cursor(false) 66 term_size, _ = get_term_size(out) 67 back_buffer.init(int(term_size.x), int(term_size.y)) 68 front_buffer.init(int(term_size.x), int(term_size.y)) 69 back_buffer.clear() 70 front_buffer.clear() 71 clear() 72 73 diffbuf = make([]diff_msg, 0, 32) 74 75 go input_event_producer() 76 IsInit = true 77 return nil 78} 79 80// Finalizes termbox library, should be called after successful initialization 81// when termbox's functionality isn't required anymore. 82func Close() { 83 // we ignore errors here, because we can't really do anything about them 84 Clear(0, 0) 85 Flush() 86 87 // stop event producer 88 cancel_comm <- true 89 set_event(interrupt) 90 select { 91 case <-input_comm: 92 default: 93 } 94 <-cancel_done_comm 95 96 set_console_screen_buffer_size(out, orig_size) 97 set_console_window_info(out, &orig_window) 98 set_console_cursor_info(out, &orig_cursor_info) 99 set_console_cursor_position(out, coord{}) 100 set_console_mode(in, orig_mode) 101 syscall.Close(in) 102 syscall.Close(out) 103 syscall.Close(interrupt) 104 IsInit = false 105} 106 107// Interrupt an in-progress call to PollEvent by causing it to return 108// EventInterrupt. Note that this function will block until the PollEvent 109// function has successfully been interrupted. 110func Interrupt() { 111 interrupt_comm <- struct{}{} 112} 113 114// https://docs.microsoft.com/en-us/windows/console/char-info-str 115const ( 116 common_lvb_leading_byte = 0x0100 117 common_lvb_trailing_byte = 0x0200 118) 119 120// Synchronizes the internal back buffer with the terminal. 121func Flush() error { 122 update_size_maybe() 123 prepare_diff_messages() 124 for _, diff := range diffbuf { 125 chars := []char_info{} 126 for _, char := range diff.chars { 127 if runewidth.RuneWidth(rune(char.char)) > 1 { 128 char.attr |= common_lvb_leading_byte 129 chars = append(chars, char) 130 chars = append(chars, char_info{ 131 char: char.char, 132 attr: char.attr | common_lvb_trailing_byte, 133 }) 134 } else { 135 chars = append(chars, char) 136 } 137 } 138 r := small_rect{ 139 left: 0, 140 top: diff.pos, 141 right: term_size.x - 1, 142 bottom: diff.pos + diff.lines - 1, 143 } 144 write_console_output(out, chars, r) 145 } 146 if !is_cursor_hidden(cursor_x, cursor_y) { 147 move_cursor(cursor_x, cursor_y) 148 } 149 return nil 150} 151 152// Sets the position of the cursor. See also HideCursor(). 153func SetCursor(x, y int) { 154 if is_cursor_hidden(cursor_x, cursor_y) && !is_cursor_hidden(x, y) { 155 show_cursor(true) 156 } 157 158 if !is_cursor_hidden(cursor_x, cursor_y) && is_cursor_hidden(x, y) { 159 show_cursor(false) 160 } 161 162 cursor_x, cursor_y = x, y 163 if !is_cursor_hidden(cursor_x, cursor_y) { 164 move_cursor(cursor_x, cursor_y) 165 } 166} 167 168// The shortcut for SetCursor(-1, -1). 169func HideCursor() { 170 SetCursor(cursor_hidden, cursor_hidden) 171} 172 173// Changes cell's parameters in the internal back buffer at the specified 174// position. 175func SetCell(x, y int, ch rune, fg, bg Attribute) { 176 if x < 0 || x >= back_buffer.width { 177 return 178 } 179 if y < 0 || y >= back_buffer.height { 180 return 181 } 182 183 back_buffer.cells[y*back_buffer.width+x] = Cell{ch, fg, bg} 184} 185 186// Returns the specified cell from the internal back buffer. 187func GetCell(x, y int) Cell { 188 return back_buffer.cells[y*back_buffer.width+x] 189} 190 191// Changes cell's character (rune) in the internal back buffer at the 192// specified position. 193func SetChar(x, y int, ch rune) { 194 if x < 0 || x >= back_buffer.width { 195 return 196 } 197 if y < 0 || y >= back_buffer.height { 198 return 199 } 200 201 back_buffer.cells[y*back_buffer.width+x].Ch = ch 202} 203 204// Changes cell's foreground attributes in the internal back buffer at 205// the specified position. 206func SetFg(x, y int, fg Attribute) { 207 if x < 0 || x >= back_buffer.width { 208 return 209 } 210 if y < 0 || y >= back_buffer.height { 211 return 212 } 213 214 back_buffer.cells[y*back_buffer.width+x].Fg = fg 215} 216 217// Changes cell's background attributes in the internal back buffer at 218// the specified position. 219func SetBg(x, y int, bg Attribute) { 220 if x < 0 || x >= back_buffer.width { 221 return 222 } 223 if y < 0 || y >= back_buffer.height { 224 return 225 } 226 227 back_buffer.cells[y*back_buffer.width+x].Bg = bg 228} 229 230// Returns a slice into the termbox's back buffer. You can get its dimensions 231// using 'Size' function. The slice remains valid as long as no 'Clear' or 232// 'Flush' function calls were made after call to this function. 233func CellBuffer() []Cell { 234 return back_buffer.cells 235} 236 237// Wait for an event and return it. This is a blocking function call. 238func PollEvent() Event { 239 select { 240 case ev := <-input_comm: 241 return ev 242 case <-interrupt_comm: 243 return Event{Type: EventInterrupt} 244 } 245} 246 247// Returns the size of the internal back buffer (which is mostly the same as 248// console's window size in characters). But it doesn't always match the size 249// of the console window, after the console size has changed, the internal back 250// buffer will get in sync only after Clear or Flush function calls. 251func Size() (int, int) { 252 return int(term_size.x), int(term_size.y) 253} 254 255// Clears the internal back buffer. 256func Clear(fg, bg Attribute) error { 257 foreground, background = fg, bg 258 update_size_maybe() 259 back_buffer.clear() 260 return nil 261} 262 263// Sets termbox input mode. Termbox has two input modes: 264// 265// 1. Esc input mode. When ESC sequence is in the buffer and it doesn't match 266// any known sequence. ESC means KeyEsc. This is the default input mode. 267// 268// 2. Alt input mode. When ESC sequence is in the buffer and it doesn't match 269// any known sequence. ESC enables ModAlt modifier for the next keyboard event. 270// 271// Both input modes can be OR'ed with Mouse mode. Setting Mouse mode bit up will 272// enable mouse button press/release and drag events. 273// 274// If 'mode' is InputCurrent, returns the current input mode. See also Input* 275// constants. 276func SetInputMode(mode InputMode) InputMode { 277 if mode == InputCurrent { 278 return input_mode 279 } 280 if mode&InputMouse != 0 { 281 err := set_console_mode(in, enable_window_input|enable_mouse_input|enable_extended_flags) 282 if err != nil { 283 panic(err) 284 } 285 } else { 286 err := set_console_mode(in, enable_window_input) 287 if err != nil { 288 panic(err) 289 } 290 } 291 292 input_mode = mode 293 return input_mode 294} 295 296// Sets the termbox output mode. 297// 298// Windows console does not support extra colour modes, 299// so this will always set and return OutputNormal. 300func SetOutputMode(mode OutputMode) OutputMode { 301 return OutputNormal 302} 303 304// Sync comes handy when something causes desync between termbox's understanding 305// of a terminal buffer and the reality. Such as a third party process. Sync 306// forces a complete resync between the termbox and a terminal, it may not be 307// visually pretty though. At the moment on Windows it does nothing. 308func Sync() error { 309 return nil 310} 311