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  }