Skip to content

Commit

Permalink
Merge pull request #87 from Parallels/send-keycode-api-integration
Browse files Browse the repository at this point in the history
Removing Parallels Virtualization SDK dependency from PDfM 19.0.0
  • Loading branch information
sai-kumar-peddireddy authored Jul 27, 2023
2 parents 4b4716c + 13e57f7 commit a5d3108
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 30 deletions.
51 changes: 43 additions & 8 deletions builder/parallels/common/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -78,23 +82,20 @@ 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")
if err != nil {
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")
Expand Down Expand Up @@ -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
}
129 changes: 107 additions & 22 deletions builder/parallels/common/driver_9.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package common

import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"log"
Expand All @@ -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.
Expand Down Expand Up @@ -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
}

Expand Down

0 comments on commit a5d3108

Please sign in to comment.