diff --git a/builder/parallels/common/driver.go b/builder/parallels/common/driver.go index 5785250..e72e695 100644 --- a/builder/parallels/common/driver.go +++ b/builder/parallels/common/driver.go @@ -6,10 +6,14 @@ package common import ( "fmt" "log" + "os" "os/exec" + "regexp" "runtime" "strconv" "strings" + + "github.com/hashicorp/go-version" ) // Driver is the interface that talks to Parallels and performs certain @@ -78,13 +82,6 @@ func NewDriver() (Driver, error) { "Parallels builder works only on \"darwin\" platform!") } - cmd := exec.Command("/usr/bin/python3", "-c", `import prlsdkapi`) - err := cmd.Run() - if err != nil { - return nil, fmt.Errorf( - "Parallels Virtualization SDK is not installed") - } - if prlctlPath == "" { var err error prlctlPath, err = exec.LookPath("prlctl") @@ -92,9 +89,13 @@ func NewDriver() (Driver, error) { return nil, err } } - log.Printf("prlctl path: %s", prlctlPath) + err := checkforPythonSDK(prlctlPath) + if err != nil { + return nil, err + } + if prlsrvctlPath == "" { var err error prlsrvctlPath, err = exec.LookPath("prlsrvctl") @@ -151,3 +152,37 @@ func NewDriver() (Driver, error) { "Unable to initialize any driver. Supported Parallels Desktop versions: "+ "%s\n", strings.Join(supportedVersions, ", ")) } + +// checks for pyhton SDK if if version is less than 19.0.0 + +func checkforPythonSDK(prlctlPath string) error { + + out, err := exec.Command(prlctlPath, "--version").Output() + if err != nil { + return err + } + + versionRe := regexp.MustCompile(`prlctl version (\d+\.\d+.\d+)`) + matches := versionRe.FindStringSubmatch(string(out)) + if matches == nil { + return fmt.Errorf( + "Could not find Parallels Desktop version in output:\n%s", string(out)) + } + + prlctlCurrVersionStr := matches[1] + prlctlCurrVersion, _ := version.NewVersion(prlctlCurrVersionStr) + v2, verErr := version.NewVersion("19.0.0") + + if verErr != nil && prlctlCurrVersion.LessThan(v2) { + pyPath := os.Getenv("PYTHONPATH") + os.Setenv("PYTHONPATH", pyPath+":/Library/Frameworks/ParallelsVirtualizationSDK.framework/Versions/Current/Libraries/Python/3.7") + cmd := exec.Command("/usr/bin/python3", "-c", `import prlsdkapi`) + err = cmd.Run() + if err != nil { + return fmt.Errorf( + "Parallels Virtualization SDK is not installed") + } + } + + return nil +} diff --git a/builder/parallels/common/driver_9.go b/builder/parallels/common/driver_9.go index ca8b9c8..538578b 100644 --- a/builder/parallels/common/driver_9.go +++ b/builder/parallels/common/driver_9.go @@ -5,6 +5,7 @@ package common import ( "bytes" + "encoding/json" "fmt" "io/ioutil" "log" @@ -18,9 +19,79 @@ import ( "github.com/ChrisTrenkamp/goxpath" "github.com/ChrisTrenkamp/goxpath/tree/xmltree" + "github.com/hashicorp/go-version" "github.com/hashicorp/packer-plugin-sdk/tmp" ) +// Struct to format Scancodes in JSON +type ScanCodes struct { + Scancode int64 `json:"scancode"` + Event string `json:"event"` + Delay int `json:"delay"` +} + +// sending scancodes to VM via prlctl send-key-event CMD +func (d *Parallels9Driver) sendJsonScancodes(vmName string, inputScanCodes []string) error { + scancodeData := []ScanCodes{} + + log.Println("scancodes received for JSON encoding ", inputScanCodes) + delay := 100 + for i := 0; i < len(inputScanCodes); i++ { + key1, convErr_ := strconv.ParseInt(inputScanCodes[i], 16, 64) + if convErr_ != nil { + log.Println(convErr_, "conversion error for %s", inputScanCodes[i]) + return convErr_ + } + + if key1 == 224 { + key2, convErr_ := strconv.ParseInt(inputScanCodes[i+1], 16, 64) + i = i + 1 + if convErr_ != nil { + log.Println(convErr_, "conversion error for %s", inputScanCodes[i]) + return convErr_ + } + if key2 < 128 { + scancodeData = append(scancodeData, ScanCodes{Scancode: key1, Event: "press", Delay: delay}) + scancodeData = append(scancodeData, ScanCodes{Scancode: key2, Event: "press", Delay: delay}) + } else { + scancodeData = append(scancodeData, ScanCodes{Scancode: key1, Event: "release", Delay: delay}) + scancodeData = append(scancodeData, ScanCodes{Scancode: key2 - 128, Event: "release", Delay: delay}) + } + } else if key1 < 128 { + scancodeData = append(scancodeData, ScanCodes{Scancode: key1, Event: "press", Delay: delay}) + } else { + scancodeData = append(scancodeData, ScanCodes{Scancode: key1 - 128, Event: "release", Delay: delay}) + } + } + + jsonFormat, err := json.MarshalIndent(scancodeData, "", "\t") + if err != nil { + log.Println(err) + return err + } + log.Printf("complete scancode data in JSON format %s", jsonFormat) + + var stdout, stderr bytes.Buffer + cmd := exec.Command(d.PrlctlPath, "send-key-event", vmName, "-j") + cmd.Stdout = &stdout + cmd.Stderr = &stderr + cmd.Stdin = strings.NewReader(string(jsonFormat)) + err = cmd.Run() + stdoutString := strings.TrimSpace(stdout.String()) + stderrString := strings.TrimSpace(stderr.String()) + if _, ok := err.(*exec.ExitError); ok { + err = fmt.Errorf("prlctl error: %s", stderrString) + } + log.Printf("stdout: %s", stdoutString) + log.Printf("stderr: %s", stderrString) + + if err != nil { + log.Println(err) + return err + } + return nil +} + // Parallels9Driver is a base type for Parallels builders. type Parallels9Driver struct { // This is the path to the "prlctl" application. @@ -284,44 +355,58 @@ func (d *Parallels9Driver) Version() (string, error) { } // SendKeyScanCodes sends the specified scancodes as key events to the VM. -// It is performed using "Prltype" script (refer to "prltype.go"). +// It is performed using "Prltype" script (refer to "prltype.go") if version is < 19.0.0. +// scancodes are sent by using prlctl CMD if version is >= 19.0.0 func (d *Parallels9Driver) SendKeyScanCodes(vmName string, codes ...string) error { var stdout, stderr bytes.Buffer + var err error if len(codes) == 0 { log.Printf("No scan codes to send") return nil } - f, err := tmp.File("prltype") - if err != nil { + prlctlCurrVersionStr, verErr := d.Version() + if verErr != nil { return err } - defer os.Remove(f.Name()) + prlctlCurrVersion, _ := version.NewVersion(prlctlCurrVersionStr) + v2, _ := version.NewVersion("19.0.0") - script := []byte(Prltype) - _, err = f.Write(script) - if err != nil { - return err - } + if verErr != nil && prlctlCurrVersion.LessThan(v2) { - args := prepend(vmName, codes) - args = prepend(f.Name(), args) - cmd := exec.Command("/usr/bin/python3", args...) - cmd.Stdout = &stdout - cmd.Stderr = &stderr - err = cmd.Run() + f, err := tmp.File("prltype") + if err != nil { + return err + } + defer os.Remove(f.Name()) - stdoutString := strings.TrimSpace(stdout.String()) - stderrString := strings.TrimSpace(stderr.String()) + script := []byte(Prltype) + _, err = f.Write(script) + if err != nil { + return err + } - if _, ok := err.(*exec.ExitError); ok { - err = fmt.Errorf("prltype error: %s", stderrString) - } + args := prepend(vmName, codes) + args = prepend(f.Name(), args) + cmd := exec.Command("/usr/bin/python3", args...) + cmd.Stdout = &stdout + cmd.Stderr = &stderr + err = cmd.Run() - log.Printf("stdout: %s", stdoutString) - log.Printf("stderr: %s", stderrString) + stdoutString := strings.TrimSpace(stdout.String()) + stderrString := strings.TrimSpace(stderr.String()) + if _, ok := err.(*exec.ExitError); ok { + err = fmt.Errorf("prltype error: %s", stderrString) + return err + } + + log.Printf("stdout: %s", stdoutString) + log.Printf("stderr: %s", stderrString) + } else { + err = d.sendJsonScancodes(vmName, codes) + } return err }