Add usb_driver to common boot_command and use it on vsphere
This commit is contained in:
parent
fcf7e634d9
commit
585a86fe03
|
@ -0,0 +1,54 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
)
|
||||
|
||||
// Step to discover the http ip
|
||||
// which guests use to reach the vm host
|
||||
// To make sure the IP is set before boot command and http server steps
|
||||
type StepHTTPIPDiscover struct {
|
||||
HTTPIP string
|
||||
}
|
||||
|
||||
func (s *StepHTTPIPDiscover) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ip, err := getHostIP(s.HTTPIP)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
state.Put("http_ip", ip)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepHTTPIPDiscover) Cleanup(state multistep.StateBag) {}
|
||||
|
||||
func getHostIP(s string) (string, error) {
|
||||
if s != "" {
|
||||
if net.ParseIP(s) != nil {
|
||||
return s, nil
|
||||
} else {
|
||||
return "", fmt.Errorf("invalid IP address")
|
||||
}
|
||||
}
|
||||
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, a := range addrs {
|
||||
ipnet, ok := a.(*net.IPNet)
|
||||
if ok && !ipnet.IP.IsLoopback() {
|
||||
if ipnet.IP.To4() != nil {
|
||||
return ipnet.IP.String(), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("IP not found")
|
||||
}
|
|
@ -1,9 +1,6 @@
|
|||
package driver
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/methods"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"golang.org/x/mobile/event/key"
|
||||
|
@ -17,58 +14,7 @@ type KeyInput struct {
|
|||
Shift bool
|
||||
}
|
||||
|
||||
var scancodeMap = make(map[rune]key.Code)
|
||||
|
||||
func init() {
|
||||
scancodeIndex := make(map[string]key.Code)
|
||||
scancodeIndex["abcdefghijklmnopqrstuvwxyz"] = key.CodeA
|
||||
scancodeIndex["ABCDEFGHIJKLMNOPQRSTUVWXYZ"] = key.CodeA
|
||||
scancodeIndex["1234567890"] = key.Code1
|
||||
scancodeIndex["!@#$%^&*()"] = key.Code1
|
||||
scancodeIndex[" "] = key.CodeSpacebar
|
||||
scancodeIndex["-=[]\\"] = key.CodeHyphenMinus
|
||||
scancodeIndex["_+{}|"] = key.CodeHyphenMinus
|
||||
scancodeIndex[";'`,./"] = key.CodeSemicolon
|
||||
scancodeIndex[":\"~<>?"] = key.CodeSemicolon
|
||||
|
||||
for chars, start := range scancodeIndex {
|
||||
for i, r := range chars {
|
||||
scancodeMap[r] = start + key.Code(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const shiftedChars = "!@#$%^&*()_+{}|:\"~<>?"
|
||||
|
||||
func (vm *VirtualMachine) TypeOnKeyboard(input KeyInput) (int32, error) {
|
||||
var spec types.UsbScanCodeSpec
|
||||
|
||||
for _, r := range input.Message {
|
||||
scancode := scancodeMap[r]
|
||||
shift := input.Shift || unicode.IsUpper(r) || strings.ContainsRune(shiftedChars, r)
|
||||
|
||||
spec.KeyEvents = append(spec.KeyEvents, types.UsbScanCodeSpecKeyEvent{
|
||||
// https://github.com/lamw/vghetto-scripts/blob/f74bc8ba20064f46592bcce5a873b161a7fa3d72/powershell/VMKeystrokes.ps1#L130
|
||||
UsbHidCode: int32(scancode)<<16 | 7,
|
||||
Modifiers: &types.UsbScanCodeSpecModifierType{
|
||||
LeftControl: &input.Ctrl,
|
||||
LeftAlt: &input.Alt,
|
||||
LeftShift: &shift,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if input.Scancode != 0 {
|
||||
spec.KeyEvents = append(spec.KeyEvents, types.UsbScanCodeSpecKeyEvent{
|
||||
UsbHidCode: int32(input.Scancode)<<16 | 7,
|
||||
Modifiers: &types.UsbScanCodeSpecModifierType{
|
||||
LeftControl: &input.Ctrl,
|
||||
LeftAlt: &input.Alt,
|
||||
LeftShift: &input.Shift,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (vm *VirtualMachine) TypeOnKeyboard(spec types.UsbScanCodeSpec) (int32, error) {
|
||||
req := &types.PutUsbScanCodes{
|
||||
This: vm.vm.Reference(),
|
||||
Spec: spec,
|
||||
|
|
|
@ -30,6 +30,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
|||
|
||||
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("debug", b.config.PackerDebug)
|
||||
state.Put("hook", hook)
|
||||
state.Put("ui", ui)
|
||||
|
||||
|
@ -89,6 +90,9 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||
Host: b.config.Host,
|
||||
SetHostForDatastoreUploads: b.config.SetHostForDatastoreUploads,
|
||||
},
|
||||
&common.StepHTTPIPDiscover{
|
||||
HTTPIP: b.config.BootConfig.HTTPIP,
|
||||
},
|
||||
&packerCommon.StepHTTPServer{
|
||||
HTTPDir: b.config.HTTPDir,
|
||||
HTTPPortMin: b.config.HTTPPortMin,
|
||||
|
|
|
@ -76,7 +76,7 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
|||
errs = packer.MultiErrorAppend(errs, c.HTTPConfig.Prepare(&c.ctx)...)
|
||||
|
||||
errs = packer.MultiErrorAppend(errs, c.CDRomConfig.Prepare()...)
|
||||
errs = packer.MultiErrorAppend(errs, c.BootConfig.Prepare()...)
|
||||
errs = packer.MultiErrorAppend(errs, c.BootConfig.Prepare(&c.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, c.WaitIpConfig.Prepare()...)
|
||||
errs = packer.MultiErrorAppend(errs, c.Comm.Prepare(&c.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, c.ShutdownConfig.Prepare()...)
|
||||
|
|
|
@ -3,24 +3,18 @@ package iso
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/hashicorp/packer/builder/vsphere/driver"
|
||||
packerCommon "github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/common/bootcommand"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"golang.org/x/mobile/event/key"
|
||||
"time"
|
||||
)
|
||||
|
||||
type BootConfig struct {
|
||||
BootCommand []string `mapstructure:"boot_command"`
|
||||
BootWait time.Duration `mapstructure:"boot_wait"` // example: "1m30s"; default: "10s"
|
||||
bootcommand.BootConfig `mapstructure:",squash"`
|
||||
HTTPIP string `mapstructure:"http_ip"`
|
||||
}
|
||||
|
||||
|
@ -30,13 +24,15 @@ type bootCommandTemplateData struct {
|
|||
Name string
|
||||
}
|
||||
|
||||
func (c *BootConfig) Prepare() []error {
|
||||
func (c *BootConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
var errs []error
|
||||
|
||||
if c.BootWait == 0 {
|
||||
c.BootWait = 10 * time.Second
|
||||
}
|
||||
|
||||
c.BootConfig.Prepare(ctx)
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
|
@ -46,44 +42,8 @@ type StepBootCommand struct {
|
|||
Ctx interpolate.Context
|
||||
}
|
||||
|
||||
var special = map[string]key.Code{
|
||||
"<enter>": key.CodeReturnEnter,
|
||||
"<esc>": key.CodeEscape,
|
||||
"<bs>": key.CodeDeleteBackspace,
|
||||
"<del>": key.CodeDeleteForward,
|
||||
"<tab>": key.CodeTab,
|
||||
"<f1>": key.CodeF1,
|
||||
"<f2>": key.CodeF2,
|
||||
"<f3>": key.CodeF3,
|
||||
"<f4>": key.CodeF4,
|
||||
"<f5>": key.CodeF5,
|
||||
"<f6>": key.CodeF6,
|
||||
"<f7>": key.CodeF7,
|
||||
"<f8>": key.CodeF8,
|
||||
"<f9>": key.CodeF9,
|
||||
"<f10>": key.CodeF10,
|
||||
"<f11>": key.CodeF11,
|
||||
"<f12>": key.CodeF12,
|
||||
"<insert>": key.CodeInsert,
|
||||
"<home>": key.CodeHome,
|
||||
"<end>": key.CodeEnd,
|
||||
"<pageUp>": key.CodePageUp,
|
||||
"<pageDown>": key.CodePageDown,
|
||||
"<left>": key.CodeLeftArrow,
|
||||
"<right>": key.CodeRightArrow,
|
||||
"<up>": key.CodeUpArrow,
|
||||
"<down>": key.CodeDownArrow,
|
||||
}
|
||||
|
||||
var keyInterval = packerCommon.PackerKeyDefault
|
||||
|
||||
func init() {
|
||||
if delay, err := time.ParseDuration(os.Getenv(packerCommon.PackerKeyEnv)); err == nil {
|
||||
keyInterval = delay
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StepBootCommand) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
func (s *StepBootCommand) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
debug := state.Get("debug").(bool)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
vm := state.Get("vm").(*driver.VirtualMachine)
|
||||
|
||||
|
@ -91,28 +51,25 @@ func (s *StepBootCommand) Run(_ context.Context, state multistep.StateBag) multi
|
|||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Waiting %s for boot...", s.Config.BootWait))
|
||||
wait := time.After(s.Config.BootWait)
|
||||
WAITLOOP:
|
||||
for {
|
||||
// Wait the for the vm to boot.
|
||||
if int64(s.Config.BootWait) > 0 {
|
||||
ui.Say(fmt.Sprintf("Waiting %s for boot...", s.Config.BootWait.String()))
|
||||
select {
|
||||
case <-wait:
|
||||
break WAITLOOP
|
||||
case <-time.After(1 * time.Second):
|
||||
if _, ok := state.GetOk(multistep.StateCancelled); ok {
|
||||
case <-time.After(s.Config.BootWait):
|
||||
break
|
||||
case <-ctx.Done():
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
|
||||
var pauseFn multistep.DebugPauseFn
|
||||
if debug {
|
||||
pauseFn = state.Get("pauseFn").(multistep.DebugPauseFn)
|
||||
}
|
||||
|
||||
port := state.Get("http_port").(int)
|
||||
if port > 0 {
|
||||
ip, err := getHostIP(s.Config.HTTPIP)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
state.Put("http_ip", ip)
|
||||
ip := state.Get("http_ip").(string)
|
||||
s.Ctx.Data = &bootCommandTemplateData{
|
||||
ip,
|
||||
port,
|
||||
|
@ -121,136 +78,72 @@ WAITLOOP:
|
|||
ui.Say(fmt.Sprintf("HTTP server is working at http://%v:%v/", ip, port))
|
||||
}
|
||||
|
||||
ui.Say("Typing boot command...")
|
||||
var keyAlt bool
|
||||
var keyCtrl bool
|
||||
var keyShift bool
|
||||
for _, command := range s.Config.BootCommand {
|
||||
message, err := interpolate.Render(command, &s.Ctx)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
sendCodes := func(codes []key.Code, downs []bool) error {
|
||||
var spec types.UsbScanCodeSpec
|
||||
|
||||
for i, code := range codes {
|
||||
var keyAlt, keyCtrl, keyShift bool
|
||||
|
||||
switch code {
|
||||
case key.CodeLeftAlt:
|
||||
// <leftAltOn>
|
||||
keyAlt = downs[i]
|
||||
case key.CodeLeftControl:
|
||||
// <leftCtrlOn>
|
||||
keyCtrl = downs[i]
|
||||
case key.CodeLeftShift:
|
||||
// <leftShiftOn>
|
||||
keyShift = downs[i]
|
||||
}
|
||||
|
||||
for len(message) > 0 {
|
||||
if _, ok := state.GetOk(multistep.StateCancelled); ok {
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if strings.HasPrefix(message, "<wait>") {
|
||||
log.Printf("Waiting 1 second")
|
||||
time.Sleep(1 * time.Second)
|
||||
message = message[len("<wait>"):]
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(message, "<wait5>") {
|
||||
log.Printf("Waiting 5 seconds")
|
||||
time.Sleep(5 * time.Second)
|
||||
message = message[len("<wait5>"):]
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(message, "<wait10>") {
|
||||
log.Printf("Waiting 10 seconds")
|
||||
time.Sleep(10 * time.Second)
|
||||
message = message[len("<wait10>"):]
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(message, "<leftAltOn>") {
|
||||
keyAlt = true
|
||||
message = message[len("<leftAltOn>"):]
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(message, "<leftAltOff>") {
|
||||
keyAlt = false
|
||||
message = message[len("<leftAltOff>"):]
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(message, "<leftCtrlOn>") {
|
||||
keyCtrl = true
|
||||
message = message[len("<leftCtrlOn>"):]
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(message, "<leftCtrlOff>") {
|
||||
keyCtrl = false
|
||||
message = message[len("<leftCtrlOff>"):]
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(message, "<leftShiftOn>") {
|
||||
keyShift = true
|
||||
message = message[len("<leftShiftOn>"):]
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(message, "<leftShiftOff>") {
|
||||
keyShift = false
|
||||
message = message[len("<leftShiftOff>"):]
|
||||
continue
|
||||
}
|
||||
|
||||
var scancode key.Code
|
||||
for specialCode, specialValue := range special {
|
||||
if strings.HasPrefix(message, specialCode) {
|
||||
scancode = specialValue
|
||||
log.Printf("Special code '%s' found, replacing with: %s", specialCode, specialValue)
|
||||
message = message[len(specialCode):]
|
||||
}
|
||||
}
|
||||
|
||||
var char rune
|
||||
if scancode == 0 {
|
||||
var size int
|
||||
char, size = utf8.DecodeRuneInString(message)
|
||||
message = message[size:]
|
||||
}
|
||||
|
||||
_, err := vm.TypeOnKeyboard(driver.KeyInput{
|
||||
Message: string(char),
|
||||
Scancode: scancode,
|
||||
Ctrl: keyCtrl,
|
||||
Alt: keyAlt,
|
||||
Shift: keyShift,
|
||||
spec.KeyEvents = append(spec.KeyEvents, types.UsbScanCodeSpecKeyEvent{
|
||||
UsbHidCode: int32(code)<<16 | 7,
|
||||
Modifiers: &types.UsbScanCodeSpecModifierType{
|
||||
LeftControl: &keyCtrl,
|
||||
LeftAlt: &keyAlt,
|
||||
LeftShift: &keyShift,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
_, err := vm.TypeOnKeyboard(spec)
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("error typing a boot command: %v", err))
|
||||
return fmt.Errorf("error typing a boot command: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
d := bootcommand.NewUSBDriver(sendCodes, s.Config.BootGroupInterval)
|
||||
|
||||
ui.Say("Typing boot command...")
|
||||
flatBootCommand := s.Config.FlatBootCommand()
|
||||
command, err := interpolate.Render(flatBootCommand, &s.Ctx)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error preparing boot command: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
time.Sleep(keyInterval)
|
||||
|
||||
seq, err := bootcommand.GenerateExpressionSequence(command)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error generating boot command: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if err := seq.Do(ctx, d); err != nil {
|
||||
err := fmt.Errorf("Error running boot command: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if pauseFn != nil {
|
||||
pauseFn(multistep.DebugLocationAfterRun, fmt.Sprintf("boot_command: %s", command), state)
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepBootCommand) Cleanup(state multistep.StateBag) {}
|
||||
|
||||
func getHostIP(s string) (string, error) {
|
||||
if s != "" {
|
||||
if net.ParseIP(s) != nil {
|
||||
return s, nil
|
||||
} else {
|
||||
return "", fmt.Errorf("invalid IP address")
|
||||
}
|
||||
}
|
||||
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, a := range addrs {
|
||||
ipnet, ok := a.(*net.IPNet)
|
||||
if ok && !ipnet.IP.IsLoopback() {
|
||||
if ipnet.IP.To4() != nil {
|
||||
return ipnet.IP.String(), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("IP not found")
|
||||
}
|
||||
func (s *StepBootCommand) Cleanup(_ multistep.StateBag) {}
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
package iso
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"github.com/hashicorp/packer/builder/vsphere/driver"
|
||||
"github.com/hashicorp/packer/common/bootcommand"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStepBootCommand_Run(t *testing.T) {
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("ui", &packer.BasicUi{
|
||||
Reader: new(bytes.Buffer),
|
||||
Writer: new(bytes.Buffer),
|
||||
})
|
||||
state.Put("debug", false)
|
||||
state.Put("vm", new(driver.VirtualMachine))
|
||||
|
||||
state.Put("http_port", 2222)
|
||||
state.Put("http_ip", "0.0.0.0")
|
||||
|
||||
step := &StepBootCommand{
|
||||
Config: &BootConfig{
|
||||
BootConfig: bootcommand.BootConfig{
|
||||
BootCommand: []string{
|
||||
" initrd=/install/initrd.gz",
|
||||
"<leftShiftOn><enter><wait><f6><wait><esc><wait>",
|
||||
"<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
|
||||
"<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
|
||||
"<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
|
||||
"<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
|
||||
"<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
|
||||
"<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
|
||||
"<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
|
||||
"<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
|
||||
"<bs><bs><bs>",
|
||||
"/install/vmlinuz",
|
||||
" initrd=/install/initrd.gz",
|
||||
" priority=critical",
|
||||
" locale=en_US",
|
||||
" file=/media/preseed_hardcoded_ip.cfg",
|
||||
" netcfg/get_ipaddress=0.0.0.0",
|
||||
" netcfg/get_gateway=0.0.0.0",
|
||||
"<enter>",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
step.Run(context.TODO(), state)
|
||||
}
|
|
@ -0,0 +1,186 @@
|
|||
package bootcommand
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/hashicorp/packer/builder/vsphere/driver"
|
||||
"github.com/hashicorp/packer/common"
|
||||
"golang.org/x/mobile/event/key"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// SendUsbScanCodes will be called to send codes to the VM
|
||||
type SendUsbScanCodes func([]key.Code, []bool) error
|
||||
|
||||
type usbDriver struct {
|
||||
vm *driver.VirtualMachine
|
||||
|
||||
sendImpl SendUsbScanCodes
|
||||
interval time.Duration
|
||||
specialMap map[string]key.Code
|
||||
scancodeMap map[rune]key.Code
|
||||
|
||||
codeBuffer []key.Code
|
||||
downBuffer []bool
|
||||
|
||||
// keyEvent can set this error which will prevent it from continuing
|
||||
err error
|
||||
}
|
||||
|
||||
func NewUSBDriver(send SendUsbScanCodes, interval time.Duration) *usbDriver {
|
||||
// We delay (default 100ms) between each key 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
|
||||
}
|
||||
|
||||
special := map[string]key.Code{
|
||||
"enter": key.CodeReturnEnter,
|
||||
"esc": key.CodeEscape,
|
||||
"bs": key.CodeDeleteBackspace,
|
||||
"del": key.CodeDeleteForward,
|
||||
"tab": key.CodeTab,
|
||||
"f1": key.CodeF1,
|
||||
"f2": key.CodeF2,
|
||||
"f3": key.CodeF3,
|
||||
"f4": key.CodeF4,
|
||||
"f5": key.CodeF5,
|
||||
"f6": key.CodeF6,
|
||||
"f7": key.CodeF7,
|
||||
"f8": key.CodeF8,
|
||||
"f9": key.CodeF9,
|
||||
"f10": key.CodeF10,
|
||||
"f11": key.CodeF11,
|
||||
"f12": key.CodeF12,
|
||||
"insert": key.CodeInsert,
|
||||
"home": key.CodeHome,
|
||||
"end": key.CodeEnd,
|
||||
"pageUp": key.CodePageUp,
|
||||
"pageDown": key.CodePageDown,
|
||||
"left": key.CodeLeftArrow,
|
||||
"right": key.CodeRightArrow,
|
||||
"up": key.CodeUpArrow,
|
||||
"down": key.CodeDownArrow,
|
||||
"leftalt": key.CodeLeftAlt,
|
||||
"leftctrl": key.CodeLeftControl,
|
||||
"leftshift": key.CodeLeftShift,
|
||||
"rightalt": key.CodeRightAlt,
|
||||
"rightctrl": key.CodeRightControl,
|
||||
"rightshift": key.CodeRightShift,
|
||||
}
|
||||
|
||||
scancodeIndex := make(map[string]key.Code)
|
||||
scancodeIndex["abcdefghijklmnopqrstuvwxyz"] = key.CodeA
|
||||
scancodeIndex["ABCDEFGHIJKLMNOPQRSTUVWXYZ"] = key.CodeA
|
||||
scancodeIndex["1234567890"] = key.Code1
|
||||
scancodeIndex["!@#$%^&*()"] = key.Code1
|
||||
scancodeIndex[" "] = key.CodeSpacebar
|
||||
scancodeIndex["-=[]\\"] = key.CodeHyphenMinus
|
||||
scancodeIndex["_+{}|"] = key.CodeHyphenMinus
|
||||
scancodeIndex[";'`,./"] = key.CodeSemicolon
|
||||
scancodeIndex[":\"~<>?"] = key.CodeSemicolon
|
||||
|
||||
var scancodeMap = make(map[rune]key.Code)
|
||||
for chars, start := range scancodeIndex {
|
||||
for i, r := range chars {
|
||||
scancodeMap[r] = start + key.Code(i)
|
||||
}
|
||||
}
|
||||
|
||||
return &usbDriver{
|
||||
sendImpl: send,
|
||||
specialMap: special,
|
||||
interval: keyInterval,
|
||||
scancodeMap: scancodeMap,
|
||||
}
|
||||
}
|
||||
|
||||
//func (d *usbDriver) keyEvent(k key.Code, down bool) error {
|
||||
// if d.err != nil {
|
||||
// return nil
|
||||
// }
|
||||
// if err := d.sendImpl(k, down); err != nil {
|
||||
// d.err = err
|
||||
// return err
|
||||
// }
|
||||
// //time.Sleep(d.interval)
|
||||
// return nil
|
||||
//}
|
||||
|
||||
// Flush does nothing here
|
||||
func (d *usbDriver) Flush() error {
|
||||
defer func() {
|
||||
d.codeBuffer = nil
|
||||
}()
|
||||
|
||||
if err := d.sendImpl(d.codeBuffer, d.downBuffer); err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(d.interval)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *usbDriver) SendKey(k rune, action KeyAction) error {
|
||||
keyShift := unicode.IsUpper(k) || strings.ContainsRune(shiftedChars, k)
|
||||
keyCode := d.scancodeMap[k]
|
||||
log.Printf("Sending char '%c', code %s, shift %v", k, keyCode, keyShift)
|
||||
|
||||
switch action {
|
||||
case KeyOn:
|
||||
if keyShift {
|
||||
d.send(key.CodeLeftShift, true)
|
||||
}
|
||||
d.send(keyCode, true)
|
||||
case KeyOff:
|
||||
if keyShift {
|
||||
d.send(key.CodeLeftShift, false)
|
||||
}
|
||||
d.send(keyCode, false)
|
||||
case KeyPress:
|
||||
if keyShift {
|
||||
d.send(key.CodeLeftShift, true)
|
||||
}
|
||||
d.send(keyCode, true)
|
||||
d.send(keyCode, false)
|
||||
if keyShift {
|
||||
d.send(key.CodeLeftShift, false)
|
||||
}
|
||||
}
|
||||
return d.err
|
||||
}
|
||||
|
||||
func (d *usbDriver) 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>' found, replacing with: %s", special, keyCode)
|
||||
|
||||
switch action {
|
||||
case KeyOn:
|
||||
d.send(keyCode, true)
|
||||
case KeyOff:
|
||||
d.send(keyCode, false)
|
||||
case KeyPress:
|
||||
d.send(keyCode, true)
|
||||
d.send(keyCode, false)
|
||||
}
|
||||
|
||||
return d.err
|
||||
}
|
||||
|
||||
// send stores the codes in an internal buffer. Use Flush to send them.
|
||||
func (d *usbDriver) send(code key.Code, down bool) {
|
||||
// slices to keep the input order
|
||||
d.codeBuffer = append(d.codeBuffer, code)
|
||||
d.downBuffer = append(d.downBuffer, down)
|
||||
|
||||
}
|
Loading…
Reference in New Issue