File: pc_xt_driver.go

package info (click to toggle)
packer 1.3.4%2Bdfsg-4
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 8,324 kB
  • sloc: python: 619; sh: 557; makefile: 111
file content (215 lines) | stat: -rw-r--r-- 6,804 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
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
}