github.com/kopoli/go-terminal-size@v0.0.0-20170219200355-5c97524c8b54/size_windows.go (about) 1 // +build windows 2 3 package tsize 4 5 import ( 6 "os" 7 "unsafe" 8 9 "golang.org/x/sys/windows" 10 ) 11 12 // Make an interface to be able to mock DLL interfaces 13 type proc interface { 14 Call(a ...uintptr) (r1, r2 uintptr, lastErr error) 15 } 16 17 var ( 18 kernel32 = windows.NewLazySystemDLL("kernel32") 19 getConsoleScreenBufferInfo proc = kernel32.NewProc("GetConsoleScreenBufferInfo") 20 getConsoleMode proc = kernel32.NewProc("GetConsoleMode") 21 setConsoleMode proc = kernel32.NewProc("SetConsoleMode") 22 readConsoleInput proc = kernel32.NewProc("ReadConsoleInputW") 23 ) 24 25 type coord struct { 26 x int16 27 y int16 28 } 29 30 type smallRect struct { 31 left int16 32 top int16 33 right int16 34 bottom int16 35 } 36 37 type consoleScreenBufferInfo struct { 38 size coord 39 cursorPosition coord 40 attributes uint16 41 window smallRect 42 maximumWindowSize coord 43 } 44 45 const ( 46 // Console mode 47 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033.aspx 48 enableWindowInput uint32 = 0x0008 49 50 // INPU_RECORD EventType 51 windowBufferSizeEvent uint16 = 0x0004 52 ) 53 54 // INPUT_RECORD is defined in https://msdn.microsoft.com/en-us/library/windows/desktop/ms683499(v=vs.85).aspx 55 // The only interesting thing is the event itself 56 type inputRecord struct { 57 eventType uint16 58 59 // Largest sub-struct in the union is the KEY_EVENT_RECORD with 4+2+2+2+2+4=16 bytes 60 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms684166(v=vs.85).aspx 61 buf [16]byte 62 } 63 64 func getTerminalSize(fp *os.File) (s Size, err error) { 65 csbi := consoleScreenBufferInfo{} 66 ret, _, err := getConsoleScreenBufferInfo.Call(uintptr(windows.Handle(fp.Fd())), 67 uintptr(unsafe.Pointer(&csbi))) 68 69 if ret == 0 { 70 return 71 } 72 73 err = nil 74 s = Size{ 75 Width: int(csbi.size.x), 76 Height: int(csbi.size.y), 77 } 78 79 return 80 } 81 82 // changes can be read with https://msdn.microsoft.com/en-us/library/windows/desktop/ms685035.aspx 83 func getTerminalSizeChanges(sc chan Size, done chan struct{}) (err error) { 84 85 var oldmode, newmode uint32 86 87 // Get terminal mode 88 handle := uintptr(windows.Handle(os.Stdin.Fd())) 89 ret, _, err := getConsoleMode.Call(handle, uintptr(unsafe.Pointer(&oldmode))) 90 if ret == 0 { 91 err = ErrNotATerminal 92 return 93 } 94 95 newmode = oldmode | enableWindowInput 96 97 ret, _, err = setConsoleMode.Call(handle, uintptr(newmode)) 98 if ret == 0 { 99 return 100 } 101 102 go func() { 103 var irs [8]inputRecord 104 var count uint32 105 106 for { 107 ret, _, _ := readConsoleInput.Call(handle, 108 uintptr(unsafe.Pointer(&irs)), 109 uintptr(len(irs)), 110 uintptr(unsafe.Pointer(&count)), 111 ) 112 113 if ret != 0 { 114 var i uint32 115 for i = 0; i < count; i++ { 116 if irs[i].eventType == windowBufferSizeEvent { 117 // Getting the terminal size through Stdout gives the proper values. 118 s, err := getTerminalSize(os.Stdout) 119 if err == nil { 120 sc <- s 121 } 122 break 123 } 124 } 125 } 126 127 select { 128 case <-done: 129 setConsoleMode.Call(handle, uintptr(oldmode)) 130 return 131 default: 132 } 133 } 134 }() 135 136 return nil 137 }