mirror of
https://github.com/gdamore/tcell.git
synced 2025-04-26 13:48:53 +08:00

A gowid user reported that mouse movement in their gowid(tcell) application was causing the console cursor to flicker on windows, jumping from the current position in the console to the top left corner, and back again. In part, this is an artifact of the way I render a gowid "canvas" - calling HideCursor() unilaterally, then calling ShowCursor(x, y) if the canvas contains a logical cursor that is within bounds. Because currently every mouse event causes a gowid application redraw, on windows the effect is to cause the cursor to jump to the top left briefly, disappear, then reappear in the previous position. You can see it most easily by moving the mouse rapidly left and right. An upstream fix is for me to only call HideCursor() if I know I'm not going to call ShowCursor(x, y). But the flicker can also be eliminated by removing the call to setCursorPos(0, 0) in console_win.go before hiding the cursor. The single call to showCursor() in console_win.go is preceded by setting cursor coordinates explicitly, so I don't think anything depends on the assumption that a hidden cursor has been set to position (0, 0) first.
1034 lines
21 KiB
Go
1034 lines
21 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"
|
|
"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
|
|
|
|
w int
|
|
h int
|
|
|
|
oscreen consoleInfo
|
|
ocursor cursorInfo
|
|
oimode uint32
|
|
oomode uint32
|
|
cells CellBuffer
|
|
colors map[Color]Color
|
|
|
|
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")
|
|
|
|
// We have to bring in the kernel32.dll 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")
|
|
)
|
|
|
|
const (
|
|
w32Infinite = ^uintptr(0)
|
|
w32WaitObject0 = uintptr(0)
|
|
)
|
|
|
|
// 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
|
|
|
|
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)
|
|
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)
|
|
}
|
|
|
|
func (s *cScreen) Fini() {
|
|
s.Lock()
|
|
s.style = StyleDefault
|
|
s.curx = -1
|
|
s.cury = -1
|
|
s.fini = true
|
|
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) showCursor() {
|
|
s.setCursorInfo(&cursorInfo{size: 100, visible: 1})
|
|
}
|
|
|
|
func (s *cScreen) hideCursor() {
|
|
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 charInfo struct {
|
|
ch uint16
|
|
attr uint16
|
|
}
|
|
|
|
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 {
|
|
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) writeString(x, y int, style Style, ch []uint16) {
|
|
// we assume the caller has hidden the cursor
|
|
if len(ch) == 0 {
|
|
return
|
|
}
|
|
nw := uint32(len(ch))
|
|
procSetConsoleTextAttribute.Call(
|
|
uintptr(s.out),
|
|
uintptr(s.mapStyle(style)))
|
|
s.setCursorPos(x, y)
|
|
syscall.WriteConsole(s.out, &ch[0], nw, &nw, 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) {
|
|
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) {
|
|
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 (
|
|
modeExtndFlg uint32 = 0x0080
|
|
modeMouseEn uint32 = 0x0010
|
|
modeResizeEn uint32 = 0x0008
|
|
modeWrapEOL uint32 = 0x0002
|
|
modeCooked uint32 = 0x0001
|
|
)
|
|
|
|
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]
|
|
}
|