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}