forked from toolshed/abra
		
	
		
			
				
	
	
		
			130 lines
		
	
	
		
			2.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			130 lines
		
	
	
		
			2.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| //go:build windows
 | |
| // +build windows
 | |
| 
 | |
| package tea
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"os"
 | |
| 	"sync"
 | |
| 
 | |
| 	"github.com/charmbracelet/x/term"
 | |
| 	"github.com/erikgeiser/coninput"
 | |
| 	"github.com/muesli/cancelreader"
 | |
| 	"golang.org/x/sys/windows"
 | |
| )
 | |
| 
 | |
| type conInputReader struct {
 | |
| 	cancelMixin
 | |
| 
 | |
| 	conin windows.Handle
 | |
| 
 | |
| 	originalMode uint32
 | |
| }
 | |
| 
 | |
| var _ cancelreader.CancelReader = &conInputReader{}
 | |
| 
 | |
| func newInputReader(r io.Reader, enableMouse bool) (cancelreader.CancelReader, error) {
 | |
| 	fallback := func(io.Reader) (cancelreader.CancelReader, error) {
 | |
| 		return cancelreader.NewReader(r)
 | |
| 	}
 | |
| 	if f, ok := r.(term.File); !ok || f.Fd() != os.Stdin.Fd() {
 | |
| 		return fallback(r)
 | |
| 	}
 | |
| 
 | |
| 	conin, err := coninput.NewStdinHandle()
 | |
| 	if err != nil {
 | |
| 		return fallback(r)
 | |
| 	}
 | |
| 
 | |
| 	modes := []uint32{
 | |
| 		windows.ENABLE_WINDOW_INPUT,
 | |
| 		windows.ENABLE_EXTENDED_FLAGS,
 | |
| 	}
 | |
| 
 | |
| 	// Since we have options to enable mouse events, [WithMouseCellMotion],
 | |
| 	// [WithMouseAllMotion], and [EnableMouseCellMotion],
 | |
| 	// [EnableMouseAllMotion], and [DisableMouse], we need to check if the user
 | |
| 	// has enabled mouse events and add the appropriate mode accordingly.
 | |
| 	// Otherwise, mouse events will be enabled all the time.
 | |
| 	if enableMouse {
 | |
| 		modes = append(modes, windows.ENABLE_MOUSE_INPUT)
 | |
| 	}
 | |
| 
 | |
| 	originalMode, err := prepareConsole(conin, modes...)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("failed to prepare console input: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	return &conInputReader{
 | |
| 		conin:        conin,
 | |
| 		originalMode: originalMode,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // Cancel implements cancelreader.CancelReader.
 | |
| func (r *conInputReader) Cancel() bool {
 | |
| 	r.setCanceled()
 | |
| 
 | |
| 	// Warning: These cancel methods do not reliably work on console input
 | |
| 	// 			and should not be counted on.
 | |
| 	return windows.CancelIoEx(r.conin, nil) == nil || windows.CancelIo(r.conin) == nil
 | |
| }
 | |
| 
 | |
| // Close implements cancelreader.CancelReader.
 | |
| func (r *conInputReader) Close() error {
 | |
| 	if r.originalMode != 0 {
 | |
| 		err := windows.SetConsoleMode(r.conin, r.originalMode)
 | |
| 		if err != nil {
 | |
| 			return fmt.Errorf("reset console mode: %w", err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Read implements cancelreader.CancelReader.
 | |
| func (r *conInputReader) Read(_ []byte) (n int, err error) {
 | |
| 	if r.isCanceled() {
 | |
| 		err = cancelreader.ErrCanceled
 | |
| 	}
 | |
| 	return
 | |
| }
 | |
| 
 | |
| func prepareConsole(input windows.Handle, modes ...uint32) (originalMode uint32, err error) {
 | |
| 	err = windows.GetConsoleMode(input, &originalMode)
 | |
| 	if err != nil {
 | |
| 		return 0, fmt.Errorf("get console mode: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	newMode := coninput.AddInputModes(0, modes...)
 | |
| 
 | |
| 	err = windows.SetConsoleMode(input, newMode)
 | |
| 	if err != nil {
 | |
| 		return 0, fmt.Errorf("set console mode: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	return originalMode, nil
 | |
| }
 | |
| 
 | |
| // cancelMixin represents a goroutine-safe cancelation status.
 | |
| type cancelMixin struct {
 | |
| 	unsafeCanceled bool
 | |
| 	lock           sync.Mutex
 | |
| }
 | |
| 
 | |
| func (c *cancelMixin) setCanceled() {
 | |
| 	c.lock.Lock()
 | |
| 	defer c.lock.Unlock()
 | |
| 
 | |
| 	c.unsafeCanceled = true
 | |
| }
 | |
| 
 | |
| func (c *cancelMixin) isCanceled() bool {
 | |
| 	c.lock.Lock()
 | |
| 	defer c.lock.Unlock()
 | |
| 
 | |
| 	return c.unsafeCanceled
 | |
| }
 |