// +build windows package winterm import ( "fmt" "syscall" "unsafe" ) //=========================================================================================================== // IMPORTANT NOTE: // // The methods below make extensive use of the "unsafe" package to obtain the required pointers. // Beginning in Go 1.3, the garbage collector may release local variables (e.g., incoming arguments, stack // variables) the pointers reference *before* the API completes. // // As a result, in those cases, the code must hint that the variables remain in active by invoking the // dummy method "use" (see below). Newer versions of Go are planned to change the mechanism to no longer // require unsafe pointers. // // If you add or modify methods, ENSURE protection of local variables through the "use" builtin to inform // the garbage collector the variables remain in use if: // // -- The value is not a pointer (e.g., int32, struct) // -- The value is not referenced by the method after passing the pointer to Windows // // See http://golang.org/doc/go1.3. //=========================================================================================================== var ( kernel32DLL = syscall.NewLazyDLL("kernel32.dll") getConsoleCursorInfoProc = kernel32DLL.NewProc("GetConsoleCursorInfo") setConsoleCursorInfoProc = kernel32DLL.NewProc("SetConsoleCursorInfo") setConsoleCursorPositionProc = kernel32DLL.NewProc("SetConsoleCursorPosition") setConsoleModeProc = kernel32DLL.NewProc("SetConsoleMode") getConsoleScreenBufferInfoProc = kernel32DLL.NewProc("GetConsoleScreenBufferInfo") setConsoleScreenBufferSizeProc = kernel32DLL.NewProc("SetConsoleScreenBufferSize") scrollConsoleScreenBufferProc = kernel32DLL.NewProc("ScrollConsoleScreenBufferA") setConsoleTextAttributeProc = kernel32DLL.NewProc("SetConsoleTextAttribute") setConsoleWindowInfoProc = kernel32DLL.NewProc("SetConsoleWindowInfo") writeConsoleOutputProc = kernel32DLL.NewProc("WriteConsoleOutputW") readConsoleInputProc = kernel32DLL.NewProc("ReadConsoleInputW") waitForSingleObjectProc = kernel32DLL.NewProc("WaitForSingleObject") ) // Windows Console constants const ( // Console modes // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx. ENABLE_PROCESSED_INPUT = 0x0001 ENABLE_LINE_INPUT = 0x0002 ENABLE_ECHO_INPUT = 0x0004 ENABLE_WINDOW_INPUT = 0x0008 ENABLE_MOUSE_INPUT = 0x0010 ENABLE_INSERT_MODE = 0x0020 ENABLE_QUICK_EDIT_MODE = 0x0040 ENABLE_EXTENDED_FLAGS = 0x0080 ENABLE_PROCESSED_OUTPUT = 0x0001 ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002 // Character attributes // Note: // -- The attributes are combined to produce various colors (e.g., Blue + Green will create Cyan). // Clearing all foreground or background colors results in black; setting all creates white. // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms682088(v=vs.85).aspx#_win32_character_attributes. FOREGROUND_BLUE WORD = 0x0001 FOREGROUND_GREEN WORD = 0x0002 FOREGROUND_RED WORD = 0x0004 FOREGROUND_INTENSITY WORD = 0x0008 FOREGROUND_MASK WORD = 0x000F BACKGROUND_BLUE WORD = 0x0010 BACKGROUND_GREEN WORD = 0x0020 BACKGROUND_RED WORD = 0x0040 BACKGROUND_INTENSITY WORD = 0x0080 BACKGROUND_MASK WORD = 0x00F0 COMMON_LVB_MASK WORD = 0xFF00 COMMON_LVB_REVERSE_VIDEO WORD = 0x4000 COMMON_LVB_UNDERSCORE WORD = 0x8000 // Input event types // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683499(v=vs.85).aspx. KEY_EVENT = 0x0001 MOUSE_EVENT = 0x0002 WINDOW_BUFFER_SIZE_EVENT = 0x0004 MENU_EVENT = 0x0008 FOCUS_EVENT = 0x0010 // WaitForSingleObject return codes WAIT_ABANDONED = 0x00000080 WAIT_FAILED = 0xFFFFFFFF WAIT_SIGNALED = 0x0000000 WAIT_TIMEOUT = 0x00000102 // WaitForSingleObject wait duration WAIT_INFINITE = 0xFFFFFFFF WAIT_ONE_SECOND = 1000 WAIT_HALF_SECOND = 500 WAIT_QUARTER_SECOND = 250 ) // Windows API Console types // -- See https://msdn.microsoft.com/en-us/library/windows/desktop/aa383751(v=vs.85).aspx for core types (e.g., SHORT) // -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms682101(v=vs.85).aspx for Console specific types (e.g., COORD) // -- See https://msdn.microsoft.com/en-us/library/aa296569(v=vs.60).aspx for comments on alignment type ( SHORT int16 BOOL int32 WORD uint16 WCHAR uint16 DWORD uint32 CHAR_INFO struct { UnicodeChar WCHAR Attributes WORD } CONSOLE_CURSOR_INFO struct { Size DWORD Visible BOOL } CONSOLE_SCREEN_BUFFER_INFO struct { Size COORD CursorPosition COORD Attributes WORD Window SMALL_RECT MaximumWindowSize COORD } COORD struct { X SHORT Y SHORT } SMALL_RECT struct { Left SHORT Top SHORT Right SHORT Bottom SHORT } // INPUT_RECORD is a C/C++ union of which KEY_EVENT_RECORD is one case, it is also the largest // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683499(v=vs.85).aspx. INPUT_RECORD struct { EventType WORD KeyEvent KEY_EVENT_RECORD } KEY_EVENT_RECORD struct { KeyDown BOOL RepeatCount WORD VirtualKeyCode WORD VirtualScanCode WORD UnicodeChar WCHAR ControlKeyState DWORD } WINDOW_BUFFER_SIZE struct { Size COORD } ) // boolToBOOL converts a Go bool into a Windows BOOL. func boolToBOOL(f bool) BOOL { if f { return BOOL(1) } else { return BOOL(0) } } // GetConsoleCursorInfo retrieves information about the size and visiblity of the console cursor. // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683163(v=vs.85).aspx. func GetConsoleCursorInfo(handle uintptr, cursorInfo *CONSOLE_CURSOR_INFO) error { r1, r2, err := getConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(cursorInfo)), 0) return checkError(r1, r2, err) } // SetConsoleCursorInfo sets the size and visiblity of the console cursor. // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686019(v=vs.85).aspx. func SetConsoleCursorInfo(handle uintptr, cursorInfo *CONSOLE_CURSOR_INFO) error { r1, r2, err := setConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(cursorInfo)), 0) return checkError(r1, r2, err) } // SetConsoleCursorPosition location of the console cursor. // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686025(v=vs.85).aspx. func SetConsoleCursorPosition(handle uintptr, coord COORD) error { r1, r2, err := setConsoleCursorPositionProc.Call(handle, coordToPointer(coord)) use(coord) return checkError(r1, r2, err) } // GetConsoleMode gets the console mode for given file descriptor // See http://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx. func GetConsoleMode(handle uintptr) (mode uint32, err error) { err = syscall.GetConsoleMode(syscall.Handle(handle), &mode) return mode, err } // SetConsoleMode sets the console mode for given file descriptor // See http://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx. func SetConsoleMode(handle uintptr, mode uint32) error { r1, r2, err := setConsoleModeProc.Call(handle, uintptr(mode), 0) use(mode) return checkError(r1, r2, err) } // GetConsoleScreenBufferInfo retrieves information about the specified console screen buffer. // See http://msdn.microsoft.com/en-us/library/windows/desktop/ms683171(v=vs.85).aspx. func GetConsoleScreenBufferInfo(handle uintptr) (*CONSOLE_SCREEN_BUFFER_INFO, error) { info := CONSOLE_SCREEN_BUFFER_INFO{} err := checkError(getConsoleScreenBufferInfoProc.Call(handle, uintptr(unsafe.Pointer(&info)), 0)) if err != nil { return nil, err } return &info, nil } func ScrollConsoleScreenBuffer(handle uintptr, scrollRect SMALL_RECT, clipRect SMALL_RECT, destOrigin COORD, char CHAR_INFO) error { r1, r2, err := scrollConsoleScreenBufferProc.Call(handle, uintptr(unsafe.Pointer(&scrollRect)), uintptr(unsafe.Pointer(&clipRect)), coordToPointer(destOrigin), uintptr(unsafe.Pointer(&char))) use(scrollRect) use(clipRect) use(destOrigin) use(char) return checkError(r1, r2, err) } // SetConsoleScreenBufferSize sets the size of the console screen buffer. // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686044(v=vs.85).aspx. func SetConsoleScreenBufferSize(handle uintptr, coord COORD) error { r1, r2, err := setConsoleScreenBufferSizeProc.Call(handle, coordToPointer(coord)) use(coord) return checkError(r1, r2, err) } // SetConsoleTextAttribute sets the attributes of characters written to the // console screen buffer by the WriteFile or WriteConsole function. // See http://msdn.microsoft.com/en-us/library/windows/desktop/ms686047(v=vs.85).aspx. func SetConsoleTextAttribute(handle uintptr, attribute WORD) error { r1, r2, err := setConsoleTextAttributeProc.Call(handle, uintptr(attribute), 0) use(attribute) return checkError(r1, r2, err) } // SetConsoleWindowInfo sets the size and position of the console screen buffer's window. // Note that the size and location must be within and no larger than the backing console screen buffer. // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686125(v=vs.85).aspx. func SetConsoleWindowInfo(handle uintptr, isAbsolute bool, rect SMALL_RECT) error { r1, r2, err := setConsoleWindowInfoProc.Call(handle, uintptr(boolToBOOL(isAbsolute)), uintptr(unsafe.Pointer(&rect))) use(isAbsolute) use(rect) return checkError(r1, r2, err) } // WriteConsoleOutput writes the CHAR_INFOs from the provided buffer to the active console buffer. // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms687404(v=vs.85).aspx. func WriteConsoleOutput(handle uintptr, buffer []CHAR_INFO, bufferSize COORD, bufferCoord COORD, writeRegion *SMALL_RECT) error { r1, r2, err := writeConsoleOutputProc.Call(handle, uintptr(unsafe.Pointer(&buffer[0])), coordToPointer(bufferSize), coordToPointer(bufferCoord), uintptr(unsafe.Pointer(writeRegion))) use(buffer) use(bufferSize) use(bufferCoord) return checkError(r1, r2, err) } // ReadConsoleInput reads (and removes) data from the console input buffer. // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms684961(v=vs.85).aspx. func ReadConsoleInput(handle uintptr, buffer []INPUT_RECORD, count *uint32) error { r1, r2, err := readConsoleInputProc.Call(handle, uintptr(unsafe.Pointer(&buffer[0])), uintptr(len(buffer)), uintptr(unsafe.Pointer(count))) use(buffer) return checkError(r1, r2, err) } // WaitForSingleObject waits for the passed handle to be signaled. // It returns true if the handle was signaled; false otherwise. // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms687032(v=vs.85).aspx. func WaitForSingleObject(handle uintptr, msWait uint32) (bool, error) { r1, _, err := waitForSingleObjectProc.Call(handle, uintptr(DWORD(msWait))) switch r1 { case WAIT_ABANDONED, WAIT_TIMEOUT: return false, nil case WAIT_SIGNALED: return true, nil } use(msWait) return false, err } // String helpers func (info CONSOLE_SCREEN_BUFFER_INFO) String() string { return fmt.Sprintf("Size(%v) Cursor(%v) Window(%v) Max(%v)", info.Size, info.CursorPosition, info.Window, info.MaximumWindowSize) } func (coord COORD) String() string { return fmt.Sprintf("%v,%v", coord.X, coord.Y) } func (rect SMALL_RECT) String() string { return fmt.Sprintf("(%v,%v),(%v,%v)", rect.Left, rect.Top, rect.Right, rect.Bottom) } // checkError evaluates the results of a Windows API call and returns the error if it failed. func checkError(r1, r2 uintptr, err error) error { // Windows APIs return non-zero to indicate success if r1 != 0 { return nil } // Return the error if provided, otherwise default to EINVAL if err != nil { return err } return syscall.EINVAL } // coordToPointer converts a COORD into a uintptr (by fooling the type system). func coordToPointer(c COORD) uintptr { // Note: This code assumes the two SHORTs are correctly laid out; the "cast" to DWORD is just to get a pointer to pass. return uintptr(*((*DWORD)(unsafe.Pointer(&c)))) } // use is a no-op, but the compiler cannot see that it is. // Calling use(p) ensures that p is kept live until that point. func use(p interface{}) {}