github.com/HashDataInc/packer@v1.3.2/common/bootcommand/pc_xt_driver.go (about)

     1  package bootcommand
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"os"
     7  	"strings"
     8  	"time"
     9  	"unicode"
    10  	"unicode/utf8"
    11  
    12  	"github.com/hashicorp/packer/common"
    13  )
    14  
    15  // SendCodeFunc will be called to send codes to the VM
    16  type SendCodeFunc func([]string) error
    17  type scMap map[string]*scancode
    18  
    19  type pcXTDriver struct {
    20  	interval    time.Duration
    21  	sendImpl    SendCodeFunc
    22  	specialMap  scMap
    23  	scancodeMap map[rune]byte
    24  	buffer      [][]string
    25  	// TODO: set from env
    26  	scancodeChunkSize int
    27  }
    28  
    29  type scancode struct {
    30  	make   []string
    31  	break_ []string
    32  }
    33  
    34  func (sc *scancode) makeBreak() []string {
    35  	return append(sc.make, sc.break_...)
    36  }
    37  
    38  // NewPCXTDriver creates a new boot command driver for VMs that expect PC-XT
    39  // keyboard codes. `send` should send its argument to the VM. `chunkSize` should
    40  // be the maximum number of keyboard codes to send to `send` at one time.
    41  func NewPCXTDriver(send SendCodeFunc, chunkSize int, interval time.Duration) *pcXTDriver {
    42  	// We delay (default 100ms) between each input event to allow for CPU or
    43  	// network latency. See PackerKeyEnv for tuning.
    44  	keyInterval := common.PackerKeyDefault
    45  	if delay, err := time.ParseDuration(os.Getenv(common.PackerKeyEnv)); err == nil {
    46  		keyInterval = delay
    47  	}
    48  	// Override interval based on builder-specific override
    49  	if interval > time.Duration(0) {
    50  		keyInterval = interval
    51  	}
    52  	// Scancodes reference: https://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html
    53  	//						https://www.win.tue.nl/~aeb/linux/kbd/scancodes-10.html
    54  	//
    55  	// Scancodes are recorded here in pairs. The first entry represents
    56  	// the key press and the second entry represents the key release and is
    57  	// derived from the first by the addition of 0x80.
    58  	sMap := make(scMap)
    59  	sMap["bs"] = &scancode{[]string{"0e"}, []string{"8e"}}
    60  	sMap["del"] = &scancode{[]string{"e0", "53"}, []string{"e0", "d3"}}
    61  	sMap["down"] = &scancode{[]string{"e0", "50"}, []string{"e0", "d0"}}
    62  	sMap["end"] = &scancode{[]string{"e0", "4f"}, []string{"e0", "cf"}}
    63  	sMap["enter"] = &scancode{[]string{"1c"}, []string{"9c"}}
    64  	sMap["esc"] = &scancode{[]string{"01"}, []string{"81"}}
    65  	sMap["f1"] = &scancode{[]string{"3b"}, []string{"bb"}}
    66  	sMap["f2"] = &scancode{[]string{"3c"}, []string{"bc"}}
    67  	sMap["f3"] = &scancode{[]string{"3d"}, []string{"bd"}}
    68  	sMap["f4"] = &scancode{[]string{"3e"}, []string{"be"}}
    69  	sMap["f5"] = &scancode{[]string{"3f"}, []string{"bf"}}
    70  	sMap["f6"] = &scancode{[]string{"40"}, []string{"c0"}}
    71  	sMap["f7"] = &scancode{[]string{"41"}, []string{"c1"}}
    72  	sMap["f8"] = &scancode{[]string{"42"}, []string{"c2"}}
    73  	sMap["f9"] = &scancode{[]string{"43"}, []string{"c3"}}
    74  	sMap["f10"] = &scancode{[]string{"44"}, []string{"c4"}}
    75  	sMap["f11"] = &scancode{[]string{"57"}, []string{"d7"}}
    76  	sMap["f12"] = &scancode{[]string{"58"}, []string{"d8"}}
    77  	sMap["home"] = &scancode{[]string{"e0", "47"}, []string{"e0", "c7"}}
    78  	sMap["insert"] = &scancode{[]string{"e0", "52"}, []string{"e0", "d2"}}
    79  	sMap["left"] = &scancode{[]string{"e0", "4b"}, []string{"e0", "cb"}}
    80  	sMap["leftalt"] = &scancode{[]string{"38"}, []string{"b8"}}
    81  	sMap["leftctrl"] = &scancode{[]string{"1d"}, []string{"9d"}}
    82  	sMap["leftshift"] = &scancode{[]string{"2a"}, []string{"aa"}}
    83  	sMap["leftsuper"] = &scancode{[]string{"e0", "5b"}, []string{"e0", "db"}}
    84  	sMap["menu"] = &scancode{[]string{"e0", "5d"}, []string{"e0", "dd"}}
    85  	sMap["pagedown"] = &scancode{[]string{"e0", "51"}, []string{"e0", "d1"}}
    86  	sMap["pageup"] = &scancode{[]string{"e0", "49"}, []string{"e0", "c9"}}
    87  	sMap["return"] = &scancode{[]string{"1c"}, []string{"9c"}}
    88  	sMap["right"] = &scancode{[]string{"e0", "4d"}, []string{"e0", "cd"}}
    89  	sMap["rightalt"] = &scancode{[]string{"e0", "38"}, []string{"e0", "b8"}}
    90  	sMap["rightctrl"] = &scancode{[]string{"e0", "1d"}, []string{"e0", "9d"}}
    91  	sMap["rightshift"] = &scancode{[]string{"36"}, []string{"b6"}}
    92  	sMap["rightsuper"] = &scancode{[]string{"e0", "5c"}, []string{"e0", "dc"}}
    93  	sMap["spacebar"] = &scancode{[]string{"39"}, []string{"b9"}}
    94  	sMap["tab"] = &scancode{[]string{"0f"}, []string{"8f"}}
    95  	sMap["up"] = &scancode{[]string{"e0", "48"}, []string{"e0", "c8"}}
    96  
    97  	scancodeIndex := make(map[string]byte)
    98  	scancodeIndex["1234567890-="] = 0x02
    99  	scancodeIndex["!@#$%^&*()_+"] = 0x02
   100  	scancodeIndex["qwertyuiop[]"] = 0x10
   101  	scancodeIndex["QWERTYUIOP{}"] = 0x10
   102  	scancodeIndex["asdfghjkl;'`"] = 0x1e
   103  	scancodeIndex[`ASDFGHJKL:"~`] = 0x1e
   104  	scancodeIndex[`\zxcvbnm,./`] = 0x2b
   105  	scancodeIndex["|ZXCVBNM<>?"] = 0x2b
   106  	scancodeIndex[" "] = 0x39
   107  
   108  	scancodeMap := make(map[rune]byte)
   109  	for chars, start := range scancodeIndex {
   110  		var i byte = 0
   111  		for len(chars) > 0 {
   112  			r, size := utf8.DecodeRuneInString(chars)
   113  			chars = chars[size:]
   114  			scancodeMap[r] = start + i
   115  			i += 1
   116  		}
   117  	}
   118  
   119  	return &pcXTDriver{
   120  		interval:          keyInterval,
   121  		sendImpl:          send,
   122  		specialMap:        sMap,
   123  		scancodeMap:       scancodeMap,
   124  		scancodeChunkSize: chunkSize,
   125  	}
   126  }
   127  
   128  // Flush send all scanecodes.
   129  func (d *pcXTDriver) Flush() error {
   130  	defer func() {
   131  		d.buffer = nil
   132  	}()
   133  	sc, err := chunkScanCodes(d.buffer, d.scancodeChunkSize)
   134  	if err != nil {
   135  		return err
   136  	}
   137  	for _, b := range sc {
   138  		if err := d.sendImpl(b); err != nil {
   139  			return err
   140  		}
   141  		time.Sleep(d.interval)
   142  	}
   143  	return nil
   144  }
   145  
   146  func (d *pcXTDriver) SendKey(key rune, action KeyAction) error {
   147  	keyShift := unicode.IsUpper(key) || strings.ContainsRune(shiftedChars, key)
   148  
   149  	var sc []string
   150  
   151  	if action&(KeyOn|KeyPress) != 0 {
   152  		scInt := d.scancodeMap[key]
   153  		if keyShift {
   154  			sc = append(sc, "2a")
   155  		}
   156  		sc = append(sc, fmt.Sprintf("%02x", scInt))
   157  	}
   158  
   159  	if action&(KeyOff|KeyPress) != 0 {
   160  		scInt := d.scancodeMap[key] + 0x80
   161  		if keyShift {
   162  			sc = append(sc, "aa")
   163  		}
   164  		sc = append(sc, fmt.Sprintf("%02x", scInt))
   165  	}
   166  
   167  	log.Printf("Sending char '%c', code '%s', shift %v",
   168  		key, strings.Join(sc, ""), keyShift)
   169  
   170  	d.send(sc)
   171  	return nil
   172  }
   173  
   174  func (d *pcXTDriver) SendSpecial(special string, action KeyAction) error {
   175  	keyCode, ok := d.specialMap[special]
   176  	if !ok {
   177  		return fmt.Errorf("special %s not found.", special)
   178  	}
   179  	log.Printf("Special code '%s' '<%s>' found, replacing with: %v", action.String(), special, keyCode)
   180  
   181  	switch action {
   182  	case KeyOn:
   183  		d.send(keyCode.make)
   184  	case KeyOff:
   185  		d.send(keyCode.break_)
   186  	case KeyPress:
   187  		d.send(keyCode.makeBreak())
   188  	}
   189  	return nil
   190  }
   191  
   192  // send stores the codes in an internal buffer. Use Flush to send them.
   193  func (d *pcXTDriver) send(codes []string) {
   194  	d.buffer = append(d.buffer, codes)
   195  }
   196  
   197  func chunkScanCodes(sc [][]string, size int) (out [][]string, err error) {
   198  	var running []string
   199  	for _, codes := range sc {
   200  		if size > 0 {
   201  			if len(codes) > size {
   202  				return nil, fmt.Errorf("chunkScanCodes: size cannot be smaller than sc width.")
   203  			}
   204  			if len(running)+len(codes) > size {
   205  				out = append(out, running)
   206  				running = nil
   207  			}
   208  		}
   209  		running = append(running, codes...)
   210  	}
   211  	if running != nil {
   212  		out = append(out, running)
   213  	}
   214  	return
   215  }