|
|
@@ -0,0 +1,1170 @@
|
|
|
+// +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
|
|
|
+}
|