github.com/kolbycrouch/elvish@v0.14.1-0.20210614162631-215b9ac1c423/pkg/cli/term/reader_windows.go (about)

     1  package term
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"log"
     7  	"os"
     8  	"sync"
     9  	"time"
    10  
    11  	"golang.org/x/sys/windows"
    12  	"src.elv.sh/pkg/sys"
    13  	"src.elv.sh/pkg/ui"
    14  )
    15  
    16  // TODO(xiaq): Put here to make edit package build on Windows. Refactor so
    17  // that isn't needed?
    18  const DefaultSeqTimeout = 10 * time.Millisecond
    19  
    20  type reader struct {
    21  	console   windows.Handle
    22  	stopEvent windows.Handle
    23  	// A mutex that is held during ReadEvent.
    24  	mutex sync.Mutex
    25  }
    26  
    27  // Creates a new Reader instance.
    28  func newReader(file *os.File) Reader {
    29  	console, err := windows.GetStdHandle(windows.STD_INPUT_HANDLE)
    30  	if err != nil {
    31  		panic(fmt.Errorf("GetStdHandle(STD_INPUT_HANDLE): %v", err))
    32  	}
    33  	stopEvent, err := windows.CreateEvent(nil, 0, 0, nil)
    34  	if err != nil {
    35  		panic(fmt.Errorf("CreateEvent: %v", err))
    36  	}
    37  	return &reader{console: console, stopEvent: stopEvent}
    38  }
    39  
    40  func (r *reader) ReadEvent() (Event, error) {
    41  	r.mutex.Lock()
    42  	defer r.mutex.Unlock()
    43  	handles := []windows.Handle{r.console, r.stopEvent}
    44  	for {
    45  		triggered, _, err := sys.WaitForMultipleObjects(handles, false, sys.INFINITE)
    46  		if err != nil {
    47  			return nil, err
    48  		}
    49  		if triggered == 1 {
    50  			return nil, ErrStopped
    51  		}
    52  
    53  		var buf [1]sys.InputRecord
    54  		nr, err := sys.ReadConsoleInput(r.console, buf[:])
    55  		if nr == 0 {
    56  			return nil, io.ErrNoProgress
    57  		}
    58  		if err != nil {
    59  			return nil, err
    60  		}
    61  		event := convertEvent(buf[0].GetEvent())
    62  		if event != nil {
    63  			return event, nil
    64  		}
    65  		// Got an event that should be ignored; keep going.
    66  	}
    67  }
    68  
    69  func (r *reader) ReadRawEvent() (Event, error) {
    70  	return r.ReadEvent()
    71  }
    72  
    73  func (r *reader) Close() {
    74  	err := windows.SetEvent(r.stopEvent)
    75  	if err != nil {
    76  		log.Println("SetEvent:", err)
    77  	}
    78  	r.mutex.Lock()
    79  	r.mutex.Unlock()
    80  	err = windows.CloseHandle(r.stopEvent)
    81  	if err != nil {
    82  		log.Println("Closing stopEvent handle for reader:", err)
    83  	}
    84  }
    85  
    86  // A subset of virtual key codes listed in
    87  // https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
    88  var keyCodeToRune = map[uint16]rune{
    89  	0x08: ui.Backspace, 0x09: ui.Tab,
    90  	0x0d: ui.Enter,
    91  	0x1b: '\x1b',
    92  	0x20: ' ',
    93  	0x23: ui.End, 0x24: ui.Home,
    94  	0x25: ui.Left, 0x26: ui.Up, 0x27: ui.Right, 0x28: ui.Down,
    95  	0x2d: ui.Insert, 0x2e: ui.Delete,
    96  	/* 0x30 - 0x39: digits, same with ASCII */
    97  	/* 0x41 - 0x5a: letters, same with ASCII */
    98  	/* 0x60 - 0x6f: numpads; currently ignored */
    99  	0x70: ui.F1, 0x71: ui.F2, 0x72: ui.F3, 0x73: ui.F4, 0x74: ui.F5, 0x75: ui.F6,
   100  	0x76: ui.F7, 0x77: ui.F8, 0x78: ui.F9, 0x79: ui.F10, 0x7a: ui.F11, 0x7b: ui.F12,
   101  	/* 0x7c - 0x87: F13 - F24; currently ignored */
   102  	0xba: ';', 0xbb: '=', 0xbc: ',', 0xbd: '-', 0xbe: '.', 0xbf: '/', 0xc0: '`',
   103  	0xdb: '[', 0xdc: '\\', 0xdd: ']', 0xde: '\'',
   104  }
   105  
   106  // A subset of constants listed in
   107  // https://docs.microsoft.com/en-us/windows/console/key-event-record-str
   108  const (
   109  	leftAlt   = 0x02
   110  	leftCtrl  = 0x08
   111  	rightAlt  = 0x01
   112  	rightCtrl = 0x04
   113  	shift     = 0x10
   114  )
   115  
   116  // convertEvent converts the native sys.InputEvent type to a suitable Event
   117  // type. It returns nil if the event should be ignored.
   118  func convertEvent(event sys.InputEvent) Event {
   119  	switch event := event.(type) {
   120  	case *sys.KeyEvent:
   121  		if event.BKeyDown == 0 {
   122  			// Ignore keyup events.
   123  			return nil
   124  		}
   125  		r := rune(event.UChar[0]) + rune(event.UChar[1])<<8
   126  		filteredMod := event.DwControlKeyState & (leftAlt | leftCtrl | rightAlt | rightCtrl | shift)
   127  		if filteredMod == 0 {
   128  			// No modifier
   129  			// TODO: Deal with surrogate pairs
   130  			if 0x20 <= r && r != 0x7f {
   131  				return KeyEvent(ui.Key{Rune: r})
   132  			}
   133  		} else if filteredMod == shift {
   134  			// If only the shift is held down, we try and see if this is a
   135  			// non-functional key by looking if the rune generated is a
   136  			// printable ASCII character.
   137  			if 0x20 <= r && r < 0x7f {
   138  				return KeyEvent(ui.Key{Rune: r})
   139  			}
   140  		}
   141  		mod := convertMod(filteredMod)
   142  		if mod == 0 && event.WVirtualKeyCode == 0x1b {
   143  			// Special case: Normalize 0x1b to Ctrl-[.
   144  			//
   145  			// TODO(xiaq): This is Unix-centric. Maybe the normalized form
   146  			// should be Escape.
   147  			return KeyEvent(ui.Key{Rune: '[', Mod: ui.Ctrl})
   148  		}
   149  		r = convertRune(event.WVirtualKeyCode, mod)
   150  		if r == 0 {
   151  			return nil
   152  		}
   153  		return KeyEvent(ui.Key{Rune: r, Mod: mod})
   154  	//case *sys.MouseEvent:
   155  	//case *sys.WindowBufferSizeEvent:
   156  	default:
   157  		// Other events are ignored.
   158  		return nil
   159  	}
   160  }
   161  
   162  func convertRune(keyCode uint16, mod ui.Mod) rune {
   163  	r, ok := keyCodeToRune[keyCode]
   164  	if ok {
   165  		return r
   166  	}
   167  	if '0' <= keyCode && keyCode <= '9' {
   168  		return rune(keyCode)
   169  	}
   170  	if 'A' <= keyCode && keyCode <= 'Z' {
   171  		// If Ctrl is involved, emulate UNIX's convention and use upper case;
   172  		// otherwise use lower case.
   173  		//
   174  		// TODO(xiaq): This is quite Unix-centric. Maybe we should make the
   175  		// base rune case-insensitive when there are modifiers involved.
   176  		if mod&ui.Ctrl != 0 {
   177  			return rune(keyCode)
   178  		}
   179  		return rune(keyCode - 'A' + 'a')
   180  	}
   181  	return 0
   182  }
   183  
   184  func convertMod(state uint32) ui.Mod {
   185  	mod := ui.Mod(0)
   186  	if state&(leftAlt|rightAlt) != 0 {
   187  		mod |= ui.Alt
   188  	}
   189  	if state&(leftCtrl|rightCtrl) != 0 {
   190  		mod |= ui.Ctrl
   191  	}
   192  	if state&shift != 0 {
   193  		mod |= ui.Shift
   194  	}
   195  	return mod
   196  }