packer-cn/common/bootcommand/pc_xt_driver.go

203 lines
5.5 KiB
Go

package bootcommand
import (
"fmt"
"log"
"os"
"strings"
"time"
"unicode"
"unicode/utf8"
"github.com/hashicorp/packer/common"
)
// SendCodeFunc will be called to send codes to the VM
type SendCodeFunc func([]string) error
type pcXTDriver struct {
interval time.Duration
sendImpl SendCodeFunc
specialMap map[string][]string
scancodeMap map[rune]byte
buffer [][]string
// TODO: set from env
scancodeChunkSize int
}
// NewPCXTDriver creates a new boot command driver for VMs that expect PC-XT
// keyboard codes. `send` should send its argument to the VM. `chunkSize` should
// be the maximum number of keyboard codes to send to `send` at one time.
func NewPCXTDriver(send SendCodeFunc, chunkSize int) *pcXTDriver {
// We delay (default 100ms) between each input event to allow for CPU or
// network latency. See PackerKeyEnv for tuning.
keyInterval := common.PackerKeyDefault
if delay, err := time.ParseDuration(os.Getenv(common.PackerKeyEnv)); err == nil {
keyInterval = delay
}
// Scancodes reference: https://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html
// https://www.win.tue.nl/~aeb/linux/kbd/scancodes-10.html
//
// Scancodes are recorded here in pairs. The first entry represents
// the key press and the second entry represents the key release and is
// derived from the first by the addition of 0x80.
sMap := make(map[string][]string)
sMap["bs"] = []string{"0e", "8e"}
sMap["del"] = []string{"e053", "e0d3"}
sMap["down"] = []string{"e050", "e0d0"}
sMap["end"] = []string{"e04f", "e0cf"}
sMap["enter"] = []string{"1c", "9c"}
sMap["esc"] = []string{"01", "81"}
sMap["f1"] = []string{"3b", "bb"}
sMap["f2"] = []string{"3c", "bc"}
sMap["f3"] = []string{"3d", "bd"}
sMap["f4"] = []string{"3e", "be"}
sMap["f5"] = []string{"3f", "bf"}
sMap["f6"] = []string{"40", "c0"}
sMap["f7"] = []string{"41", "c1"}
sMap["f8"] = []string{"42", "c2"}
sMap["f9"] = []string{"43", "c3"}
sMap["f10"] = []string{"44", "c4"}
sMap["f11"] = []string{"57", "d7"}
sMap["f12"] = []string{"58", "d8"}
sMap["home"] = []string{"e047", "e0c7"}
sMap["insert"] = []string{"e052", "e0d2"}
sMap["left"] = []string{"e04b", "e0cb"}
sMap["leftalt"] = []string{"38", "b8"}
sMap["leftctrl"] = []string{"1d", "9d"}
sMap["leftshift"] = []string{"2a", "aa"}
sMap["leftsuper"] = []string{"e05b", "e0db"}
sMap["menu"] = []string{"e05d", "e0dd"}
sMap["pagedown"] = []string{"e051", "e0d1"}
sMap["pageup"] = []string{"e049", "e0c9"}
sMap["return"] = []string{"1c", "9c"}
sMap["right"] = []string{"e04d", "e0cd"}
sMap["rightalt"] = []string{"e038", "e0b8"}
sMap["rightctrl"] = []string{"e01d", "e09d"}
sMap["rightshift"] = []string{"36", "b6"}
sMap["rightsuper"] = []string{"e05c", "e0dc"}
sMap["spacebar"] = []string{"39", "b9"}
sMap["tab"] = []string{"0f", "8f"}
sMap["up"] = []string{"e048", "e0c8"}
scancodeIndex := make(map[string]byte)
scancodeIndex["1234567890-="] = 0x02
scancodeIndex["!@#$%^&*()_+"] = 0x02
scancodeIndex["qwertyuiop[]"] = 0x10
scancodeIndex["QWERTYUIOP{}"] = 0x10
scancodeIndex["asdfghjkl;'`"] = 0x1e
scancodeIndex[`ASDFGHJKL:"~`] = 0x1e
scancodeIndex[`\zxcvbnm,./`] = 0x2b
scancodeIndex["|ZXCVBNM<>?"] = 0x2b
scancodeIndex[" "] = 0x39
scancodeMap := make(map[rune]byte)
for chars, start := range scancodeIndex {
var i byte = 0
for len(chars) > 0 {
r, size := utf8.DecodeRuneInString(chars)
chars = chars[size:]
scancodeMap[r] = start + i
i += 1
}
}
return &pcXTDriver{
interval: keyInterval,
sendImpl: send,
specialMap: sMap,
scancodeMap: scancodeMap,
scancodeChunkSize: chunkSize,
}
}
// Finalize flushes all scanecodes.
func (d *pcXTDriver) Finalize() error {
defer func() {
d.buffer = nil
}()
sc, err := chunkScanCodes(d.buffer, d.scancodeChunkSize)
if err != nil {
return err
}
for _, b := range sc {
if err := d.sendImpl(b); err != nil {
return err
}
time.Sleep(d.interval)
}
return nil
}
func (d *pcXTDriver) SendKey(key rune, action KeyAction) error {
keyShift := unicode.IsUpper(key) || strings.ContainsRune(shiftedChars, key)
var scancode []string
if action&(KeyOn|KeyPress) != 0 {
scancodeInt := d.scancodeMap[key]
if keyShift {
scancode = append(scancode, "2a")
}
scancode = append(scancode, fmt.Sprintf("%02x", scancodeInt))
}
if action&(KeyOff|KeyPress) != 0 {
scancodeInt := d.scancodeMap[key] + 0x80
if keyShift {
scancode = append(scancode, "aa")
}
scancode = append(scancode, fmt.Sprintf("%02x", scancodeInt))
}
for _, sc := range scancode {
log.Printf("Sending char '%c', code '%s', shift %v", key, sc, keyShift)
}
d.send(scancode)
return nil
}
func (d *pcXTDriver) SendSpecial(special string, action KeyAction) error {
keyCode, ok := d.specialMap[special]
if !ok {
return fmt.Errorf("special %s not found.", special)
}
switch action {
case KeyOn:
d.send([]string{keyCode[0]})
case KeyOff:
d.send([]string{keyCode[1]})
case KeyPress:
d.send(keyCode)
}
return nil
}
// send stores the codes in an internal buffer. Use finalize to flush them.
func (d *pcXTDriver) send(codes []string) {
d.buffer = append(d.buffer, codes)
}
func chunkScanCodes(sc [][]string, size int) (out [][]string, err error) {
var running []string
for _, codes := range sc {
if size > 0 {
if len(codes) > size {
return nil, fmt.Errorf("chunkScanCodes: size cannot be smaller than sc width.")
}
if len(running)+len(codes) > size {
out = append(out, running)
running = nil
}
}
running = append(running, codes...)
}
if running != nil {
out = append(out, running)
}
return
}