forked from TrueCloudLab/restic
b2208bb9c2
This now keeps the cursor at the first column of the first status line so that messages printed to stdout or stderr by some other part of the progarm will still be visible. The message will overwrite the status lines, but those are easily reprinted on the next status update.
154 lines
4.6 KiB
Go
154 lines
4.6 KiB
Go
// +build windows
|
|
|
|
package termstatus
|
|
|
|
import (
|
|
"io"
|
|
"syscall"
|
|
"unsafe"
|
|
)
|
|
|
|
// 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
|
|
}
|
|
|
|
// check if the output file type is a pipe (0x0003)
|
|
if getFileType(fd) != fileTypePipe {
|
|
// return empty func, update state is not possible on this terminal
|
|
return func(io.Writer, uintptr) {}
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// check if the output file type is a pipe (0x0003)
|
|
if getFileType(fd) != fileTypePipe {
|
|
// return empty func, update state is not possible on this terminal
|
|
return func(io.Writer, uintptr, int) {}
|
|
}
|
|
|
|
// assume we're running in mintty/cygwin
|
|
return posixMoveCursorUp
|
|
}
|
|
|
|
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
|
|
|
var (
|
|
procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
|
|
procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition")
|
|
procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW")
|
|
procFillConsoleOutputAttribute = kernel32.NewProc("FillConsoleOutputAttribute")
|
|
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
|
|
procGetFileType = kernel32.NewProc("GetFileType")
|
|
)
|
|
|
|
type (
|
|
short int16
|
|
word uint16
|
|
dword uint32
|
|
|
|
coord struct {
|
|
x short
|
|
y short
|
|
}
|
|
smallRect struct {
|
|
left short
|
|
top short
|
|
right short
|
|
bottom short
|
|
}
|
|
consoleScreenBufferInfo struct {
|
|
size coord
|
|
cursorPosition coord
|
|
attributes word
|
|
window smallRect
|
|
maximumWindowSize coord
|
|
}
|
|
)
|
|
|
|
// 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 consoleScreenBufferInfo
|
|
procGetConsoleScreenBufferInfo.Call(fd, uintptr(unsafe.Pointer(&info)))
|
|
|
|
// clear the line
|
|
cursor := coord{
|
|
x: info.window.left,
|
|
y: info.cursorPosition.y,
|
|
}
|
|
var count, w dword
|
|
count = dword(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 consoleScreenBufferInfo
|
|
procGetConsoleScreenBufferInfo.Call(fd, uintptr(unsafe.Pointer(&info)))
|
|
|
|
// move cursor up by n lines and to the first column
|
|
info.cursorPosition.y -= short(n)
|
|
info.cursorPosition.x = 0
|
|
procSetConsoleCursorPosition.Call(fd, uintptr(*(*int32)(unsafe.Pointer(&info.cursorPosition))))
|
|
}
|
|
|
|
// getTermSize returns the dimensions of the given terminal.
|
|
// the code is taken from "golang.org/x/crypto/ssh/terminal"
|
|
func getTermSize(fd uintptr) (width, height int, err error) {
|
|
var info consoleScreenBufferInfo
|
|
_, _, e := syscall.Syscall(procGetConsoleScreenBufferInfo.Addr(), 2, fd, uintptr(unsafe.Pointer(&info)), 0)
|
|
if e != 0 {
|
|
return 0, 0, error(e)
|
|
}
|
|
return int(info.size.x), int(info.size.y), nil
|
|
}
|
|
|
|
// isWindowsTerminal return true if the file descriptor is a windows terminal (cmd, psh).
|
|
func isWindowsTerminal(fd uintptr) bool {
|
|
var st uint32
|
|
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0)
|
|
return r != 0 && e == 0
|
|
}
|
|
|
|
const fileTypePipe = 0x0003
|
|
|
|
// getFileType returns the file type for the given fd.
|
|
// https://msdn.microsoft.com/de-de/library/windows/desktop/aa364960(v=vs.85).aspx
|
|
func getFileType(fd uintptr) int {
|
|
r, _, e := syscall.Syscall(procGetFileType.Addr(), 1, fd, 0, 0)
|
|
if e != 0 {
|
|
return 0
|
|
}
|
|
return int(r)
|
|
}
|
|
|
|
// 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 if the output file type is a pipe (0x0003)
|
|
if getFileType(fd) != fileTypePipe {
|
|
return false
|
|
}
|
|
|
|
// assume we're running in mintty/cygwin
|
|
return true
|
|
}
|