216 lines
6.6 KiB
Go
216 lines
6.6 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 scMap map[string]*scancode
|
|
|
|
type pcXTDriver struct {
|
|
interval time.Duration
|
|
sendImpl SendCodeFunc
|
|
specialMap scMap
|
|
scancodeMap map[rune]byte
|
|
buffer [][]string
|
|
// TODO: set from env
|
|
scancodeChunkSize int
|
|
}
|
|
|
|
type scancode struct {
|
|
make []string
|
|
break_ []string
|
|
}
|
|
|
|
func (sc *scancode) makeBreak() []string {
|
|
return append(sc.make, sc.break_...)
|
|
}
|
|
|
|
// 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, interval time.Duration) *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
|
|
}
|
|
// Override interval based on builder-specific override
|
|
if interval > time.Duration(0) {
|
|
keyInterval = interval
|
|
}
|
|
// 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(scMap)
|
|
sMap["bs"] = &scancode{[]string{"0e"}, []string{"8e"}}
|
|
sMap["del"] = &scancode{[]string{"e0", "53"}, []string{"e0", "d3"}}
|
|
sMap["down"] = &scancode{[]string{"e0", "50"}, []string{"e0", "d0"}}
|
|
sMap["end"] = &scancode{[]string{"e0", "4f"}, []string{"e0", "cf"}}
|
|
sMap["enter"] = &scancode{[]string{"1c"}, []string{"9c"}}
|
|
sMap["esc"] = &scancode{[]string{"01"}, []string{"81"}}
|
|
sMap["f1"] = &scancode{[]string{"3b"}, []string{"bb"}}
|
|
sMap["f2"] = &scancode{[]string{"3c"}, []string{"bc"}}
|
|
sMap["f3"] = &scancode{[]string{"3d"}, []string{"bd"}}
|
|
sMap["f4"] = &scancode{[]string{"3e"}, []string{"be"}}
|
|
sMap["f5"] = &scancode{[]string{"3f"}, []string{"bf"}}
|
|
sMap["f6"] = &scancode{[]string{"40"}, []string{"c0"}}
|
|
sMap["f7"] = &scancode{[]string{"41"}, []string{"c1"}}
|
|
sMap["f8"] = &scancode{[]string{"42"}, []string{"c2"}}
|
|
sMap["f9"] = &scancode{[]string{"43"}, []string{"c3"}}
|
|
sMap["f10"] = &scancode{[]string{"44"}, []string{"c4"}}
|
|
sMap["f11"] = &scancode{[]string{"57"}, []string{"d7"}}
|
|
sMap["f12"] = &scancode{[]string{"58"}, []string{"d8"}}
|
|
sMap["home"] = &scancode{[]string{"e0", "47"}, []string{"e0", "c7"}}
|
|
sMap["insert"] = &scancode{[]string{"e0", "52"}, []string{"e0", "d2"}}
|
|
sMap["left"] = &scancode{[]string{"e0", "4b"}, []string{"e0", "cb"}}
|
|
sMap["leftalt"] = &scancode{[]string{"38"}, []string{"b8"}}
|
|
sMap["leftctrl"] = &scancode{[]string{"1d"}, []string{"9d"}}
|
|
sMap["leftshift"] = &scancode{[]string{"2a"}, []string{"aa"}}
|
|
sMap["leftsuper"] = &scancode{[]string{"e0", "5b"}, []string{"e0", "db"}}
|
|
sMap["menu"] = &scancode{[]string{"e0", "5d"}, []string{"e0", "dd"}}
|
|
sMap["pagedown"] = &scancode{[]string{"e0", "51"}, []string{"e0", "d1"}}
|
|
sMap["pageup"] = &scancode{[]string{"e0", "49"}, []string{"e0", "c9"}}
|
|
sMap["return"] = &scancode{[]string{"1c"}, []string{"9c"}}
|
|
sMap["right"] = &scancode{[]string{"e0", "4d"}, []string{"e0", "cd"}}
|
|
sMap["rightalt"] = &scancode{[]string{"e0", "38"}, []string{"e0", "b8"}}
|
|
sMap["rightctrl"] = &scancode{[]string{"e0", "1d"}, []string{"e0", "9d"}}
|
|
sMap["rightshift"] = &scancode{[]string{"36"}, []string{"b6"}}
|
|
sMap["rightsuper"] = &scancode{[]string{"e0", "5c"}, []string{"e0", "dc"}}
|
|
sMap["spacebar"] = &scancode{[]string{"39"}, []string{"b9"}}
|
|
sMap["tab"] = &scancode{[]string{"0f"}, []string{"8f"}}
|
|
sMap["up"] = &scancode{[]string{"e0", "48"}, []string{"e0", "c8"}}
|
|
|
|
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,
|
|
}
|
|
}
|
|
|
|
// Flush send all scanecodes.
|
|
func (d *pcXTDriver) Flush() 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 sc []string
|
|
|
|
if action&(KeyOn|KeyPress) != 0 {
|
|
scInt := d.scancodeMap[key]
|
|
if keyShift {
|
|
sc = append(sc, "2a")
|
|
}
|
|
sc = append(sc, fmt.Sprintf("%02x", scInt))
|
|
}
|
|
|
|
if action&(KeyOff|KeyPress) != 0 {
|
|
scInt := d.scancodeMap[key] + 0x80
|
|
if keyShift {
|
|
sc = append(sc, "aa")
|
|
}
|
|
sc = append(sc, fmt.Sprintf("%02x", scInt))
|
|
}
|
|
|
|
log.Printf("Sending char '%c', code '%s', shift %v",
|
|
key, strings.Join(sc, ""), keyShift)
|
|
|
|
d.send(sc)
|
|
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)
|
|
}
|
|
log.Printf("Special code '%s' '<%s>' found, replacing with: %v", action.String(), special, keyCode)
|
|
|
|
switch action {
|
|
case KeyOn:
|
|
d.send(keyCode.make)
|
|
case KeyOff:
|
|
d.send(keyCode.break_)
|
|
case KeyPress:
|
|
d.send(keyCode.makeBreak())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// send stores the codes in an internal buffer. Use Flush to send 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
|
|
}
|