1
0
mirror of https://github.com/gdamore/tcell.git synced 2025-04-26 13:48:53 +08:00
tcell/console_win.go
Garrett D'Amore 0c473b86d8 fixes #187 24-bit color for Windows 10 console
This works well on the new Windows 10 Terminal, as well as recent
Windows 10 ConHost.  Support for this is automatically enabled if
we detect that the terminal supports ANSI escapes, except for
ConEmu, which has a fairly severe scrolling bug with truecolor.

To opt-in anyway, set TCELL_TRUECOLOR to "enable" in your environment.

To opt-out, set TCELL_TRUECOLOR to "disable" in your environment.
2020-08-25 08:10:11 -07:00

1171 lines
24 KiB
Go

// +build windows
// Copyright 2019 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use file except in compliance with the License.
// You may obtain a copy of the license at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tcell
import (
"errors"
"fmt"
"os"
"strings"
"sync"
"syscall"
"unicode/utf16"
"unsafe"
)
type cScreen struct {
in syscall.Handle
out syscall.Handle
cancelflag syscall.Handle
scandone chan struct{}
evch chan Event
quit chan struct{}
curx int
cury int
style Style
clear bool
fini bool
vten bool
truecolor bool
w int
h int
oscreen consoleInfo
ocursor cursorInfo
oimode uint32
oomode uint32
cells CellBuffer
sync.Mutex
}
var winLock sync.Mutex
var winPalette = []Color{
ColorBlack,
ColorMaroon,
ColorGreen,
ColorNavy,
ColorOlive,
ColorPurple,
ColorTeal,
ColorSilver,
ColorGray,
ColorRed,
ColorLime,
ColorBlue,
ColorYellow,
ColorFuchsia,
ColorAqua,
ColorWhite,
}
var winColors = map[Color]Color{
ColorBlack: ColorBlack,
ColorMaroon: ColorMaroon,
ColorGreen: ColorGreen,
ColorNavy: ColorNavy,
ColorOlive: ColorOlive,
ColorPurple: ColorPurple,
ColorTeal: ColorTeal,
ColorSilver: ColorSilver,
ColorGray: ColorGray,
ColorRed: ColorRed,
ColorLime: ColorLime,
ColorBlue: ColorBlue,
ColorYellow: ColorYellow,
ColorFuchsia: ColorFuchsia,
ColorAqua: ColorAqua,
ColorWhite: ColorWhite,
}
var (
k32 = syscall.NewLazyDLL("kernel32.dll")
u32 = syscall.NewLazyDLL("user32.dll")
)
// We have to bring in the kernel32 and user32 DLLs directly, so we can get
// access to some system calls that the core Go API lacks.
//
// Note that Windows appends some functions with W to indicate that wide
// characters (Unicode) are in use. The documentation refers to them
// without this suffix, as the resolution is made via preprocessor.
var (
procReadConsoleInput = k32.NewProc("ReadConsoleInputW")
procWaitForMultipleObjects = k32.NewProc("WaitForMultipleObjects")
procCreateEvent = k32.NewProc("CreateEventW")
procSetEvent = k32.NewProc("SetEvent")
procGetConsoleCursorInfo = k32.NewProc("GetConsoleCursorInfo")
procSetConsoleCursorInfo = k32.NewProc("SetConsoleCursorInfo")
procSetConsoleCursorPosition = k32.NewProc("SetConsoleCursorPosition")
procSetConsoleMode = k32.NewProc("SetConsoleMode")
procGetConsoleMode = k32.NewProc("GetConsoleMode")
procGetConsoleScreenBufferInfo = k32.NewProc("GetConsoleScreenBufferInfo")
procFillConsoleOutputAttribute = k32.NewProc("FillConsoleOutputAttribute")
procFillConsoleOutputCharacter = k32.NewProc("FillConsoleOutputCharacterW")
procSetConsoleWindowInfo = k32.NewProc("SetConsoleWindowInfo")
procSetConsoleScreenBufferSize = k32.NewProc("SetConsoleScreenBufferSize")
procSetConsoleTextAttribute = k32.NewProc("SetConsoleTextAttribute")
procMessageBeep = u32.NewProc("MessageBeep")
)
const (
w32Infinite = ^uintptr(0)
w32WaitObject0 = uintptr(0)
)
const (
// VT100/XTerm escapes understood by the console
vtShowCursor = "\x1b[?25h"
vtHideCursor = "\x1b[?25l"
vtCursorPos = "\x1b[%d;%dH" // Note that it is Y then X
vtSgr0 = "\x1b[0m"
vtBold = "\x1b[1m"
vtUnderline = "\x1b[4m"
vtBlink = "\x1b[5m" // Not sure this is processed
vtReverse = "\x1b[7m"
vtSetFg = "\x1b[38;2;%d;%d;%dm" // RGB
vtSetBg = "\x1b[48;2;%d;%d;%dm" // RGB
)
// NewConsoleScreen returns a Screen for the Windows console associated
// with the current process. The Screen makes use of the Windows Console
// API to display content and read events.
func NewConsoleScreen() (Screen, error) {
return &cScreen{}, nil
}
func (s *cScreen) Init() error {
s.evch = make(chan Event, 10)
s.quit = make(chan struct{})
s.scandone = make(chan struct{})
in, e := syscall.Open("CONIN$", syscall.O_RDWR, 0)
if e != nil {
return e
}
s.in = in
out, e := syscall.Open("CONOUT$", syscall.O_RDWR, 0)
if e != nil {
syscall.Close(s.in)
return e
}
s.out = out
s.truecolor = true
// ConEmu handling of colors and scrolling when in terminal
// mode is extremely problematic at the best. The color
// palette will scroll even though characters do not, when
// emiting stuff for the last character. In the future we
// might change this to look at specific versions of ConEmu
// if they fix the bug.
if os.Getenv("ConEmuPID") != "" {
s.truecolor = false
}
switch os.Getenv("TCELL_TRUECOLOR") {
case "disable":
s.truecolor = false
case "enable":
s.truecolor = true
}
cf, _, e := procCreateEvent.Call(
uintptr(0),
uintptr(1),
uintptr(0),
uintptr(0))
if cf == uintptr(0) {
return e
}
s.cancelflag = syscall.Handle(cf)
s.Lock()
s.curx = -1
s.cury = -1
s.style = StyleDefault
s.getCursorInfo(&s.ocursor)
s.getConsoleInfo(&s.oscreen)
s.getOutMode(&s.oomode)
s.getInMode(&s.oimode)
s.resize()
s.fini = false
s.setInMode(modeResizeEn | modeExtndFlg)
// 24-bit color is opt-in for now, because we can't figure out
// to make it work consistently.
if s.truecolor {
s.setOutMode(modeVtOutput | modeNoAutoNL | modeCookedOut)
var omode uint32
s.getOutMode(&omode)
if omode&modeVtOutput == modeVtOutput {
s.vten = true
} else {
s.truecolor = false
}
} else {
s.setOutMode(0)
}
s.clearScreen(s.style)
s.hideCursor()
s.Unlock()
go s.scanInput()
return nil
}
func (s *cScreen) CharacterSet() string {
// We are always UTF-16LE on Windows
return "UTF-16LE"
}
func (s *cScreen) EnableMouse() {
s.setInMode(modeResizeEn | modeMouseEn | modeExtndFlg)
}
func (s *cScreen) DisableMouse() {
s.setInMode(modeResizeEn | modeExtndFlg)
}
func (s *cScreen) Fini() {
s.Lock()
s.style = StyleDefault
s.curx = -1
s.cury = -1
s.fini = true
s.vten = false
s.Unlock()
s.setCursorInfo(&s.ocursor)
s.setInMode(s.oimode)
s.setOutMode(s.oomode)
s.setBufferSize(int(s.oscreen.size.x), int(s.oscreen.size.y))
s.clearScreen(StyleDefault)
s.setCursorPos(0, 0)
procSetConsoleTextAttribute.Call(
uintptr(s.out),
uintptr(s.mapStyle(StyleDefault)))
close(s.quit)
procSetEvent.Call(uintptr(s.cancelflag))
// Block until scanInput returns; this prevents a race condition on Win 8+
// which causes syscall.Close to block until another keypress is read.
<-s.scandone
syscall.Close(s.in)
syscall.Close(s.out)
}
func (s *cScreen) PostEventWait(ev Event) {
s.evch <- ev
}
func (s *cScreen) PostEvent(ev Event) error {
select {
case s.evch <- ev:
return nil
default:
return ErrEventQFull
}
}
func (s *cScreen) PollEvent() Event {
select {
case <-s.quit:
return nil
case ev := <-s.evch:
return ev
}
}
type cursorInfo struct {
size uint32
visible uint32
}
type coord struct {
x int16
y int16
}
func (c coord) uintptr() uintptr {
// little endian, put x first
return uintptr(c.x) | (uintptr(c.y) << 16)
}
type rect struct {
left int16
top int16
right int16
bottom int16
}
func (s *cScreen) emitVtString(vs string) {
esc := utf16.Encode([]rune(vs))
syscall.WriteConsole(s.out, &esc[0], uint32(len(esc)), nil, nil)
}
func (s *cScreen) showCursor() {
if s.vten {
s.emitVtString(vtShowCursor)
} else {
s.setCursorInfo(&cursorInfo{size: 100, visible: 1})
}
}
func (s *cScreen) hideCursor() {
if s.vten {
s.emitVtString(vtHideCursor)
} else {
s.setCursorInfo(&cursorInfo{size: 1, visible: 0})
}
}
func (s *cScreen) ShowCursor(x, y int) {
s.Lock()
if !s.fini {
s.curx = x
s.cury = y
}
s.doCursor()
s.Unlock()
}
func (s *cScreen) doCursor() {
x, y := s.curx, s.cury
if x < 0 || y < 0 || x >= s.w || y >= s.h {
s.hideCursor()
} else {
s.setCursorPos(x, y)
s.showCursor()
}
}
func (s *cScreen) HideCursor() {
s.ShowCursor(-1, -1)
}
type inputRecord struct {
typ uint16
_ uint16
data [16]byte
}
const (
keyEvent uint16 = 1
mouseEvent uint16 = 2
resizeEvent uint16 = 4
menuEvent uint16 = 8 // don't use
focusEvent uint16 = 16 // don't use
)
type mouseRecord struct {
x int16
y int16
btns uint32
mod uint32
flags uint32
}
const (
mouseDoubleClick uint32 = 0x2
mouseHWheeled uint32 = 0x8
mouseVWheeled uint32 = 0x4
mouseMoved uint32 = 0x1
)
type resizeRecord struct {
x int16
y int16
}
type keyRecord struct {
isdown int32
repeat uint16
kcode uint16
scode uint16
ch uint16
mod uint32
}
const (
// Constants per Microsoft. We don't put the modifiers
// here.
vkCancel = 0x03
vkBack = 0x08 // Backspace
vkTab = 0x09
vkClear = 0x0c
vkReturn = 0x0d
vkPause = 0x13
vkEscape = 0x1b
vkSpace = 0x20
vkPrior = 0x21 // PgUp
vkNext = 0x22 // PgDn
vkEnd = 0x23
vkHome = 0x24
vkLeft = 0x25
vkUp = 0x26
vkRight = 0x27
vkDown = 0x28
vkPrint = 0x2a
vkPrtScr = 0x2c
vkInsert = 0x2d
vkDelete = 0x2e
vkHelp = 0x2f
vkF1 = 0x70
vkF2 = 0x71
vkF3 = 0x72
vkF4 = 0x73
vkF5 = 0x74
vkF6 = 0x75
vkF7 = 0x76
vkF8 = 0x77
vkF9 = 0x78
vkF10 = 0x79
vkF11 = 0x7a
vkF12 = 0x7b
vkF13 = 0x7c
vkF14 = 0x7d
vkF15 = 0x7e
vkF16 = 0x7f
vkF17 = 0x80
vkF18 = 0x81
vkF19 = 0x82
vkF20 = 0x83
vkF21 = 0x84
vkF22 = 0x85
vkF23 = 0x86
vkF24 = 0x87
)
var vkKeys = map[uint16]Key{
vkCancel: KeyCancel,
vkBack: KeyBackspace,
vkTab: KeyTab,
vkClear: KeyClear,
vkPause: KeyPause,
vkPrint: KeyPrint,
vkPrtScr: KeyPrint,
vkPrior: KeyPgUp,
vkNext: KeyPgDn,
vkReturn: KeyEnter,
vkEnd: KeyEnd,
vkHome: KeyHome,
vkLeft: KeyLeft,
vkUp: KeyUp,
vkRight: KeyRight,
vkDown: KeyDown,
vkInsert: KeyInsert,
vkDelete: KeyDelete,
vkHelp: KeyHelp,
vkF1: KeyF1,
vkF2: KeyF2,
vkF3: KeyF3,
vkF4: KeyF4,
vkF5: KeyF5,
vkF6: KeyF6,
vkF7: KeyF7,
vkF8: KeyF8,
vkF9: KeyF9,
vkF10: KeyF10,
vkF11: KeyF11,
vkF12: KeyF12,
vkF13: KeyF13,
vkF14: KeyF14,
vkF15: KeyF15,
vkF16: KeyF16,
vkF17: KeyF17,
vkF18: KeyF18,
vkF19: KeyF19,
vkF20: KeyF20,
vkF21: KeyF21,
vkF22: KeyF22,
vkF23: KeyF23,
vkF24: KeyF24,
}
// NB: All Windows platforms are little endian. We assume this
// never, ever change. The following code is endian safe. and does
// not use unsafe pointers.
func getu32(v []byte) uint32 {
return uint32(v[0]) + (uint32(v[1]) << 8) + (uint32(v[2]) << 16) + (uint32(v[3]) << 24)
}
func geti32(v []byte) int32 {
return int32(getu32(v))
}
func getu16(v []byte) uint16 {
return uint16(v[0]) + (uint16(v[1]) << 8)
}
func geti16(v []byte) int16 {
return int16(getu16(v))
}
// Convert windows dwControlKeyState to modifier mask
func mod2mask(cks uint32) ModMask {
mm := ModNone
// Left or right control
if (cks & (0x0008 | 0x0004)) != 0 {
mm |= ModCtrl
}
// Left or right alt
if (cks & (0x0002 | 0x0001)) != 0 {
mm |= ModAlt
}
// Any shift
if (cks & 0x0010) != 0 {
mm |= ModShift
}
return mm
}
func mrec2btns(mbtns, flags uint32) ButtonMask {
btns := ButtonNone
if mbtns&0x1 != 0 {
btns |= Button1
}
if mbtns&0x2 != 0 {
btns |= Button2
}
if mbtns&0x4 != 0 {
btns |= Button3
}
if mbtns&0x8 != 0 {
btns |= Button4
}
if mbtns&0x10 != 0 {
btns |= Button5
}
if mbtns&0x20 != 0 {
btns |= Button6
}
if mbtns&0x40 != 0 {
btns |= Button7
}
if mbtns&0x80 != 0 {
btns |= Button8
}
if flags&mouseVWheeled != 0 {
if mbtns&0x80000000 == 0 {
btns |= WheelUp
} else {
btns |= WheelDown
}
}
if flags&mouseHWheeled != 0 {
if mbtns&0x80000000 == 0 {
btns |= WheelRight
} else {
btns |= WheelLeft
}
}
return btns
}
func (s *cScreen) getConsoleInput() error {
// cancelFlag comes first as WaitForMultipleObjects returns the lowest index
// in the event that both events are signalled.
waitObjects := []syscall.Handle{s.cancelflag, s.in}
// As arrays are contiguous in memory, a pointer to the first object is the
// same as a pointer to the array itself.
pWaitObjects := unsafe.Pointer(&waitObjects[0])
rv, _, er := procWaitForMultipleObjects.Call(
uintptr(len(waitObjects)),
uintptr(pWaitObjects),
uintptr(0),
w32Infinite)
// WaitForMultipleObjects returns WAIT_OBJECT_0 + the index.
switch rv {
case w32WaitObject0: // s.cancelFlag
return errors.New("cancelled")
case w32WaitObject0 + 1: // s.in
rec := &inputRecord{}
var nrec int32
rv, _, er := procReadConsoleInput.Call(
uintptr(s.in),
uintptr(unsafe.Pointer(rec)),
uintptr(1),
uintptr(unsafe.Pointer(&nrec)))
if rv == 0 {
return er
}
if nrec != 1 {
return nil
}
switch rec.typ {
case keyEvent:
krec := &keyRecord{}
krec.isdown = geti32(rec.data[0:])
krec.repeat = getu16(rec.data[4:])
krec.kcode = getu16(rec.data[6:])
krec.scode = getu16(rec.data[8:])
krec.ch = getu16(rec.data[10:])
krec.mod = getu32(rec.data[12:])
if krec.isdown == 0 || krec.repeat < 1 {
// its a key release event, ignore it
return nil
}
if krec.ch != 0 {
// synthesized key code
for krec.repeat > 0 {
// convert shift+tab to backtab
if mod2mask(krec.mod) == ModShift && krec.ch == vkTab {
s.PostEvent(NewEventKey(KeyBacktab, 0,
ModNone))
} else {
s.PostEvent(NewEventKey(KeyRune, rune(krec.ch),
mod2mask(krec.mod)))
}
krec.repeat--
}
return nil
}
key := KeyNUL // impossible on Windows
ok := false
if key, ok = vkKeys[krec.kcode]; !ok {
return nil
}
for krec.repeat > 0 {
s.PostEvent(NewEventKey(key, rune(krec.ch),
mod2mask(krec.mod)))
krec.repeat--
}
case mouseEvent:
var mrec mouseRecord
mrec.x = geti16(rec.data[0:])
mrec.y = geti16(rec.data[2:])
mrec.btns = getu32(rec.data[4:])
mrec.mod = getu32(rec.data[8:])
mrec.flags = getu32(rec.data[12:])
btns := mrec2btns(mrec.btns, mrec.flags)
// we ignore double click, events are delivered normally
s.PostEvent(NewEventMouse(int(mrec.x), int(mrec.y), btns,
mod2mask(mrec.mod)))
case resizeEvent:
var rrec resizeRecord
rrec.x = geti16(rec.data[0:])
rrec.y = geti16(rec.data[2:])
s.PostEvent(NewEventResize(int(rrec.x), int(rrec.y)))
default:
}
default:
return er
}
return nil
}
func (s *cScreen) scanInput() {
for {
if e := s.getConsoleInput(); e != nil {
close(s.scandone)
return
}
}
}
// Windows console can display 8 characters, in either low or high intensity
func (s *cScreen) Colors() int {
if s.vten {
return 1 << 24
}
return 16
}
var vgaColors = map[Color]uint16{
ColorBlack: 0,
ColorMaroon: 0x4,
ColorGreen: 0x2,
ColorNavy: 0x1,
ColorOlive: 0x6,
ColorPurple: 0x5,
ColorTeal: 0x3,
ColorSilver: 0x7,
ColorGrey: 0x8,
ColorRed: 0xc,
ColorLime: 0xa,
ColorBlue: 0x9,
ColorYellow: 0xe,
ColorFuchsia: 0xd,
ColorAqua: 0xb,
ColorWhite: 0xf,
}
// Windows uses RGB signals
func mapColor2RGB(c Color) uint16 {
winLock.Lock()
if v, ok := winColors[c]; ok {
c = v
} else {
v = FindColor(c, winPalette)
winColors[c] = v
c = v
}
winLock.Unlock()
if vc, ok := vgaColors[c]; ok {
return vc
}
return 0
}
// Map a tcell style to Windows attributes
func (s *cScreen) mapStyle(style Style) uint16 {
f, b, a := style.Decompose()
fa := s.oscreen.attrs & 0xf
ba := (s.oscreen.attrs) >> 4 & 0xf
if f != ColorDefault {
fa = mapColor2RGB(f)
}
if b != ColorDefault {
ba = mapColor2RGB(b)
}
var attr uint16
// We simulate reverse by doing the color swap ourselves.
// Apparently windows cannot really do this except in DBCS
// views.
if a&AttrReverse != 0 {
attr = ba
attr |= (fa << 4)
} else {
attr = fa
attr |= (ba << 4)
}
if a&AttrBold != 0 {
attr |= 0x8
}
if a&AttrDim != 0 {
attr &^= 0x8
}
if a&AttrUnderline != 0 {
// Best effort -- doesn't seem to work though.
attr |= 0x8000
}
// Blink is unsupported
return attr
}
func (s *cScreen) SetCell(x, y int, style Style, ch ...rune) {
if len(ch) > 0 {
s.SetContent(x, y, ch[0], ch[1:], style)
} else {
s.SetContent(x, y, ' ', nil, style)
}
}
func (s *cScreen) SetContent(x, y int, mainc rune, combc []rune, style Style) {
s.Lock()
if !s.fini {
s.cells.SetContent(x, y, mainc, combc, style)
}
s.Unlock()
}
func (s *cScreen) GetContent(x, y int) (rune, []rune, Style, int) {
s.Lock()
mainc, combc, style, width := s.cells.GetContent(x, y)
s.Unlock()
return mainc, combc, style, width
}
func (s *cScreen) sendVtStyle(style Style) {
esc := &strings.Builder{}
fg, bg, attrs := style.Decompose()
esc.WriteString(vtSgr0)
if attrs&(AttrBold|AttrDim) == AttrBold {
esc.WriteString(vtBold)
}
if attrs&AttrBlink != 0 {
esc.WriteString(vtBlink)
}
if attrs&AttrUnderline != 0 {
esc.WriteString(vtUnderline)
}
if attrs&AttrReverse != 0 {
esc.WriteString(vtReverse)
}
if fg != ColorDefault {
r, g, b := fg.RGB()
fmt.Fprintf(esc, vtSetFg, r, g, b)
}
if bg != ColorDefault {
r, g, b := bg.RGB()
fmt.Fprintf(esc, vtSetBg, r, g, b)
}
s.emitVtString(esc.String())
}
func (s *cScreen) writeString(x, y int, style Style, ch []uint16) {
// we assume the caller has hidden the cursor
if len(ch) == 0 {
return
}
s.setCursorPos(x, y)
if s.vten {
s.sendVtStyle(style)
} else {
procSetConsoleTextAttribute.Call(
uintptr(s.out),
uintptr(s.mapStyle(style)))
}
syscall.WriteConsole(s.out, &ch[0], uint32(len(ch)), nil, nil)
}
func (s *cScreen) draw() {
// allocate a scratch line bit enough for no combining chars.
// if you have combining characters, you may pay for extra allocs.
if s.clear {
s.clearScreen(s.style)
s.clear = false
s.cells.Invalidate()
}
buf := make([]uint16, 0, s.w)
wcs := buf[:]
lstyle := Style(-1) // invalid attribute
lx, ly := -1, -1
ra := make([]rune, 1)
for y := 0; y < int(s.h); y++ {
for x := 0; x < int(s.w); x++ {
mainc, combc, style, width := s.cells.GetContent(x, y)
dirty := s.cells.Dirty(x, y)
if style == StyleDefault {
style = s.style
}
if !dirty || style != lstyle {
// write out any data queued thus far
// because we are going to skip over some
// cells, or because we need to change styles
s.writeString(lx, ly, lstyle, wcs)
wcs = buf[0:0]
lstyle = Style(-1)
if !dirty {
continue
}
}
if x > s.w-width {
mainc = ' '
combc = nil
width = 1
}
if len(wcs) == 0 {
lstyle = style
lx = x
ly = y
}
ra[0] = mainc
wcs = append(wcs, utf16.Encode(ra)...)
if len(combc) != 0 {
wcs = append(wcs, utf16.Encode(combc)...)
}
for dx := 0; dx < width; dx++ {
s.cells.SetDirty(x+dx, y, false)
}
x += width - 1
}
s.writeString(lx, ly, lstyle, wcs)
wcs = buf[0:0]
lstyle = Style(-1)
}
}
func (s *cScreen) Show() {
s.Lock()
if !s.fini {
s.hideCursor()
s.resize()
s.draw()
s.doCursor()
}
s.Unlock()
}
func (s *cScreen) Sync() {
s.Lock()
if !s.fini {
s.cells.Invalidate()
s.hideCursor()
s.resize()
s.draw()
s.doCursor()
}
s.Unlock()
}
type consoleInfo struct {
size coord
pos coord
attrs uint16
win rect
maxsz coord
}
func (s *cScreen) getConsoleInfo(info *consoleInfo) {
procGetConsoleScreenBufferInfo.Call(
uintptr(s.out),
uintptr(unsafe.Pointer(info)))
}
func (s *cScreen) getCursorInfo(info *cursorInfo) {
procGetConsoleCursorInfo.Call(
uintptr(s.out),
uintptr(unsafe.Pointer(info)))
}
func (s *cScreen) setCursorInfo(info *cursorInfo) {
procSetConsoleCursorInfo.Call(
uintptr(s.out),
uintptr(unsafe.Pointer(info)))
}
func (s *cScreen) setCursorPos(x, y int) {
if s.vten {
// Note that the string is Y first. Origin is 1,1.
s.emitVtString(fmt.Sprintf(vtCursorPos, y+1, x+1))
} else {
procSetConsoleCursorPosition.Call(
uintptr(s.out),
coord{int16(x), int16(y)}.uintptr())
}
}
func (s *cScreen) setBufferSize(x, y int) {
procSetConsoleScreenBufferSize.Call(
uintptr(s.out),
coord{int16(x), int16(y)}.uintptr())
}
func (s *cScreen) Size() (int, int) {
s.Lock()
w, h := s.w, s.h
s.Unlock()
return w, h
}
func (s *cScreen) resize() {
info := consoleInfo{}
s.getConsoleInfo(&info)
w := int((info.win.right - info.win.left) + 1)
h := int((info.win.bottom - info.win.top) + 1)
if s.w == w && s.h == h {
return
}
s.cells.Resize(w, h)
s.w = w
s.h = h
s.setBufferSize(w, h)
r := rect{0, 0, int16(w - 1), int16(h - 1)}
procSetConsoleWindowInfo.Call(
uintptr(s.out),
uintptr(1),
uintptr(unsafe.Pointer(&r)))
s.PostEvent(NewEventResize(w, h))
}
func (s *cScreen) Clear() {
s.Fill(' ', s.style)
}
func (s *cScreen) Fill(r rune, style Style) {
s.Lock()
if !s.fini {
s.cells.Fill(r, style)
s.clear = true
}
s.Unlock()
}
func (s *cScreen) clearScreen(style Style) {
if s.vten {
s.sendVtStyle(style)
row := strings.Repeat(" ", s.w)
for y := 0; y < s.h; y++ {
s.setCursorPos(0, y)
s.emitVtString(row)
}
s.setCursorPos(0, 0)
} else {
pos := coord{0, 0}
attr := s.mapStyle(style)
x, y := s.w, s.h
scratch := uint32(0)
count := uint32(x * y)
procFillConsoleOutputAttribute.Call(
uintptr(s.out),
uintptr(attr),
uintptr(count),
pos.uintptr(),
uintptr(unsafe.Pointer(&scratch)))
procFillConsoleOutputCharacter.Call(
uintptr(s.out),
uintptr(' '),
uintptr(count),
pos.uintptr(),
uintptr(unsafe.Pointer(&scratch)))
}
}
const (
// Input modes
modeExtndFlg uint32 = 0x0080
modeMouseEn = 0x0010
modeResizeEn = 0x0008
modeCooked = 0x0001
modeVtInput = 0x0200
// Output modes
modeCookedOut uint32 = 0x0001
modeWrapEOL = 0x0002
modeVtOutput = 0x0004
modeNoAutoNL = 0x0008
)
func (s *cScreen) setInMode(mode uint32) error {
rv, _, err := procSetConsoleMode.Call(
uintptr(s.in),
uintptr(mode))
if rv == 0 {
return err
}
return nil
}
func (s *cScreen) setOutMode(mode uint32) error {
rv, _, err := procSetConsoleMode.Call(
uintptr(s.out),
uintptr(mode))
if rv == 0 {
return err
}
return nil
}
func (s *cScreen) getInMode(v *uint32) {
procGetConsoleMode.Call(
uintptr(s.in),
uintptr(unsafe.Pointer(v)))
}
func (s *cScreen) getOutMode(v *uint32) {
procGetConsoleMode.Call(
uintptr(s.out),
uintptr(unsafe.Pointer(v)))
}
func (s *cScreen) SetStyle(style Style) {
s.Lock()
s.style = style
s.Unlock()
}
// No fallback rune support, since we have Unicode. Yay!
func (s *cScreen) RegisterRuneFallback(r rune, subst string) {
}
func (s *cScreen) UnregisterRuneFallback(r rune) {
}
func (s *cScreen) CanDisplay(r rune, checkFallbacks bool) bool {
// We presume we can display anything -- we're Unicode.
// (Sadly this not precisely true. Combinings are especially
// poorly supported under Windows.)
return true
}
func (s *cScreen) HasMouse() bool {
return true
}
func (s *cScreen) Resize(int, int, int, int) {}
func (s *cScreen) HasKey(k Key) bool {
// Microsoft has codes for some keys, but they are unusual,
// so we don't include them. We include all the typical
// 101, 105 key layout keys.
valid := map[Key]bool{
KeyBackspace: true,
KeyTab: true,
KeyEscape: true,
KeyPause: true,
KeyPrint: true,
KeyPgUp: true,
KeyPgDn: true,
KeyEnter: true,
KeyEnd: true,
KeyHome: true,
KeyLeft: true,
KeyUp: true,
KeyRight: true,
KeyDown: true,
KeyInsert: true,
KeyDelete: true,
KeyF1: true,
KeyF2: true,
KeyF3: true,
KeyF4: true,
KeyF5: true,
KeyF6: true,
KeyF7: true,
KeyF8: true,
KeyF9: true,
KeyF10: true,
KeyF11: true,
KeyF12: true,
KeyRune: true,
}
return valid[k]
}
func (s *cScreen) Beep() error {
// A simple beep. If the sound card is not available, the sound is generated
// using the speaker.
//
// Reference:
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messagebeep
const simpleBeep = 0xffffffff
if rv, _, err := procMessageBeep.Call(simpleBeep); rv == 0 {
return err
}
return nil
}