mirror of https://github.com/gdamore/tcell.git
988 lines
19 KiB
Go
988 lines
19 KiB
Go
// +build windows
|
|
|
|
// Copyright 2015 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 (
|
|
"sync"
|
|
"syscall"
|
|
"unicode/utf16"
|
|
"unsafe"
|
|
)
|
|
|
|
type cScreen struct {
|
|
in syscall.Handle
|
|
out syscall.Handle
|
|
mbtns uint32 // debounce mouse buttons
|
|
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.
|
|
// We have to bring in the kernel32.dll directly, so we can get access to some
|
|
// system calls that the core Go API lacks.
|
|
|
|
var (
|
|
procReadConsoleInput = k32.NewProc("ReadConsoleInputW")
|
|
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")
|
|
)
|
|
|
|
// 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{})
|
|
|
|
if in, e := syscall.Open("CONIN$", syscall.O_RDWR, 0); e != nil {
|
|
return e
|
|
} else {
|
|
s.in = in
|
|
}
|
|
if out, e := syscall.Open("CONOUT$", syscall.O_RDWR, 0); e != nil {
|
|
syscall.Close(s.in)
|
|
return e
|
|
} else {
|
|
s.out = out
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
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(mapStyle(StyleDefault)))
|
|
|
|
close(s.quit)
|
|
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.setCursorPos(0, 0)
|
|
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
|
|
)
|
|
|
|
// 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 (s *cScreen) getConsoleInput() error {
|
|
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 {
|
|
s.PostEvent(NewEventKey(KeyRune, rune(krec.ch),
|
|
mod2mask(krec.mod)))
|
|
krec.repeat--
|
|
}
|
|
return nil
|
|
}
|
|
key := KeyNUL // impossible on Windows
|
|
switch krec.kcode {
|
|
case vkCancel:
|
|
key = KeyCancel
|
|
case vkBack:
|
|
key = KeyBackspace
|
|
case vkTab:
|
|
key = KeyTab
|
|
case vkClear:
|
|
key = KeyClear
|
|
case vkPause:
|
|
key = KeyPause
|
|
case vkPrint, vkPrtScr:
|
|
key = KeyPrint
|
|
case vkPrior:
|
|
key = KeyPgUp
|
|
case vkNext:
|
|
key = KeyPgDn
|
|
case vkReturn:
|
|
key = KeyEnter
|
|
case vkEnd:
|
|
key = KeyEnd
|
|
case vkHome:
|
|
key = KeyHome
|
|
case vkLeft:
|
|
key = KeyLeft
|
|
case vkUp:
|
|
key = KeyUp
|
|
case vkRight:
|
|
key = KeyRight
|
|
case vkDown:
|
|
key = KeyDown
|
|
case vkInsert:
|
|
key = KeyInsert
|
|
case vkDelete:
|
|
key = KeyDelete
|
|
case vkHelp:
|
|
key = KeyHelp
|
|
case vkF1:
|
|
key = KeyF1
|
|
case vkF2:
|
|
key = KeyF2
|
|
case vkF3:
|
|
key = KeyF3
|
|
case vkF4:
|
|
key = KeyF4
|
|
case vkF5:
|
|
key = KeyF5
|
|
case vkF6:
|
|
key = KeyF6
|
|
case vkF7:
|
|
key = KeyF7
|
|
case vkF8:
|
|
key = KeyF8
|
|
case vkF9:
|
|
key = KeyF9
|
|
case vkF10:
|
|
key = KeyF10
|
|
case vkF11:
|
|
key = KeyF11
|
|
case vkF12:
|
|
key = KeyF12
|
|
case vkF13:
|
|
key = KeyF13
|
|
case vkF14:
|
|
key = KeyF14
|
|
case vkF15:
|
|
key = KeyF15
|
|
case vkF16:
|
|
key = KeyF16
|
|
case vkF17:
|
|
key = KeyF17
|
|
case vkF18:
|
|
key = KeyF18
|
|
case vkF19:
|
|
key = KeyF19
|
|
case vkF20:
|
|
key = KeyF20
|
|
case vkF21:
|
|
key = KeyF21
|
|
case vkF22:
|
|
key = KeyF22
|
|
case vkF23:
|
|
key = KeyF23
|
|
case vkF24:
|
|
key = KeyF24
|
|
default:
|
|
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:]) // not using yet
|
|
btns := ButtonNone
|
|
|
|
s.mbtns = mrec.btns
|
|
if mrec.btns&0x1 != 0 {
|
|
btns |= Button1
|
|
}
|
|
if mrec.btns&0x2 != 0 {
|
|
btns |= Button2
|
|
}
|
|
if mrec.btns&0x4 != 0 {
|
|
btns |= Button3
|
|
}
|
|
if mrec.btns&0x8 != 0 {
|
|
btns |= Button4
|
|
}
|
|
if mrec.btns&0x10 != 0 {
|
|
btns |= Button5
|
|
}
|
|
if mrec.btns&0x20 != 0 {
|
|
btns |= Button6
|
|
}
|
|
if mrec.btns&0x40 != 0 {
|
|
btns |= Button7
|
|
}
|
|
if mrec.btns&0x80 != 0 {
|
|
btns |= Button8
|
|
}
|
|
|
|
if mrec.flags&mouseVWheeled != 0 {
|
|
if mrec.btns&0x80000000 == 0 {
|
|
btns |= WheelUp
|
|
} else {
|
|
btns |= WheelDown
|
|
}
|
|
}
|
|
if mrec.flags&mouseHWheeled != 0 {
|
|
if mrec.btns&0x80000000 == 0 {
|
|
btns |= WheelRight
|
|
} else {
|
|
btns |= WheelLeft
|
|
}
|
|
}
|
|
// 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:
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *cScreen) scanInput() {
|
|
for {
|
|
if e := s.getConsoleInput(); e != nil {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// Windows console can display 8 characters, in either low or high intensity
|
|
func (s *cScreen) Colors() int {
|
|
return 16
|
|
}
|
|
|
|
// 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()
|
|
|
|
switch c {
|
|
case ColorBlack:
|
|
return 0
|
|
// primaries
|
|
case ColorMaroon:
|
|
return 0x4
|
|
case ColorGreen:
|
|
return 0x2
|
|
case ColorNavy:
|
|
return 0x1
|
|
case ColorOlive:
|
|
return 0x6
|
|
case ColorPurple:
|
|
return 0x5
|
|
case ColorTeal:
|
|
return 0x3
|
|
case ColorSilver:
|
|
return 0x7
|
|
// bright variants
|
|
case ColorGrey:
|
|
return 0x8
|
|
case ColorRed:
|
|
return 0xc
|
|
case ColorLime:
|
|
return 0xa
|
|
case ColorBlue:
|
|
return 0x9
|
|
case ColorYellow:
|
|
return 0xe
|
|
case ColorFuchsia:
|
|
return 0xd
|
|
case ColorAqua:
|
|
return 0xb
|
|
case ColorWhite:
|
|
return 0xf
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// Map a tcell style to Windows attributes
|
|
func mapStyle(style Style) uint16 {
|
|
f, b, a := style.Decompose()
|
|
if f == ColorDefault {
|
|
f = ColorWhite
|
|
}
|
|
if b == ColorDefault {
|
|
b = ColorBlack
|
|
}
|
|
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 = mapColor2RGB(b)
|
|
attr |= (mapColor2RGB(f) << 4)
|
|
} else {
|
|
attr = mapColor2RGB(f)
|
|
attr |= (mapColor2RGB(b) << 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(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)...)
|
|
}
|
|
s.cells.SetDirty(x, 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
|
|
|
|
r := rect{0, 0, int16(w - 1), int16(h - 1)}
|
|
procSetConsoleWindowInfo.Call(
|
|
uintptr(s.out),
|
|
uintptr(1),
|
|
uintptr(unsafe.Pointer(&r)))
|
|
|
|
s.setBufferSize(w, h)
|
|
|
|
s.PostEvent(NewEventResize(w, h))
|
|
}
|
|
|
|
func (s *cScreen) Clear() {
|
|
s.Lock()
|
|
if !s.fini {
|
|
s.cells.Fill(' ', s.style)
|
|
s.clear = true
|
|
}
|
|
s.Unlock()
|
|
}
|
|
|
|
func (s *cScreen) clearScreen(style Style) {
|
|
pos := coord{0, 0}
|
|
attr := 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 (
|
|
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) Resize(int, int, int, int) {}
|