mirror of https://github.com/gdamore/tcell.git
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.
This commit is contained in:
parent
776d98d385
commit
0c473b86d8
202
console_win.go
202
console_win.go
|
@ -18,6 +18,9 @@ package tcell
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unicode/utf16"
|
||||
|
@ -36,6 +39,8 @@ type cScreen struct {
|
|||
style Style
|
||||
clear bool
|
||||
fini bool
|
||||
vten bool
|
||||
truecolor bool
|
||||
|
||||
w int
|
||||
h int
|
||||
|
@ -45,7 +50,6 @@ type cScreen struct {
|
|||
oimode uint32
|
||||
oomode uint32
|
||||
cells CellBuffer
|
||||
colors map[Color]Color
|
||||
|
||||
sync.Mutex
|
||||
}
|
||||
|
@ -125,6 +129,20 @@ const (
|
|||
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.
|
||||
|
@ -149,6 +167,24 @@ func (s *cScreen) Init() error {
|
|||
}
|
||||
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),
|
||||
|
@ -171,8 +207,23 @@ func (s *cScreen) Init() error {
|
|||
s.resize()
|
||||
|
||||
s.fini = false
|
||||
s.setInMode(modeResizeEn)
|
||||
s.setOutMode(0)
|
||||
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()
|
||||
|
@ -191,7 +242,7 @@ func (s *cScreen) EnableMouse() {
|
|||
}
|
||||
|
||||
func (s *cScreen) DisableMouse() {
|
||||
s.setInMode(modeResizeEn)
|
||||
s.setInMode(modeResizeEn | modeExtndFlg)
|
||||
}
|
||||
|
||||
func (s *cScreen) Fini() {
|
||||
|
@ -200,6 +251,7 @@ func (s *cScreen) Fini() {
|
|||
s.curx = -1
|
||||
s.cury = -1
|
||||
s.fini = true
|
||||
s.vten = false
|
||||
s.Unlock()
|
||||
|
||||
s.setCursorInfo(&s.ocursor)
|
||||
|
@ -265,12 +317,25 @@ type rect struct {
|
|||
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() {
|
||||
s.setCursorInfo(&cursorInfo{size: 100, visible: 1})
|
||||
if s.vten {
|
||||
s.emitVtString(vtShowCursor)
|
||||
} else {
|
||||
s.setCursorInfo(&cursorInfo{size: 100, visible: 1})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *cScreen) hideCursor() {
|
||||
s.setCursorInfo(&cursorInfo{size: 1, visible: 0})
|
||||
if s.vten {
|
||||
s.emitVtString(vtHideCursor)
|
||||
} else {
|
||||
s.setCursorInfo(&cursorInfo{size: 1, visible: 0})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *cScreen) ShowCursor(x, y int) {
|
||||
|
@ -298,11 +363,6 @@ func (s *cScreen) HideCursor() {
|
|||
s.ShowCursor(-1, -1)
|
||||
}
|
||||
|
||||
type charInfo struct {
|
||||
ch uint16
|
||||
attr uint16
|
||||
}
|
||||
|
||||
type inputRecord struct {
|
||||
typ uint16
|
||||
_ uint16
|
||||
|
@ -629,6 +689,9 @@ func (s *cScreen) scanInput() {
|
|||
|
||||
// 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
|
||||
}
|
||||
|
||||
|
@ -728,17 +791,51 @@ func (s *cScreen) GetContent(x, y int) (rune, []rune, Style, int) {
|
|||
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
|
||||
}
|
||||
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)
|
||||
|
||||
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() {
|
||||
|
@ -848,12 +945,18 @@ 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())
|
||||
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) {
|
||||
|
@ -892,7 +995,6 @@ func (s *cScreen) resize() {
|
|||
uintptr(s.out),
|
||||
uintptr(1),
|
||||
uintptr(unsafe.Pointer(&r)))
|
||||
|
||||
s.PostEvent(NewEventResize(w, h))
|
||||
}
|
||||
|
||||
|
@ -910,32 +1012,50 @@ func (s *cScreen) Fill(r rune, style Style) {
|
|||
}
|
||||
|
||||
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)
|
||||
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)
|
||||
|
||||
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)))
|
||||
} 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 uint32 = 0x0010
|
||||
modeResizeEn uint32 = 0x0008
|
||||
modeWrapEOL uint32 = 0x0002
|
||||
modeCooked uint32 = 0x0001
|
||||
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 {
|
||||
|
|
Loading…
Reference in New Issue