restic/internal/ui/termstatus/terminal_windows.go
Michael Eischer 5e6af77b7a Unify interactive terminal detection code
Previously the progress bar / status update interval used
stdoutIsTerminal to determine whether it is possible to update the
progress bar or not. However, its implementation differed from the
detection within the backup command which included additional checks to
detect the presence of mintty on Windows. mintty behaves like a terminal
but uses pipes for communication.

This adds stdoutCanUpdateStatus() which calls the same terminal detection
code used by backup. This ensures that all commands consistently switch
between interactive and non-interactive terminal mode.

stdoutIsTerminal() now also returns true whenever stdoutCanUpdateStatus()
does so. This is required to properly handle the special case of mintty.
2021-04-11 20:02:09 +02:00

99 lines
2.9 KiB
Go

// +build windows
package termstatus
import (
"io"
"syscall"
"unsafe"
"golang.org/x/crypto/ssh/terminal"
"golang.org/x/sys/windows"
)
// clearCurrentLine removes all characters from the current line and resets the
// cursor position to the first column.
func clearCurrentLine(wr io.Writer, fd uintptr) func(io.Writer, uintptr) {
// easy case, the terminal is cmd or psh, without redirection
if isWindowsTerminal(fd) {
return windowsClearCurrentLine
}
// assume we're running in mintty/cygwin
return posixClearCurrentLine
}
// moveCursorUp moves the cursor to the line n lines above the current one.
func moveCursorUp(wr io.Writer, fd uintptr) func(io.Writer, uintptr, int) {
// easy case, the terminal is cmd or psh, without redirection
if isWindowsTerminal(fd) {
return windowsMoveCursorUp
}
// assume we're running in mintty/cygwin
return posixMoveCursorUp
}
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
var (
procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW")
procFillConsoleOutputAttribute = kernel32.NewProc("FillConsoleOutputAttribute")
)
// windowsClearCurrentLine removes all characters from the current line and
// resets the cursor position to the first column.
func windowsClearCurrentLine(wr io.Writer, fd uintptr) {
var info windows.ConsoleScreenBufferInfo
windows.GetConsoleScreenBufferInfo(windows.Handle(fd), &info)
// clear the line
cursor := windows.Coord{
X: info.Window.Left,
Y: info.CursorPosition.Y,
}
var count, w uint32
count = uint32(info.Size.X)
procFillConsoleOutputAttribute.Call(fd, uintptr(info.Attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&w)))
procFillConsoleOutputCharacter.Call(fd, uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&w)))
}
// windowsMoveCursorUp moves the cursor to the line n lines above the current one.
func windowsMoveCursorUp(wr io.Writer, fd uintptr, n int) {
var info windows.ConsoleScreenBufferInfo
windows.GetConsoleScreenBufferInfo(windows.Handle(fd), &info)
// move cursor up by n lines and to the first column
windows.SetConsoleCursorPosition(windows.Handle(fd), windows.Coord{
X: 0,
Y: info.CursorPosition.Y - int16(n),
})
}
// isWindowsTerminal return true if the file descriptor is a windows terminal (cmd, psh).
func isWindowsTerminal(fd uintptr) bool {
return terminal.IsTerminal(int(fd))
}
func isPipe(fd uintptr) bool {
typ, err := windows.GetFileType(windows.Handle(fd))
return err == nil && typ == windows.FILE_TYPE_PIPE
}
// CanUpdateStatus returns true if status lines can be printed, the process
// output is not redirected to a file or pipe.
func CanUpdateStatus(fd uintptr) bool {
// easy case, the terminal is cmd or psh, without redirection
if isWindowsTerminal(fd) {
return true
}
// check that the output file type is a pipe (0x0003)
if !isPipe(fd) {
return false
}
// assume we're running in mintty/cygwin
return true
}