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// Synchronizes the internal back buffer with the terminal.
115func Flush() error {
116	update_size_maybe()
117	prepare_diff_messages()
118	for _, diff := range diffbuf {
119		chars := []char_info{}
120		for _, char := range diff.chars {
121			chars = append(chars, char)
122			if runewidth.RuneWidth(rune(char.char)) > 1 {
123				chars = append(chars, char_info{
124					char: ' ',
125					attr: char.attr,
126				})
127			}
128		}
129		r := small_rect{
130			left:   0,
131			top:    diff.pos,
132			right:  term_size.x - 1,
133			bottom: diff.pos + diff.lines - 1,
134		}
135		write_console_output(out, chars, r)
136	}
137	if !is_cursor_hidden(cursor_x, cursor_y) {
138		move_cursor(cursor_x, cursor_y)
139	}
140	return nil
141}
142
143// Sets the position of the cursor. See also HideCursor().
144func SetCursor(x, y int) {
145	if is_cursor_hidden(cursor_x, cursor_y) && !is_cursor_hidden(x, y) {
146		show_cursor(true)
147	}
148
149	if !is_cursor_hidden(cursor_x, cursor_y) && is_cursor_hidden(x, y) {
150		show_cursor(false)
151	}
152
153	cursor_x, cursor_y = x, y
154	if !is_cursor_hidden(cursor_x, cursor_y) {
155		move_cursor(cursor_x, cursor_y)
156	}
157}
158
159// The shortcut for SetCursor(-1, -1).
160func HideCursor() {
161	SetCursor(cursor_hidden, cursor_hidden)
162}
163
164// Changes cell's parameters in the internal back buffer at the specified
165// position.
166func SetCell(x, y int, ch rune, fg, bg Attribute) {
167	if x < 0 || x >= back_buffer.width {
168		return
169	}
170	if y < 0 || y >= back_buffer.height {
171		return
172	}
173
174	back_buffer.cells[y*back_buffer.width+x] = Cell{ch, fg, bg}
175}
176
177// Returns a slice into the termbox's back buffer. You can get its dimensions
178// using 'Size' function. The slice remains valid as long as no 'Clear' or
179// 'Flush' function calls were made after call to this function.
180func CellBuffer() []Cell {
181	return back_buffer.cells
182}
183
184// Wait for an event and return it. This is a blocking function call.
185func PollEvent() Event {
186	select {
187	case ev := <-input_comm:
188		return ev
189	case <-interrupt_comm:
190		return Event{Type: EventInterrupt}
191	}
192}
193
194// Returns the size of the internal back buffer (which is mostly the same as
195// console's window size in characters). But it doesn't always match the size
196// of the console window, after the console size has changed, the internal back
197// buffer will get in sync only after Clear or Flush function calls.
198func Size() (int, int) {
199	return int(term_size.x), int(term_size.y)
200}
201
202// Clears the internal back buffer.
203func Clear(fg, bg Attribute) error {
204	foreground, background = fg, bg
205	update_size_maybe()
206	back_buffer.clear()
207	return nil
208}
209
210// Sets termbox input mode. Termbox has two input modes:
211//
212// 1. Esc input mode. When ESC sequence is in the buffer and it doesn't match
213// any known sequence. ESC means KeyEsc. This is the default input mode.
214//
215// 2. Alt input mode. When ESC sequence is in the buffer and it doesn't match
216// any known sequence. ESC enables ModAlt modifier for the next keyboard event.
217//
218// Both input modes can be OR'ed with Mouse mode. Setting Mouse mode bit up will
219// enable mouse button press/release and drag events.
220//
221// If 'mode' is InputCurrent, returns the current input mode. See also Input*
222// constants.
223func SetInputMode(mode InputMode) InputMode {
224	if mode == InputCurrent {
225		return input_mode
226	}
227	if mode&InputMouse != 0 {
228		err := set_console_mode(in, enable_window_input|enable_mouse_input|enable_extended_flags)
229		if err != nil {
230			panic(err)
231		}
232	} else {
233		err := set_console_mode(in, enable_window_input)
234		if err != nil {
235			panic(err)
236		}
237	}
238
239	input_mode = mode
240	return input_mode
241}
242
243// Sets the termbox output mode.
244//
245// Windows console does not support extra colour modes,
246// so this will always set and return OutputNormal.
247func SetOutputMode(mode OutputMode) OutputMode {
248	return OutputNormal
249}
250
251// Sync comes handy when something causes desync between termbox's understanding
252// of a terminal buffer and the reality. Such as a third party process. Sync
253// forces a complete resync between the termbox and a terminal, it may not be
254// visually pretty though. At the moment on Windows it does nothing.
255func Sync() error {
256	return nil
257}
258