bubbletea/tea_windows.go

165 lines
4.0 KiB
Go
Raw Permalink Normal View History

2023-09-14 18:00:38 -04:00
package tea
import (
"fmt"
"log"
"github.com/erikgeiser/coninput"
"golang.org/x/sys/windows"
)
func enableWindowsConInput(p *Program) (func() error, error) {
con, err := windows.GetStdHandle(windows.STD_INPUT_HANDLE)
if err != nil {
return nil, fmt.Errorf("get stdin handle: %w", err)
}
p.conInput = con
var originalConsoleMode uint32
err = windows.GetConsoleMode(con, &originalConsoleMode)
if err != nil {
return nil, fmt.Errorf("get console mode: %w", err)
}
log.Println("Input mode:", coninput.DescribeInputMode(originalConsoleMode))
newConsoleMode := coninput.AddInputModes(
0,
windows.ENABLE_WINDOW_INPUT,
windows.ENABLE_MOUSE_INPUT,
// windows.ENABLE_PROCESSED_INPUT,
windows.ENABLE_EXTENDED_FLAGS,
// windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING,
)
log.Println("Setting mode to:", coninput.DescribeInputMode(newConsoleMode))
err = windows.SetConsoleMode(con, newConsoleMode)
if err != nil {
return nil, fmt.Errorf("set console mode: %w", err)
}
cancelEvent, err := windows.CreateEvent(nil, 0, 0, nil)
if err != nil {
return nil, fmt.Errorf("create stop event: %w", err)
}
p.cancelEvent = cancelEvent
return func() error {
log.Println("Resetting input mode to:", coninput.DescribeInputMode(originalConsoleMode))
if err := windows.CloseHandle(p.cancelEvent); err != nil {
return fmt.Errorf("close stop event: %w", err)
}
resetErr := windows.SetConsoleMode(con, originalConsoleMode)
if err == nil && resetErr != nil {
return fmt.Errorf("reset console mode: %w", resetErr)
}
return nil
}, nil
}
func (p *Program) handleConInput() chan struct{} {
ch := make(chan struct{})
go func() {
defer func() {
windows.SetEvent(p.cancelEvent)
close(ch)
}()
for {
select {
case <-p.ctx.Done():
return
default:
if p.ctx.Err() != nil {
return
}
if err := waitForInput(p.conInput, p.cancelEvent); err != nil {
log.Printf("wait for input: %s", err)
return
}
n, err := coninput.ReadConsoleInput(p.conInput, p.inputEvents)
if err != nil {
log.Printf("read input events: %s", err)
return
}
log.Printf("Read %d events:\n", n)
for _, event := range p.inputEvents[:n] {
log.Println(" ", event)
switch e := event.Unwrap().(type) {
case coninput.WindowBufferSizeEventRecord:
p.msgs <- WindowSizeMsg{
Width: int(e.Size.X),
Height: int(e.Size.Y),
}
}
}
}
}
}()
return ch
}
var errCanceled = fmt.Errorf("read cancelled")
func waitForInput(conin, cancel windows.Handle) error {
event, err := windows.WaitForMultipleObjects([]windows.Handle{conin, cancel}, false, windows.INFINITE)
switch {
case windows.WAIT_OBJECT_0 <= event && event < windows.WAIT_OBJECT_0+2:
if event == windows.WAIT_OBJECT_0+1 {
return errCanceled
}
if event == windows.WAIT_OBJECT_0 {
return nil
}
return fmt.Errorf("unexpected wait object is ready: %d", event-windows.WAIT_OBJECT_0)
case windows.WAIT_ABANDONED <= event && event < windows.WAIT_ABANDONED+2:
return fmt.Errorf("abandoned")
case event == uint32(windows.WAIT_TIMEOUT):
return fmt.Errorf("timeout")
case event == windows.WAIT_FAILED:
return fmt.Errorf("failed")
default:
return fmt.Errorf("unexpected error: %w", error(err))
}
}
type overlappedReader windows.Handle
// Read performs an overlapping read fom a windows.Handle.
func (r overlappedReader) Read(data []byte) (int, error) {
hevent, err := windows.CreateEvent(nil, 0, 0, nil)
if err != nil {
return 0, fmt.Errorf("create event: %w", err)
}
overlapped := windows.Overlapped{HEvent: hevent}
var n uint32
err = windows.ReadFile(windows.Handle(r), data, &n, &overlapped)
if err != nil && err != windows.ERROR_IO_PENDING {
return int(n), err
}
err = windows.GetOverlappedResult(windows.Handle(r), &overlapped, &n, true)
if err != nil {
return int(n), nil
}
return int(n), nil
}