2022-03-29 21:20:33 +09:00
//go:build windows
2019-02-04 22:51:39 -08:00
package tui
import (
"os"
"syscall"
2021-04-03 21:19:43 -07:00
"time"
2019-02-04 22:51:39 -08:00
"github.com/junegunn/fzf/src/util"
"golang.org/x/sys/windows"
)
2021-04-03 21:19:43 -07:00
const (
timeoutInterval = 10
)
2019-02-04 22:51:39 -08:00
var (
consoleFlagsInput = uint32 ( windows . ENABLE_VIRTUAL_TERMINAL_INPUT | windows . ENABLE_PROCESSED_INPUT | windows . ENABLE_EXTENDED_FLAGS )
consoleFlagsOutput = uint32 ( windows . ENABLE_VIRTUAL_TERMINAL_PROCESSING | windows . ENABLE_PROCESSED_OUTPUT | windows . DISABLE_NEWLINE_AUTO_RETURN )
2025-03-03 14:01:53 +09:00
counter = uint64 ( 0 )
2019-02-04 22:51:39 -08:00
)
// IsLightRendererSupported checks to see if the Light renderer is supported
func IsLightRendererSupported () bool {
var oldState uint32
// enable vt100 emulation (https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences)
if windows . GetConsoleMode ( windows . Stderr , & oldState ) != nil {
return false
}
// attempt to set mode to determine if we support VT 100 codes. This will work on newer Windows 10
// version:
canSetVt100 := windows . SetConsoleMode ( windows . Stderr , oldState | windows . ENABLE_VIRTUAL_TERMINAL_PROCESSING ) == nil
var checkState uint32
if windows . GetConsoleMode ( windows . Stderr , & checkState ) != nil ||
( checkState & windows . ENABLE_VIRTUAL_TERMINAL_PROCESSING ) != windows . ENABLE_VIRTUAL_TERMINAL_PROCESSING {
return false
}
windows . SetConsoleMode ( windows . Stderr , oldState )
return canSetVt100
}
2024-12-31 17:03:18 +09:00
func ( r * LightRenderer ) DefaultTheme () * ColorTheme {
2020-09-01 21:47:13 -07:00
// the getenv check is borrowed from here: https://github.com/gdamore/tcell/commit/0c473b86d82f68226a142e96cc5a34c5a29b3690#diff-b008fcd5e6934bf31bc3d33bf49f47d8R178:
if ! IsLightRendererSupported () || os . Getenv ( "ConEmuPID" ) != "" || os . Getenv ( "TCELL_TRUECOLOR" ) == "disable" {
return Default16
}
return Dark256
}
2019-02-04 22:51:39 -08:00
func ( r * LightRenderer ) initPlatform () error {
//outHandle := windows.Stdout
outHandle , _ := syscall . Open ( "CONOUT$" , syscall . O_RDWR , 0 )
// enable vt100 emulation (https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences)
if err := windows . GetConsoleMode ( windows . Handle ( outHandle ), & r . origStateOutput ); err != nil {
return err
}
r . outHandle = uintptr ( outHandle )
inHandle , _ := syscall . Open ( "CONIN$" , syscall . O_RDWR , 0 )
if err := windows . GetConsoleMode ( windows . Handle ( inHandle ), & r . origStateInput ); err != nil {
return err
}
r . inHandle = uintptr ( inHandle )
// channel for non-blocking reads. Buffer to make sure
// we get the ESC sets:
2021-04-03 21:19:43 -07:00
r . ttyinChannel = make ( chan byte , 1024 )
2019-02-04 22:51:39 -08:00
2025-03-03 14:01:53 +09:00
r . setupTerminal ()
2019-02-04 22:51:39 -08:00
return nil
}
func ( r * LightRenderer ) closePlatform () {
windows . SetConsoleMode ( windows . Handle ( r . outHandle ), r . origStateOutput )
windows . SetConsoleMode ( windows . Handle ( r . inHandle ), r . origStateInput )
}
2025-04-20 11:24:50 +09:00
func openTtyIn ( ttyDefault string ) ( * os . File , error ) {
2019-02-04 22:51:39 -08:00
// not used
2024-05-07 01:06:42 +09:00
return nil , nil
2019-02-04 22:51:39 -08:00
}
2025-04-20 11:24:50 +09:00
func openTtyOut ( ttyDefault string ) ( * os . File , error ) {
2024-06-01 14:34:27 +09:00
return os . Stderr , nil
2024-05-14 16:29:22 +09:00
}
2025-03-03 14:01:53 +09:00
func ( r * LightRenderer ) setupTerminal () {
windows . SetConsoleMode ( windows . Handle ( r . outHandle ), consoleFlagsOutput )
windows . SetConsoleMode ( windows . Handle ( r . inHandle ), consoleFlagsInput )
// The following allows for non-blocking IO.
// syscall.SetNonblock() is a NOOP under Windows.
current := counter
go func () {
fd := int ( r . inHandle )
b := make ([] byte , 1 )
for {
if _ , err := util . Read ( fd , b ); err == nil {
r . mutex . Lock ()
// This condition prevents the goroutine from running after the renderer
// has been closed or paused.
if current != counter {
r . mutex . Unlock ()
break
}
r . ttyinChannel <- b [ 0 ]
// HACK: if run from PSReadline, something resets ConsoleMode to remove ENABLE_VIRTUAL_TERMINAL_INPUT.
windows . SetConsoleMode ( windows . Handle ( r . inHandle ), consoleFlagsInput )
r . mutex . Unlock ()
}
}
}()
2019-02-04 22:51:39 -08:00
}
2025-03-03 14:01:53 +09:00
func ( r * LightRenderer ) restoreTerminal () {
r . mutex . Lock ()
counter ++
// We're setting ENABLE_VIRTUAL_TERMINAL_INPUT to allow escape sequences to be read during 'execute'.
// e.g. fzf --bind 'enter:execute:less {}'
windows . SetConsoleMode ( windows . Handle ( r . inHandle ), r . origStateInput | windows . ENABLE_VIRTUAL_TERMINAL_INPUT )
windows . SetConsoleMode ( windows . Handle ( r . outHandle ), r . origStateOutput )
r . mutex . Unlock ()
2019-02-04 22:51:39 -08:00
}
2023-11-10 12:55:18 +09:00
func ( r * LightRenderer ) Size () TermSize {
var w , h int
2019-02-04 22:51:39 -08:00
var bufferInfo windows . ConsoleScreenBufferInfo
if err := windows . GetConsoleScreenBufferInfo ( windows . Handle ( r . outHandle ), & bufferInfo ); err != nil {
2023-11-10 12:55:18 +09:00
w = getEnv ( "COLUMNS" , defaultWidth )
h = r . maxHeightFunc ( getEnv ( "LINES" , defaultHeight ))
2019-02-04 22:51:39 -08:00
} else {
2023-11-10 12:55:18 +09:00
w = int ( bufferInfo . Window . Right - bufferInfo . Window . Left )
h = r . maxHeightFunc ( int ( bufferInfo . Window . Bottom - bufferInfo . Window . Top ))
2019-02-04 22:51:39 -08:00
}
2023-11-10 12:55:18 +09:00
return TermSize { h , w , 0 , 0 }
}
func ( r * LightRenderer ) updateTerminalSize () {
size := r . Size ()
r . width = size . Columns
r . height = size . Lines
2019-02-04 22:51:39 -08:00
}
func ( r * LightRenderer ) findOffset () ( row int , col int ) {
var bufferInfo windows . ConsoleScreenBufferInfo
if err := windows . GetConsoleScreenBufferInfo ( windows . Handle ( r . outHandle ), & bufferInfo ); err != nil {
return - 1 , - 1
}
2024-06-12 21:18:02 +09:00
return int ( bufferInfo . CursorPosition . Y ), int ( bufferInfo . CursorPosition . X )
2019-02-04 22:51:39 -08:00
}
2026-01-09 00:29:40 +09:00
func ( r * LightRenderer ) getch ( cancellable bool , nonblock bool ) ( int , getCharResult ) {
if ! nonblock && ! cancellable {
2019-02-04 22:51:39 -08:00
bc := <- r . ttyinChannel
2026-01-09 00:29:40 +09:00
return int ( bc ), getCharSuccess
}
var timeout <- chan time . Time
if nonblock {
timeout = time . After ( timeoutInterval * time . Millisecond )
}
var cancel chan struct {}
if cancellable {
cancel = make ( chan struct {})
r . setCancel ( func () {
close ( cancel )
})
defer r . setCancel ( nil )
}
select {
case bc := <- r . ttyinChannel :
return int ( bc ), getCharSuccess
case <- cancel :
return 0 , getCharCancelled
case <- timeout :
// NOTE: not really an error
return 0 , getCharError
2019-02-04 22:51:39 -08:00
}
}