1package terminal 2 3import ( 4 "bytes" 5 "syscall" 6 "unsafe" 7) 8 9var ( 10 dll = syscall.NewLazyDLL("kernel32.dll") 11 setConsoleMode = dll.NewProc("SetConsoleMode") 12 getConsoleMode = dll.NewProc("GetConsoleMode") 13 readConsoleInput = dll.NewProc("ReadConsoleInputW") 14) 15 16const ( 17 EVENT_KEY = 0x0001 18 19 // key codes for arrow keys 20 // https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx 21 VK_DELETE = 0x2E 22 VK_END = 0x23 23 VK_HOME = 0x24 24 VK_LEFT = 0x25 25 VK_UP = 0x26 26 VK_RIGHT = 0x27 27 VK_DOWN = 0x28 28 29 RIGHT_CTRL_PRESSED = 0x0004 30 LEFT_CTRL_PRESSED = 0x0008 31 32 ENABLE_ECHO_INPUT uint32 = 0x0004 33 ENABLE_LINE_INPUT uint32 = 0x0002 34 ENABLE_PROCESSED_INPUT uint32 = 0x0001 35) 36 37type inputRecord struct { 38 eventType uint16 39 padding uint16 40 event [16]byte 41} 42 43type keyEventRecord struct { 44 bKeyDown int32 45 wRepeatCount uint16 46 wVirtualKeyCode uint16 47 wVirtualScanCode uint16 48 unicodeChar uint16 49 wdControlKeyState uint32 50} 51 52type runeReaderState struct { 53 term uint32 54} 55 56func newRuneReaderState(input FileReader) runeReaderState { 57 return runeReaderState{} 58} 59 60func (rr *RuneReader) Buffer() *bytes.Buffer { 61 return nil 62} 63 64func (rr *RuneReader) SetTermMode() error { 65 r, _, err := getConsoleMode.Call(uintptr(rr.stdio.In.Fd()), uintptr(unsafe.Pointer(&rr.state.term))) 66 // windows return 0 on error 67 if r == 0 { 68 return err 69 } 70 71 newState := rr.state.term 72 newState &^= ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT 73 r, _, err = setConsoleMode.Call(uintptr(rr.stdio.In.Fd()), uintptr(newState)) 74 // windows return 0 on error 75 if r == 0 { 76 return err 77 } 78 return nil 79} 80 81func (rr *RuneReader) RestoreTermMode() error { 82 r, _, err := setConsoleMode.Call(uintptr(rr.stdio.In.Fd()), uintptr(rr.state.term)) 83 // windows return 0 on error 84 if r == 0 { 85 return err 86 } 87 return nil 88} 89 90func (rr *RuneReader) ReadRune() (rune, int, error) { 91 ir := &inputRecord{} 92 bytesRead := 0 93 for { 94 rv, _, e := readConsoleInput.Call(rr.stdio.In.Fd(), uintptr(unsafe.Pointer(ir)), 1, uintptr(unsafe.Pointer(&bytesRead))) 95 // windows returns non-zero to indicate success 96 if rv == 0 && e != nil { 97 return 0, 0, e 98 } 99 100 if ir.eventType != EVENT_KEY { 101 continue 102 } 103 104 // the event data is really a c struct union, so here we have to do an usafe 105 // cast to put the data into the keyEventRecord (since we have already verified 106 // above that this event does correspond to a key event 107 key := (*keyEventRecord)(unsafe.Pointer(&ir.event[0])) 108 // we only care about key down events 109 if key.bKeyDown == 0 { 110 continue 111 } 112 if key.wdControlKeyState&(LEFT_CTRL_PRESSED|RIGHT_CTRL_PRESSED) != 0 && key.unicodeChar == 'C' { 113 return KeyInterrupt, bytesRead, nil 114 } 115 // not a normal character so look up the input sequence from the 116 // virtual key code mappings (VK_*) 117 if key.unicodeChar == 0 { 118 switch key.wVirtualKeyCode { 119 case VK_DOWN: 120 return KeyArrowDown, bytesRead, nil 121 case VK_LEFT: 122 return KeyArrowLeft, bytesRead, nil 123 case VK_RIGHT: 124 return KeyArrowRight, bytesRead, nil 125 case VK_UP: 126 return KeyArrowUp, bytesRead, nil 127 case VK_DELETE: 128 return SpecialKeyDelete, bytesRead, nil 129 case VK_HOME: 130 return SpecialKeyHome, bytesRead, nil 131 case VK_END: 132 return SpecialKeyEnd, bytesRead, nil 133 default: 134 // not a virtual key that we care about so just continue on to 135 // the next input key 136 continue 137 } 138 } 139 r := rune(key.unicodeChar) 140 return r, bytesRead, nil 141 } 142} 143