package termbox import "math" import "syscall" import "unsafe" import "unicode/utf16" import "github.com/mattn/go-runewidth" type ( wchar uint16 short int16 dword uint32 word uint16 char_info struct { char wchar attr word } coord struct { x short y short } small_rect struct { left short top short right short bottom short } console_screen_buffer_info struct { size coord cursor_position coord attributes word window small_rect maximum_window_size coord } console_cursor_info struct { size dword visible int32 } input_record struct { event_type word _ [2]byte event [16]byte } key_event_record struct { key_down int32 repeat_count word virtual_key_code word virtual_scan_code word unicode_char wchar control_key_state dword } window_buffer_size_record struct { size coord } mouse_event_record struct { mouse_pos coord button_state dword control_key_state dword event_flags dword } console_font_info struct { font uint32 font_size coord } ) const ( mouse_lmb = 0x1 mouse_rmb = 0x2 mouse_mmb = 0x4 | 0x8 | 0x10 SM_CXMIN = 28 SM_CYMIN = 29 ) func (this coord) uintptr() uintptr { return uintptr(*(*int32)(unsafe.Pointer(&this))) } func (this *small_rect) uintptr() uintptr { return uintptr(unsafe.Pointer(this)) } var kernel32 = syscall.NewLazyDLL("kernel32.dll") var moduser32 = syscall.NewLazyDLL("user32.dll") var is_cjk = runewidth.IsEastAsian() var ( proc_set_console_active_screen_buffer = kernel32.NewProc("SetConsoleActiveScreenBuffer") proc_set_console_screen_buffer_size = kernel32.NewProc("SetConsoleScreenBufferSize") proc_set_console_window_info = kernel32.NewProc("SetConsoleWindowInfo") proc_create_console_screen_buffer = kernel32.NewProc("CreateConsoleScreenBuffer") proc_get_console_screen_buffer_info = kernel32.NewProc("GetConsoleScreenBufferInfo") proc_write_console_output = kernel32.NewProc("WriteConsoleOutputW") proc_write_console_output_character = kernel32.NewProc("WriteConsoleOutputCharacterW") proc_write_console_output_attribute = kernel32.NewProc("WriteConsoleOutputAttribute") proc_set_console_cursor_info = kernel32.NewProc("SetConsoleCursorInfo") proc_set_console_cursor_position = kernel32.NewProc("SetConsoleCursorPosition") proc_get_console_cursor_info = kernel32.NewProc("GetConsoleCursorInfo") proc_read_console_input = kernel32.NewProc("ReadConsoleInputW") proc_get_console_mode = kernel32.NewProc("GetConsoleMode") proc_set_console_mode = kernel32.NewProc("SetConsoleMode") proc_fill_console_output_character = kernel32.NewProc("FillConsoleOutputCharacterW") proc_fill_console_output_attribute = kernel32.NewProc("FillConsoleOutputAttribute") proc_create_event = kernel32.NewProc("CreateEventW") proc_wait_for_multiple_objects = kernel32.NewProc("WaitForMultipleObjects") proc_set_event = kernel32.NewProc("SetEvent") proc_get_current_console_font = kernel32.NewProc("GetCurrentConsoleFont") get_system_metrics = moduser32.NewProc("GetSystemMetrics") ) func set_console_active_screen_buffer(h syscall.Handle) (err error) { r0, _, e1 := syscall.Syscall(proc_set_console_active_screen_buffer.Addr(), 1, uintptr(h), 0, 0) if int(r0) == 0 { if e1 != 0 { err = error(e1) } else { err = syscall.EINVAL } } return } func set_console_screen_buffer_size(h syscall.Handle, size coord) (err error) { r0, _, e1 := syscall.Syscall(proc_set_console_screen_buffer_size.Addr(), 2, uintptr(h), size.uintptr(), 0) if int(r0) == 0 { if e1 != 0 { err = error(e1) } else { err = syscall.EINVAL } } return } func set_console_window_info(h syscall.Handle, window *small_rect) (err error) { var absolute uint32 absolute = 1 r0, _, e1 := syscall.Syscall(proc_set_console_window_info.Addr(), 3, uintptr(h), uintptr(absolute), window.uintptr()) if int(r0) == 0 { if e1 != 0 { err = error(e1) } else { err = syscall.EINVAL } } return } func create_console_screen_buffer() (h syscall.Handle, err error) { r0, _, e1 := syscall.Syscall6(proc_create_console_screen_buffer.Addr(), 5, uintptr(generic_read|generic_write), 0, 0, console_textmode_buffer, 0, 0) if int(r0) == 0 { if e1 != 0 { err = error(e1) } else { err = syscall.EINVAL } } return syscall.Handle(r0), err } func get_console_screen_buffer_info(h syscall.Handle, info *console_screen_buffer_info) (err error) { r0, _, e1 := syscall.Syscall(proc_get_console_screen_buffer_info.Addr(), 2, uintptr(h), uintptr(unsafe.Pointer(info)), 0) if int(r0) == 0 { if e1 != 0 { err = error(e1) } else { err = syscall.EINVAL } } return } func write_console_output(h syscall.Handle, chars []char_info, dst small_rect) (err error) { tmp_coord = coord{dst.right - dst.left + 1, dst.bottom - dst.top + 1} tmp_rect = dst r0, _, e1 := syscall.Syscall6(proc_write_console_output.Addr(), 5, uintptr(h), uintptr(unsafe.Pointer(&chars[0])), tmp_coord.uintptr(), tmp_coord0.uintptr(), uintptr(unsafe.Pointer(&tmp_rect)), 0) if int(r0) == 0 { if e1 != 0 { err = error(e1) } else { err = syscall.EINVAL } } return } func write_console_output_character(h syscall.Handle, chars []wchar, pos coord) (err error) { r0, _, e1 := syscall.Syscall6(proc_write_console_output_character.Addr(), 5, uintptr(h), uintptr(unsafe.Pointer(&chars[0])), uintptr(len(chars)), pos.uintptr(), uintptr(unsafe.Pointer(&tmp_arg)), 0) if int(r0) == 0 { if e1 != 0 { err = error(e1) } else { err = syscall.EINVAL } } return } func write_console_output_attribute(h syscall.Handle, attrs []word, pos coord) (err error) { r0, _, e1 := syscall.Syscall6(proc_write_console_output_attribute.Addr(), 5, uintptr(h), uintptr(unsafe.Pointer(&attrs[0])), uintptr(len(attrs)), pos.uintptr(), uintptr(unsafe.Pointer(&tmp_arg)), 0) if int(r0) == 0 { if e1 != 0 { err = error(e1) } else { err = syscall.EINVAL } } return } func set_console_cursor_info(h syscall.Handle, info *console_cursor_info) (err error) { r0, _, e1 := syscall.Syscall(proc_set_console_cursor_info.Addr(), 2, uintptr(h), uintptr(unsafe.Pointer(info)), 0) if int(r0) == 0 { if e1 != 0 { err = error(e1) } else { err = syscall.EINVAL } } return } func get_console_cursor_info(h syscall.Handle, info *console_cursor_info) (err error) { r0, _, e1 := syscall.Syscall(proc_get_console_cursor_info.Addr(), 2, uintptr(h), uintptr(unsafe.Pointer(info)), 0) if int(r0) == 0 { if e1 != 0 { err = error(e1) } else { err = syscall.EINVAL } } return } func set_console_cursor_position(h syscall.Handle, pos coord) (err error) { r0, _, e1 := syscall.Syscall(proc_set_console_cursor_position.Addr(), 2, uintptr(h), pos.uintptr(), 0) if int(r0) == 0 { if e1 != 0 { err = error(e1) } else { err = syscall.EINVAL } } return } func read_console_input(h syscall.Handle, record *input_record) (err error) { r0, _, e1 := syscall.Syscall6(proc_read_console_input.Addr(), 4, uintptr(h), uintptr(unsafe.Pointer(record)), 1, uintptr(unsafe.Pointer(&tmp_arg)), 0, 0) if int(r0) == 0 { if e1 != 0 { err = error(e1) } else { err = syscall.EINVAL } } return } func get_console_mode(h syscall.Handle, mode *dword) (err error) { r0, _, e1 := syscall.Syscall(proc_get_console_mode.Addr(), 2, uintptr(h), uintptr(unsafe.Pointer(mode)), 0) if int(r0) == 0 { if e1 != 0 { err = error(e1) } else { err = syscall.EINVAL } } return } func set_console_mode(h syscall.Handle, mode dword) (err error) { r0, _, e1 := syscall.Syscall(proc_set_console_mode.Addr(), 2, uintptr(h), uintptr(mode), 0) if int(r0) == 0 { if e1 != 0 { err = error(e1) } else { err = syscall.EINVAL } } return } func fill_console_output_character(h syscall.Handle, char wchar, n int) (err error) { tmp_coord = coord{0, 0} r0, _, e1 := syscall.Syscall6(proc_fill_console_output_character.Addr(), 5, uintptr(h), uintptr(char), uintptr(n), tmp_coord.uintptr(), uintptr(unsafe.Pointer(&tmp_arg)), 0) if int(r0) == 0 { if e1 != 0 { err = error(e1) } else { err = syscall.EINVAL } } return } func fill_console_output_attribute(h syscall.Handle, attr word, n int) (err error) { tmp_coord = coord{0, 0} r0, _, e1 := syscall.Syscall6(proc_fill_console_output_attribute.Addr(), 5, uintptr(h), uintptr(attr), uintptr(n), tmp_coord.uintptr(), uintptr(unsafe.Pointer(&tmp_arg)), 0) if int(r0) == 0 { if e1 != 0 { err = error(e1) } else { err = syscall.EINVAL } } return } func create_event() (out syscall.Handle, err error) { r0, _, e1 := syscall.Syscall6(proc_create_event.Addr(), 4, 0, 0, 0, 0, 0, 0) if int(r0) == 0 { if e1 != 0 { err = error(e1) } else { err = syscall.EINVAL } } return syscall.Handle(r0), err } func wait_for_multiple_objects(objects []syscall.Handle) (err error) { r0, _, e1 := syscall.Syscall6(proc_wait_for_multiple_objects.Addr(), 4, uintptr(len(objects)), uintptr(unsafe.Pointer(&objects[0])), 0, 0xFFFFFFFF, 0, 0) if uint32(r0) == 0xFFFFFFFF { if e1 != 0 { err = error(e1) } else { err = syscall.EINVAL } } return } func set_event(ev syscall.Handle) (err error) { r0, _, e1 := syscall.Syscall(proc_set_event.Addr(), 1, uintptr(ev), 0, 0) if int(r0) == 0 { if e1 != 0 { err = error(e1) } else { err = syscall.EINVAL } } return } func get_current_console_font(h syscall.Handle, info *console_font_info) (err error) { r0, _, e1 := syscall.Syscall(proc_get_current_console_font.Addr(), 3, uintptr(h), 0, uintptr(unsafe.Pointer(info))) if int(r0) == 0 { if e1 != 0 { err = error(e1) } else { err = syscall.EINVAL } } return } type diff_msg struct { pos short lines short chars []char_info } type input_event struct { event Event err error } var ( orig_cursor_info console_cursor_info orig_size coord orig_window small_rect orig_mode dword orig_screen syscall.Handle back_buffer cellbuf front_buffer cellbuf term_size coord input_mode = InputEsc cursor_x = cursor_hidden cursor_y = cursor_hidden foreground = ColorDefault background = ColorDefault in syscall.Handle out syscall.Handle interrupt syscall.Handle charbuf []char_info diffbuf []diff_msg beg_x = -1 beg_y = -1 beg_i = -1 input_comm = make(chan Event) interrupt_comm = make(chan struct{}) cancel_comm = make(chan bool, 1) cancel_done_comm = make(chan bool) alt_mode_esc = false // these ones just to prevent heap allocs at all costs tmp_info console_screen_buffer_info tmp_arg dword tmp_coord0 = coord{0, 0} tmp_coord = coord{0, 0} tmp_rect = small_rect{0, 0, 0, 0} tmp_finfo console_font_info ) func get_cursor_position(out syscall.Handle) coord { err := get_console_screen_buffer_info(out, &tmp_info) if err != nil { panic(err) } return tmp_info.cursor_position } func get_term_size(out syscall.Handle) (coord, small_rect) { err := get_console_screen_buffer_info(out, &tmp_info) if err != nil { panic(err) } return tmp_info.size, tmp_info.window } func get_win_min_size(out syscall.Handle) coord { x, _, err := get_system_metrics.Call(SM_CXMIN) y, _, err := get_system_metrics.Call(SM_CYMIN) if x == 0 || y == 0 { if err != nil { panic(err) } } err1 := get_current_console_font(out, &tmp_finfo) if err1 != nil { panic(err1) } return coord{ x: short(math.Ceil(float64(x) / float64(tmp_finfo.font_size.x))), y: short(math.Ceil(float64(y) / float64(tmp_finfo.font_size.y))), } } func get_win_size(out syscall.Handle) coord { err := get_console_screen_buffer_info(out, &tmp_info) if err != nil { panic(err) } min_size := get_win_min_size(out) size := coord{ x: tmp_info.window.right - tmp_info.window.left + 1, y: tmp_info.window.bottom - tmp_info.window.top + 1, } if size.x < min_size.x { size.x = min_size.x } if size.y < min_size.y { size.y = min_size.y } return size } func fix_win_size(out syscall.Handle, size coord) (err error) { window := small_rect{} window.top = 0 window.bottom = size.y - 1 window.left = 0 window.right = size.x - 1 return set_console_window_info(out, &window) } func update_size_maybe() { size := get_win_size(out) if size.x != term_size.x || size.y != term_size.y { set_console_screen_buffer_size(out, size) fix_win_size(out, size) term_size = size back_buffer.resize(int(size.x), int(size.y)) front_buffer.resize(int(size.x), int(size.y)) front_buffer.clear() clear() area := int(size.x) * int(size.y) if cap(charbuf) < area { charbuf = make([]char_info, 0, area) } } } var color_table_bg = []word{ 0, // default (black) 0, // black background_red, background_green, background_red | background_green, // yellow background_blue, background_red | background_blue, // magenta background_green | background_blue, // cyan background_red | background_blue | background_green, // white } var color_table_fg = []word{ foreground_red | foreground_blue | foreground_green, // default (white) 0, foreground_red, foreground_green, foreground_red | foreground_green, // yellow foreground_blue, foreground_red | foreground_blue, // magenta foreground_green | foreground_blue, // cyan foreground_red | foreground_blue | foreground_green, // white } const ( replacement_char = '\uFFFD' max_rune = '\U0010FFFF' surr1 = 0xd800 surr2 = 0xdc00 surr3 = 0xe000 surr_self = 0x10000 ) func append_diff_line(y int) int { n := 0 for x := 0; x < front_buffer.width; { cell_offset := y*front_buffer.width + x back := &back_buffer.cells[cell_offset] front := &front_buffer.cells[cell_offset] attr, char := cell_to_char_info(*back) charbuf = append(charbuf, char_info{attr: attr, char: char[0]}) *front = *back n++ w := runewidth.RuneWidth(back.Ch) if w == 0 || w == 2 && runewidth.IsAmbiguousWidth(back.Ch) { w = 1 } x += w // If not CJK, fill trailing space with whitespace if !is_cjk && w == 2 { charbuf = append(charbuf, char_info{attr: attr, char: ' '}) } } return n } // compares 'back_buffer' with 'front_buffer' and prepares all changes in the form of // 'diff_msg's in the 'diff_buf' func prepare_diff_messages() { // clear buffers diffbuf = diffbuf[:0] charbuf = charbuf[:0] var diff diff_msg gbeg := 0 for y := 0; y < front_buffer.height; y++ { same := true line_offset := y * front_buffer.width for x := 0; x < front_buffer.width; x++ { cell_offset := line_offset + x back := &back_buffer.cells[cell_offset] front := &front_buffer.cells[cell_offset] if *back != *front { same = false break } } if same && diff.lines > 0 { diffbuf = append(diffbuf, diff) diff = diff_msg{} } if !same { beg := len(charbuf) end := beg + append_diff_line(y) if diff.lines == 0 { diff.pos = short(y) gbeg = beg } diff.lines++ diff.chars = charbuf[gbeg:end] } } if diff.lines > 0 { diffbuf = append(diffbuf, diff) diff = diff_msg{} } } func get_ct(table []word, idx int) word { idx = idx & 0x0F if idx >= len(table) { idx = len(table) - 1 } return table[idx] } func cell_to_char_info(c Cell) (attr word, wc [2]wchar) { attr = get_ct(color_table_fg, int(c.Fg)) | get_ct(color_table_bg, int(c.Bg)) if c.Fg&AttrReverse|c.Bg&AttrReverse != 0 { attr = (attr&0xF0)>>4 | (attr&0x0F)<<4 } if c.Fg&AttrBold != 0 { attr |= foreground_intensity } if c.Bg&AttrBold != 0 { attr |= background_intensity } r0, r1 := utf16.EncodeRune(c.Ch) if r0 == 0xFFFD { wc[0] = wchar(c.Ch) wc[1] = ' ' } else { wc[0] = wchar(r0) wc[1] = wchar(r1) } return } func move_cursor(x, y int) { err := set_console_cursor_position(out, coord{short(x), short(y)}) if err != nil { panic(err) } } func show_cursor(visible bool) { var v int32 if visible { v = 1 } var info console_cursor_info info.size = 100 info.visible = v err := set_console_cursor_info(out, &info) if err != nil { panic(err) } } func clear() { var err error attr, char := cell_to_char_info(Cell{ ' ', foreground, background, }) area := int(term_size.x) * int(term_size.y) err = fill_console_output_attribute(out, attr, area) if err != nil { panic(err) } err = fill_console_output_character(out, char[0], area) if err != nil { panic(err) } if !is_cursor_hidden(cursor_x, cursor_y) { move_cursor(cursor_x, cursor_y) } } func key_event_record_to_event(r *key_event_record) (Event, bool) { if r.key_down == 0 { return Event{}, false } e := Event{Type: EventKey} if input_mode&InputAlt != 0 { if alt_mode_esc { e.Mod = ModAlt alt_mode_esc = false } if r.control_key_state&(left_alt_pressed|right_alt_pressed) != 0 { e.Mod = ModAlt } } ctrlpressed := r.control_key_state&(left_ctrl_pressed|right_ctrl_pressed) != 0 if r.virtual_key_code >= vk_f1 && r.virtual_key_code <= vk_f12 { switch r.virtual_key_code { case vk_f1: e.Key = KeyF1 case vk_f2: e.Key = KeyF2 case vk_f3: e.Key = KeyF3 case vk_f4: e.Key = KeyF4 case vk_f5: e.Key = KeyF5 case vk_f6: e.Key = KeyF6 case vk_f7: e.Key = KeyF7 case vk_f8: e.Key = KeyF8 case vk_f9: e.Key = KeyF9 case vk_f10: e.Key = KeyF10 case vk_f11: e.Key = KeyF11 case vk_f12: e.Key = KeyF12 default: panic("unreachable") } return e, true } if r.virtual_key_code <= vk_delete { switch r.virtual_key_code { case vk_insert: e.Key = KeyInsert case vk_delete: e.Key = KeyDelete case vk_home: e.Key = KeyHome case vk_end: e.Key = KeyEnd case vk_pgup: e.Key = KeyPgup case vk_pgdn: e.Key = KeyPgdn case vk_arrow_up: e.Key = KeyArrowUp case vk_arrow_down: e.Key = KeyArrowDown case vk_arrow_left: e.Key = KeyArrowLeft case vk_arrow_right: e.Key = KeyArrowRight case vk_backspace: if ctrlpressed { e.Key = KeyBackspace2 } else { e.Key = KeyBackspace } case vk_tab: e.Key = KeyTab case vk_enter: if ctrlpressed { e.Key = KeyCtrlJ } else { e.Key = KeyEnter } case vk_esc: switch { case input_mode&InputEsc != 0: e.Key = KeyEsc case input_mode&InputAlt != 0: alt_mode_esc = true return Event{}, false } case vk_space: if ctrlpressed { // manual return here, because KeyCtrlSpace is zero e.Key = KeyCtrlSpace return e, true } else { e.Key = KeySpace } } if e.Key != 0 { return e, true } } if ctrlpressed { if Key(r.unicode_char) >= KeyCtrlA && Key(r.unicode_char) <= KeyCtrlRsqBracket { e.Key = Key(r.unicode_char) if input_mode&InputAlt != 0 && e.Key == KeyEsc { alt_mode_esc = true return Event{}, false } return e, true } switch r.virtual_key_code { case 192, 50: // manual return here, because KeyCtrl2 is zero e.Key = KeyCtrl2 return e, true case 51: if input_mode&InputAlt != 0 { alt_mode_esc = true return Event{}, false } e.Key = KeyCtrl3 case 52: e.Key = KeyCtrl4 case 53: e.Key = KeyCtrl5 case 54: e.Key = KeyCtrl6 case 189, 191, 55: e.Key = KeyCtrl7 case 8, 56: e.Key = KeyCtrl8 } if e.Key != 0 { return e, true } } if r.unicode_char != 0 { e.Ch = rune(r.unicode_char) return e, true } return Event{}, false } func input_event_producer() { var r input_record var err error var last_button Key var last_button_pressed Key var last_state = dword(0) var last_x, last_y = -1, -1 handles := []syscall.Handle{in, interrupt} for { err = wait_for_multiple_objects(handles) if err != nil { input_comm <- Event{Type: EventError, Err: err} } select { case <-cancel_comm: cancel_done_comm <- true return default: } err = read_console_input(in, &r) if err != nil { input_comm <- Event{Type: EventError, Err: err} } switch r.event_type { case key_event: kr := (*key_event_record)(unsafe.Pointer(&r.event)) ev, ok := key_event_record_to_event(kr) if ok { for i := 0; i < int(kr.repeat_count); i++ { input_comm <- ev } } case window_buffer_size_event: sr := *(*window_buffer_size_record)(unsafe.Pointer(&r.event)) input_comm <- Event{ Type: EventResize, Width: int(sr.size.x), Height: int(sr.size.y), } case mouse_event: mr := *(*mouse_event_record)(unsafe.Pointer(&r.event)) ev := Event{Type: EventMouse} switch mr.event_flags { case 0, 2: // single or double click cur_state := mr.button_state switch { case last_state&mouse_lmb == 0 && cur_state&mouse_lmb != 0: last_button = MouseLeft last_button_pressed = last_button case last_state&mouse_rmb == 0 && cur_state&mouse_rmb != 0: last_button = MouseRight last_button_pressed = last_button case last_state&mouse_mmb == 0 && cur_state&mouse_mmb != 0: last_button = MouseMiddle last_button_pressed = last_button case last_state&mouse_lmb != 0 && cur_state&mouse_lmb == 0: last_button = MouseRelease case last_state&mouse_rmb != 0 && cur_state&mouse_rmb == 0: last_button = MouseRelease case last_state&mouse_mmb != 0 && cur_state&mouse_mmb == 0: last_button = MouseRelease default: last_state = cur_state continue } last_state = cur_state ev.Key = last_button last_x, last_y = int(mr.mouse_pos.x), int(mr.mouse_pos.y) ev.MouseX = last_x ev.MouseY = last_y case 1: // mouse motion x, y := int(mr.mouse_pos.x), int(mr.mouse_pos.y) if last_state != 0 && (last_x != x || last_y != y) { ev.Key = last_button_pressed ev.Mod = ModMotion ev.MouseX = x ev.MouseY = y last_x, last_y = x, y } else { ev.Type = EventNone } case 4: // mouse wheel n := int16(mr.button_state >> 16) if n > 0 { ev.Key = MouseWheelUp } else { ev.Key = MouseWheelDown } last_x, last_y = int(mr.mouse_pos.x), int(mr.mouse_pos.y) ev.MouseX = last_x ev.MouseY = last_y default: ev.Type = EventNone } if ev.Type != EventNone { input_comm <- ev } } } }