okami.mom/ultraviolet
git clone https://git.nkp.pet/okami.mom/ultraviolet
21397ab
upstream
1//go:build windows 2// +build windows 3 4package uv 5 6import ( 7"fmt" 8"io" 9"os" 10"sync" 11 12 xwindows"github.com/charmbracelet/x/windows" 13"github.com/muesli/cancelreader" 14"golang.org/x/sys/windows" 15) 16 17type conInputReader struct { 18cancelMixin 19conin windows.Handle 20originalMode uint32 21newMode uint32 22} 23 24var _ cancelreader.CancelReader = & conInputReader {} 25 26// NewCancelReader creates a new [cancelreader.CancelReader] that provides a 27// cancelable reader interface that can be used to cancel reads. 28func NewCancelReader (r io.Reader ) (cancelreader.CancelReader ,error ) { 29fallback := func (io.Reader ) (cancelreader.CancelReader ,error ) { 30return cancelreader .NewReader (r ) 31 } 32 33var dummy uint32 34if f ,ok := r .(cancelreader.File );! ok || f .Fd ()!= os .Stdin .Fd ()|| 35// If data was piped to the standard input, it does not emit events 36// anymore. We can detect this if the console mode cannot be set anymore, 37// in this case, we fallback to the default cancelreader implementation. 38windows .GetConsoleMode (windows .Handle (f .Fd ()),& dummy )!= nil { 39return fallback (r ) 40 } 41 42conin ,err := windows .GetStdHandle (windows .STD_INPUT_HANDLE ) 43if err != nil { 44return fallback (r ) 45 } 46 47// Discard any pending input events. 48if err := xwindows .FlushConsoleInputBuffer (conin );err != nil { 49return fallback (r ) 50 } 51 52modes := []uint32 { 53windows .ENABLE_VIRTUAL_TERMINAL_INPUT , 54windows .ENABLE_WINDOW_INPUT , 55windows .ENABLE_EXTENDED_FLAGS , 56 } 57 58originalMode ,newMode ,err := prepareConsole (conin ,modes ... ) 59if err != nil { 60return nil ,fmt .Errorf ("failed to prepare console input: %w" ,err ) 61 } 62 63return & conInputReader { 64conin :conin , 65originalMode :originalMode , 66newMode :newMode , 67 },nil 68} 69 70// Cancel implements cancelreader.CancelReader. 71func (r * conInputReader )Cancel ()bool { 72r .setCanceled () 73 74return windows .CancelIoEx (r .conin ,nil )== nil || windows .CancelIo (r .conin )== nil 75} 76 77// Close implements cancelreader.CancelReader. 78func (r * conInputReader )Close ()error { 79if r .originalMode != 0 { 80err := windows .SetConsoleMode (r .conin ,r .originalMode ) 81if err != nil { 82return fmt .Errorf ("reset console mode: %w" ,err ) 83 } 84 } 85 86return nil 87} 88 89// Read implements cancelreader.CancelReader. 90func (r * conInputReader )Read (data []byte ) (int ,error ) { 91if r .isCanceled () { 92return 0 ,cancelreader .ErrCanceled 93 } 94 95var n uint32 96if err := windows .ReadFile (r .conin ,data ,& n ,nil );err != nil { 97return int (n ),fmt .Errorf ("read console input: %w" ,err ) 98 } 99 100return int (n ),nil 101} 102 103func prepareConsole (input windows.Handle ,modes ... uint32 ) (originalMode ,newMode uint32 ,err error ) { 104err = windows .GetConsoleMode (input ,& originalMode ) 105if err != nil { 106return 0 ,0 ,fmt .Errorf ("get console mode: %w" ,err ) 107 } 108 109for _ ,mode := range modes { 110newMode |= mode 111 } 112 113err = windows .SetConsoleMode (input ,newMode ) 114if err != nil { 115return 0 ,0 ,fmt .Errorf ("set console mode: %w" ,err ) 116 } 117 118return originalMode ,newMode ,nil 119} 120 121// cancelMixin represents a goroutine-safe cancelation status. 122type cancelMixin struct { 123unsafeCanceled bool 124lock sync.Mutex 125} 126 127func (c * cancelMixin )setCanceled () { 128c .lock .Lock () 129defer c .lock .Unlock () 130 131c .unsafeCanceled = true 132} 133 134func (c * cancelMixin )isCanceled ()bool { 135c .lock .Lock () 136defer c .lock .Unlock () 137 138return c .unsafeCanceled 139}