chore: ⬆️ updated deps
This commit is contained in:
+290
@@ -0,0 +1,290 @@
|
||||
// Copyright 2026 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.
|
||||
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package tcell
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
"unicode/utf16"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
k32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
)
|
||||
|
||||
var (
|
||||
procReadConsoleInput = k32.NewProc("ReadConsoleInputW")
|
||||
procGetNumberOfConsoleInputEvents = k32.NewProc("GetNumberOfConsoleInputEvents")
|
||||
procFlushConsoleInputBuffer = k32.NewProc("FlushConsoleInputBuffer")
|
||||
procWaitForMultipleObjects = k32.NewProc("WaitForMultipleObjects")
|
||||
procSetConsoleMode = k32.NewProc("SetConsoleMode")
|
||||
procGetConsoleMode = k32.NewProc("GetConsoleMode")
|
||||
procGetConsoleScreenBufferInfo = k32.NewProc("GetConsoleScreenBufferInfo")
|
||||
procCreateEvent = k32.NewProc("CreateEventW")
|
||||
procSetEvent = k32.NewProc("SetEvent")
|
||||
)
|
||||
|
||||
const (
|
||||
keyEvent uint16 = 1
|
||||
mouseEvent uint16 = 2
|
||||
resizeEvent uint16 = 4
|
||||
menuEvent uint16 = 8 // don't use
|
||||
focusEvent uint16 = 16
|
||||
)
|
||||
|
||||
type inputRecord struct {
|
||||
typ uint16
|
||||
_ uint16
|
||||
data [16]byte
|
||||
}
|
||||
|
||||
type winTty struct {
|
||||
buf chan byte
|
||||
out syscall.Handle
|
||||
in syscall.Handle
|
||||
cancelFlag syscall.Handle
|
||||
running bool
|
||||
stopQ chan struct{}
|
||||
resizeCb func()
|
||||
cols uint16
|
||||
rows uint16
|
||||
pair []uint16 // for surrogate pairs (UTF-16)
|
||||
oimode uint32 // original input mode
|
||||
oomode uint32 // original output mode
|
||||
oscreen consoleInfo
|
||||
wg sync.WaitGroup
|
||||
surrogate rune
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func (w *winTty) Read(b []byte) (int, error) {
|
||||
// first character read blocks
|
||||
var num int
|
||||
select {
|
||||
case c := <-w.buf:
|
||||
b[0] = c
|
||||
num++
|
||||
case <-w.stopQ:
|
||||
// stopping, so make sure we eat everything, which might require
|
||||
// very short sleeps to ensure all buffered data is consumed.
|
||||
break
|
||||
}
|
||||
|
||||
// second character read is non-blocking
|
||||
for ; num < len(b); num++ {
|
||||
select {
|
||||
case c := <-w.buf:
|
||||
b[num] = c
|
||||
case <-time.After(time.Millisecond * 10):
|
||||
return num, nil
|
||||
}
|
||||
}
|
||||
return num, nil
|
||||
}
|
||||
|
||||
func (w *winTty) Write(b []byte) (int, error) {
|
||||
esc := utf16.Encode([]rune(string(b)))
|
||||
if len(esc) > 0 {
|
||||
err := syscall.WriteConsole(w.out, &esc[0], uint32(len(esc)), nil, nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
func (w *winTty) Close() error {
|
||||
_ = syscall.Close(w.in)
|
||||
_ = syscall.Close(w.out)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *winTty) Drain() error {
|
||||
close(w.stopQ)
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
_, _, _ = procSetEvent.Call(uintptr(w.cancelFlag))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *winTty) getConsoleInput() error {
|
||||
// cancelFlag comes first as WaitForMultipleObjects returns the lowest index
|
||||
// in the event that both events are signaled.
|
||||
waitObjects := []syscall.Handle{w.cancelFlag, w.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: // w.cancelFlag
|
||||
return errors.New("cancelled")
|
||||
case w32WaitObject0 + 1: // w.in
|
||||
// rec := &inputRecord{}
|
||||
var nrec int32
|
||||
rv, _, er := procGetNumberOfConsoleInputEvents.Call(
|
||||
uintptr(w.in),
|
||||
uintptr(unsafe.Pointer(&nrec)))
|
||||
rec := make([]inputRecord, nrec)
|
||||
rv, _, er = procReadConsoleInput.Call(
|
||||
uintptr(w.in),
|
||||
uintptr(unsafe.Pointer(&rec[0])),
|
||||
uintptr(nrec),
|
||||
uintptr(unsafe.Pointer(&nrec)))
|
||||
if rv == 0 {
|
||||
return er
|
||||
}
|
||||
loop:
|
||||
for i := range nrec {
|
||||
ir := rec[i]
|
||||
switch ir.typ {
|
||||
case keyEvent:
|
||||
// we normally only expect to see ascii, but paste data may come in as UTF-16.
|
||||
wc := rune(binary.LittleEndian.Uint16(ir.data[10:]))
|
||||
if wc >= 0xD800 && wc <= 0xDBFF {
|
||||
// if it was a high surrogate, which happens for pasted UTF-16,
|
||||
// then save it until we get the low and can decode it.
|
||||
w.surrogate = wc
|
||||
continue
|
||||
} else if wc >= 0xDC00 && wc <= 0xDFFF {
|
||||
wc = utf16.DecodeRune(w.surrogate, wc)
|
||||
}
|
||||
w.surrogate = 0
|
||||
for _, chr := range []byte(string(wc)) {
|
||||
// We normally expect only to see ASCII (win32-input-mode),
|
||||
// but apparently pasted data can arrive in UTF-16 here.
|
||||
select {
|
||||
case w.buf <- chr:
|
||||
case <-w.stopQ:
|
||||
break loop
|
||||
}
|
||||
}
|
||||
|
||||
case resizeEvent:
|
||||
w.Lock()
|
||||
w.cols = binary.LittleEndian.Uint16(ir.data[0:])
|
||||
w.rows = binary.LittleEndian.Uint16(ir.data[2:])
|
||||
cb := w.resizeCb
|
||||
w.Unlock()
|
||||
if cb != nil {
|
||||
cb()
|
||||
}
|
||||
|
||||
default:
|
||||
}
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return er
|
||||
}
|
||||
}
|
||||
|
||||
func (w *winTty) scanInput() {
|
||||
defer w.wg.Done()
|
||||
for {
|
||||
if e := w.getConsoleInput(); e != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *winTty) Start() error {
|
||||
|
||||
w.Lock()
|
||||
defer w.Unlock()
|
||||
|
||||
if w.running {
|
||||
return errors.New("already engaged")
|
||||
}
|
||||
_, _, _ = procFlushConsoleInputBuffer.Call(uintptr(w.in))
|
||||
w.stopQ = make(chan struct{})
|
||||
cf, _, err := procCreateEvent.Call(
|
||||
uintptr(0),
|
||||
uintptr(1),
|
||||
uintptr(0),
|
||||
uintptr(0))
|
||||
if cf == uintptr(0) {
|
||||
return err
|
||||
}
|
||||
w.running = true
|
||||
w.cancelFlag = syscall.Handle(cf)
|
||||
|
||||
_, _, _ = procSetConsoleMode.Call(uintptr(w.in),
|
||||
uintptr(modeVtInput|modeResizeEn|modeExtendFlg))
|
||||
_, _, _ = procSetConsoleMode.Call(uintptr(w.out),
|
||||
uintptr(modeVtOutput|modeNoAutoNL|modeCookedOut|modeUnderline))
|
||||
|
||||
w.wg.Add(1)
|
||||
go w.scanInput()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *winTty) Stop() error {
|
||||
w.wg.Wait()
|
||||
w.Lock()
|
||||
defer w.Unlock()
|
||||
_, _, _ = procSetConsoleMode.Call(uintptr(w.in), uintptr(w.oimode))
|
||||
_, _, _ = procSetConsoleMode.Call(uintptr(w.out), uintptr(w.oomode))
|
||||
_, _, _ = procFlushConsoleInputBuffer.Call(uintptr(w.in))
|
||||
w.running = false
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *winTty) NotifyResize(cb func()) {
|
||||
w.resizeCb = cb
|
||||
}
|
||||
|
||||
func (w *winTty) WindowSize() (WindowSize, error) {
|
||||
w.Lock()
|
||||
defer w.Unlock()
|
||||
return WindowSize{Width: int(w.cols), Height: int(w.rows)}, nil
|
||||
}
|
||||
|
||||
func NewDevTty() (Tty, error) {
|
||||
w := &winTty{}
|
||||
var err error
|
||||
w.in, err = syscall.Open("CONIN$", syscall.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
w.out, err = syscall.Open("CONOUT$", syscall.O_RDWR, 0)
|
||||
if err != nil {
|
||||
_ = syscall.Close(w.in)
|
||||
return nil, err
|
||||
}
|
||||
w.buf = make(chan byte, 128)
|
||||
|
||||
_, _, _ = procGetConsoleScreenBufferInfo.Call(uintptr(w.out), uintptr(unsafe.Pointer(&w.oscreen)))
|
||||
_, _, _ = procGetConsoleMode.Call(uintptr(w.out), uintptr(unsafe.Pointer(&w.oomode)))
|
||||
_, _, _ = procGetConsoleMode.Call(uintptr(w.in), uintptr(unsafe.Pointer(&w.oimode)))
|
||||
w.rows = uint16(w.oscreen.size.y)
|
||||
w.cols = uint16(w.oscreen.size.x)
|
||||
|
||||
return w, nil
|
||||
}
|
||||
Reference in New Issue
Block a user