1// +build windows 2 3package termstatus 4 5import ( 6 "io" 7 "strings" 8 "syscall" 9 "unsafe" 10 11 "golang.org/x/crypto/ssh/terminal" 12 "golang.org/x/sys/windows" 13) 14 15// clearCurrentLine removes all characters from the current line and resets the 16// cursor position to the first column. 17func clearCurrentLine(wr io.Writer, fd uintptr) func(io.Writer, uintptr) { 18 // easy case, the terminal is cmd or psh, without redirection 19 if isWindowsTerminal(fd) { 20 return windowsClearCurrentLine 21 } 22 23 // assume we're running in mintty/cygwin 24 return posixClearCurrentLine 25} 26 27// moveCursorUp moves the cursor to the line n lines above the current one. 28func moveCursorUp(wr io.Writer, fd uintptr) func(io.Writer, uintptr, int) { 29 // easy case, the terminal is cmd or psh, without redirection 30 if isWindowsTerminal(fd) { 31 return windowsMoveCursorUp 32 } 33 34 // assume we're running in mintty/cygwin 35 return posixMoveCursorUp 36} 37 38var kernel32 = syscall.NewLazyDLL("kernel32.dll") 39 40var ( 41 procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW") 42 procFillConsoleOutputAttribute = kernel32.NewProc("FillConsoleOutputAttribute") 43) 44 45// windowsClearCurrentLine removes all characters from the current line and 46// resets the cursor position to the first column. 47func windowsClearCurrentLine(wr io.Writer, fd uintptr) { 48 var info windows.ConsoleScreenBufferInfo 49 windows.GetConsoleScreenBufferInfo(windows.Handle(fd), &info) 50 51 // clear the line 52 cursor := windows.Coord{ 53 X: info.Window.Left, 54 Y: info.CursorPosition.Y, 55 } 56 var count, w uint32 57 count = uint32(info.Size.X) 58 procFillConsoleOutputAttribute.Call(fd, uintptr(info.Attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&w))) 59 procFillConsoleOutputCharacter.Call(fd, uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&w))) 60} 61 62// windowsMoveCursorUp moves the cursor to the line n lines above the current one. 63func windowsMoveCursorUp(wr io.Writer, fd uintptr, n int) { 64 var info windows.ConsoleScreenBufferInfo 65 windows.GetConsoleScreenBufferInfo(windows.Handle(fd), &info) 66 67 // move cursor up by n lines and to the first column 68 windows.SetConsoleCursorPosition(windows.Handle(fd), windows.Coord{ 69 X: 0, 70 Y: info.CursorPosition.Y - int16(n), 71 }) 72} 73 74// isWindowsTerminal return true if the file descriptor is a windows terminal (cmd, psh). 75func isWindowsTerminal(fd uintptr) bool { 76 return terminal.IsTerminal(int(fd)) 77} 78 79func isPipe(fd uintptr) bool { 80 typ, err := windows.GetFileType(windows.Handle(fd)) 81 return err == nil && typ == windows.FILE_TYPE_PIPE 82} 83 84func getFileNameByHandle(fd uintptr) (string, error) { 85 type FILE_NAME_INFO struct { 86 FileNameLength int32 87 FileName [windows.MAX_LONG_PATH]uint16 88 } 89 90 var fi FILE_NAME_INFO 91 err := windows.GetFileInformationByHandleEx(windows.Handle(fd), windows.FileNameInfo, (*byte)(unsafe.Pointer(&fi)), uint32(unsafe.Sizeof(fi))) 92 if err != nil { 93 return "", err 94 } 95 96 filename := syscall.UTF16ToString(fi.FileName[:]) 97 return filename, nil 98} 99 100// CanUpdateStatus returns true if status lines can be printed, the process 101// output is not redirected to a file or pipe. 102func CanUpdateStatus(fd uintptr) bool { 103 // easy case, the terminal is cmd or psh, without redirection 104 if isWindowsTerminal(fd) { 105 return true 106 } 107 108 // pipes require special handling 109 if !isPipe(fd) { 110 return false 111 } 112 113 fn, err := getFileNameByHandle(fd) 114 if err != nil { 115 return false 116 } 117 118 // inspired by https://github.com/RyanGlScott/mintty/blob/master/src/System/Console/MinTTY/Win32.hsc 119 // terminal: \msys-dd50a72ab4668b33-pty0-to-master 120 // pipe to cat: \msys-dd50a72ab4668b33-13244-pipe-0x16 121 if (strings.HasPrefix(fn, "\\cygwin-") || strings.HasPrefix(fn, "\\msys-")) && 122 strings.Contains(fn, "-pty") && strings.HasSuffix(fn, "-master") { 123 return true 124 } 125 126 return false 127} 128