123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263 |
- // +build windows
- package windowsconsole
- import (
- "bytes"
- "errors"
- "fmt"
- "io"
- "os"
- "strings"
- "unsafe"
- ansiterm "github.com/Azure/go-ansiterm"
- "github.com/Azure/go-ansiterm/winterm"
- )
- const (
- escapeSequence = ansiterm.KEY_ESC_CSI
- )
- // ansiReader wraps a standard input file (e.g., os.Stdin) providing ANSI sequence translation.
- type ansiReader struct {
- file *os.File
- fd uintptr
- buffer []byte
- cbBuffer int
- command []byte
- }
- // NewAnsiReader returns an io.ReadCloser that provides VT100 terminal emulation on top of a
- // Windows console input handle.
- func NewAnsiReader(nFile int) io.ReadCloser {
- initLogger()
- file, fd := winterm.GetStdFile(nFile)
- return &ansiReader{
- file: file,
- fd: fd,
- command: make([]byte, 0, ansiterm.ANSI_MAX_CMD_LENGTH),
- buffer: make([]byte, 0),
- }
- }
- // Close closes the wrapped file.
- func (ar *ansiReader) Close() (err error) {
- return ar.file.Close()
- }
- // Fd returns the file descriptor of the wrapped file.
- func (ar *ansiReader) Fd() uintptr {
- return ar.fd
- }
- // Read reads up to len(p) bytes of translated input events into p.
- func (ar *ansiReader) Read(p []byte) (int, error) {
- if len(p) == 0 {
- return 0, nil
- }
- // Previously read bytes exist, read as much as we can and return
- if len(ar.buffer) > 0 {
- logger.Debugf("Reading previously cached bytes")
- originalLength := len(ar.buffer)
- copiedLength := copy(p, ar.buffer)
- if copiedLength == originalLength {
- ar.buffer = make([]byte, 0, len(p))
- } else {
- ar.buffer = ar.buffer[copiedLength:]
- }
- logger.Debugf("Read from cache p[%d]: % x", copiedLength, p)
- return copiedLength, nil
- }
- // Read and translate key events
- events, err := readInputEvents(ar.fd, len(p))
- if err != nil {
- return 0, err
- } else if len(events) == 0 {
- logger.Debug("No input events detected")
- return 0, nil
- }
- keyBytes := translateKeyEvents(events, []byte(escapeSequence))
- // Save excess bytes and right-size keyBytes
- if len(keyBytes) > len(p) {
- logger.Debugf("Received %d keyBytes, only room for %d bytes", len(keyBytes), len(p))
- ar.buffer = keyBytes[len(p):]
- keyBytes = keyBytes[:len(p)]
- } else if len(keyBytes) == 0 {
- logger.Debug("No key bytes returned from the translator")
- return 0, nil
- }
- copiedLength := copy(p, keyBytes)
- if copiedLength != len(keyBytes) {
- return 0, errors.New("unexpected copy length encountered")
- }
- logger.Debugf("Read p[%d]: % x", copiedLength, p)
- logger.Debugf("Read keyBytes[%d]: % x", copiedLength, keyBytes)
- return copiedLength, nil
- }
- // readInputEvents polls until at least one event is available.
- func readInputEvents(fd uintptr, maxBytes int) ([]winterm.INPUT_RECORD, error) {
- // Determine the maximum number of records to retrieve
- // -- Cast around the type system to obtain the size of a single INPUT_RECORD.
- // unsafe.Sizeof requires an expression vs. a type-reference; the casting
- // tricks the type system into believing it has such an expression.
- recordSize := int(unsafe.Sizeof(*((*winterm.INPUT_RECORD)(unsafe.Pointer(&maxBytes)))))
- countRecords := maxBytes / recordSize
- if countRecords > ansiterm.MAX_INPUT_EVENTS {
- countRecords = ansiterm.MAX_INPUT_EVENTS
- } else if countRecords == 0 {
- countRecords = 1
- }
- logger.Debugf("[windows] readInputEvents: Reading %v records (buffer size %v, record size %v)", countRecords, maxBytes, recordSize)
- // Wait for and read input events
- events := make([]winterm.INPUT_RECORD, countRecords)
- nEvents := uint32(0)
- eventsExist, err := winterm.WaitForSingleObject(fd, winterm.WAIT_INFINITE)
- if err != nil {
- return nil, err
- }
- if eventsExist {
- err = winterm.ReadConsoleInput(fd, events, &nEvents)
- if err != nil {
- return nil, err
- }
- }
- // Return a slice restricted to the number of returned records
- logger.Debugf("[windows] readInputEvents: Read %v events", nEvents)
- return events[:nEvents], nil
- }
- // KeyEvent Translation Helpers
- var arrowKeyMapPrefix = map[uint16]string{
- winterm.VK_UP: "%s%sA",
- winterm.VK_DOWN: "%s%sB",
- winterm.VK_RIGHT: "%s%sC",
- winterm.VK_LEFT: "%s%sD",
- }
- var keyMapPrefix = map[uint16]string{
- winterm.VK_UP: "\x1B[%sA",
- winterm.VK_DOWN: "\x1B[%sB",
- winterm.VK_RIGHT: "\x1B[%sC",
- winterm.VK_LEFT: "\x1B[%sD",
- winterm.VK_HOME: "\x1B[1%s~", // showkey shows ^[[1
- winterm.VK_END: "\x1B[4%s~", // showkey shows ^[[4
- winterm.VK_INSERT: "\x1B[2%s~",
- winterm.VK_DELETE: "\x1B[3%s~",
- winterm.VK_PRIOR: "\x1B[5%s~",
- winterm.VK_NEXT: "\x1B[6%s~",
- winterm.VK_F1: "",
- winterm.VK_F2: "",
- winterm.VK_F3: "\x1B[13%s~",
- winterm.VK_F4: "\x1B[14%s~",
- winterm.VK_F5: "\x1B[15%s~",
- winterm.VK_F6: "\x1B[17%s~",
- winterm.VK_F7: "\x1B[18%s~",
- winterm.VK_F8: "\x1B[19%s~",
- winterm.VK_F9: "\x1B[20%s~",
- winterm.VK_F10: "\x1B[21%s~",
- winterm.VK_F11: "\x1B[23%s~",
- winterm.VK_F12: "\x1B[24%s~",
- }
- // translateKeyEvents converts the input events into the appropriate ANSI string.
- func translateKeyEvents(events []winterm.INPUT_RECORD, escapeSequence []byte) []byte {
- var buffer bytes.Buffer
- for _, event := range events {
- if event.EventType == winterm.KEY_EVENT && event.KeyEvent.KeyDown != 0 {
- buffer.WriteString(keyToString(&event.KeyEvent, escapeSequence))
- }
- }
- return buffer.Bytes()
- }
- // keyToString maps the given input event record to the corresponding string.
- func keyToString(keyEvent *winterm.KEY_EVENT_RECORD, escapeSequence []byte) string {
- if keyEvent.UnicodeChar == 0 {
- return formatVirtualKey(keyEvent.VirtualKeyCode, keyEvent.ControlKeyState, escapeSequence)
- }
- _, alt, control := getControlKeys(keyEvent.ControlKeyState)
- if control {
- // TODO(azlinux): Implement following control sequences
- // <Ctrl>-D Signals the end of input from the keyboard; also exits current shell.
- // <Ctrl>-H Deletes the first character to the left of the cursor. Also called the ERASE key.
- // <Ctrl>-Q Restarts printing after it has been stopped with <Ctrl>-s.
- // <Ctrl>-S Suspends printing on the screen (does not stop the program).
- // <Ctrl>-U Deletes all characters on the current line. Also called the KILL key.
- // <Ctrl>-E Quits current command and creates a core
- }
- // <Alt>+Key generates ESC N Key
- if !control && alt {
- return ansiterm.KEY_ESC_N + strings.ToLower(string(keyEvent.UnicodeChar))
- }
- return string(keyEvent.UnicodeChar)
- }
- // formatVirtualKey converts a virtual key (e.g., up arrow) into the appropriate ANSI string.
- func formatVirtualKey(key uint16, controlState uint32, escapeSequence []byte) string {
- shift, alt, control := getControlKeys(controlState)
- modifier := getControlKeysModifier(shift, alt, control)
- if format, ok := arrowKeyMapPrefix[key]; ok {
- return fmt.Sprintf(format, escapeSequence, modifier)
- }
- if format, ok := keyMapPrefix[key]; ok {
- return fmt.Sprintf(format, modifier)
- }
- return ""
- }
- // getControlKeys extracts the shift, alt, and ctrl key states.
- func getControlKeys(controlState uint32) (shift, alt, control bool) {
- shift = 0 != (controlState & winterm.SHIFT_PRESSED)
- alt = 0 != (controlState & (winterm.LEFT_ALT_PRESSED | winterm.RIGHT_ALT_PRESSED))
- control = 0 != (controlState & (winterm.LEFT_CTRL_PRESSED | winterm.RIGHT_CTRL_PRESSED))
- return shift, alt, control
- }
- // getControlKeysModifier returns the ANSI modifier for the given combination of control keys.
- func getControlKeysModifier(shift, alt, control bool) string {
- if shift && alt && control {
- return ansiterm.KEY_CONTROL_PARAM_8
- }
- if alt && control {
- return ansiterm.KEY_CONTROL_PARAM_7
- }
- if shift && control {
- return ansiterm.KEY_CONTROL_PARAM_6
- }
- if control {
- return ansiterm.KEY_CONTROL_PARAM_5
- }
- if shift && alt {
- return ansiterm.KEY_CONTROL_PARAM_4
- }
- if alt {
- return ansiterm.KEY_CONTROL_PARAM_3
- }
- if shift {
- return ansiterm.KEY_CONTROL_PARAM_2
- }
- return ""
- }
|