238 lines
5.9 KiB
Go
238 lines
5.9 KiB
Go
package vdagent
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"github.com/cirruslabs/tart-guest-agent/internal/spice/vd"
|
|
"github.com/cirruslabs/tart-guest-agent/internal/spice/vdi"
|
|
"go.uber.org/zap"
|
|
"golang.design/x/clipboard"
|
|
"os"
|
|
"time"
|
|
)
|
|
|
|
const serialPortPath = "/dev/tty.com.redhat.spice.0"
|
|
|
|
type VDAgent struct {
|
|
serialPort *os.File
|
|
vdi *vdi.VDI
|
|
lastClipboardState []byte
|
|
}
|
|
|
|
func New() (*VDAgent, error) {
|
|
sp, err := os.OpenFile(serialPortPath, os.O_RDWR, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := clipboard.Init(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &VDAgent{
|
|
serialPort: sp,
|
|
vdi: vdi.New(sp),
|
|
}, nil
|
|
}
|
|
|
|
func (agent *VDAgent) Run(ctx context.Context) error {
|
|
clipboardCh := clipboard.Watch(ctx, clipboard.FmtText)
|
|
|
|
for {
|
|
// Check for cancellation and clipboard changes
|
|
select {
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
case newClipboardState := <-clipboardCh:
|
|
if err := agent.processClipboardState(newClipboardState); err != nil {
|
|
return err
|
|
}
|
|
agent.lastClipboardState = newClipboardState
|
|
default:
|
|
// Nothing, proceed
|
|
}
|
|
|
|
if err := agent.serialPort.SetReadDeadline(time.Now().Add(time.Second)); err != nil {
|
|
return err
|
|
}
|
|
|
|
vdiAgentMessage, err := vd.ReadVDAgentMessage(agent.vdi)
|
|
if err != nil {
|
|
if errors.Is(err, os.ErrDeadlineExceeded) {
|
|
continue
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
switch vdiAgentMessage.Type {
|
|
case vd.VD_AGENT_ANNOUNCE_CAPABILITIES:
|
|
vdAgentAnnounceCapabilities, err := vd.ReadVDAgentAnnounceCapabilities(vdiAgentMessage.Data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
zap.S().Debugf("I: VD_AGENT_ANNOUNCE_CAPABILITIES: %s", vdAgentAnnounceCapabilities)
|
|
|
|
if vdAgentAnnounceCapabilities.Request == 0 {
|
|
// No need to send our capabilities
|
|
break
|
|
}
|
|
|
|
// Send our capabilities
|
|
ourCapabilities := vd.VDAgentAnnounceCapabilities{
|
|
Request: 0,
|
|
Caps: vd.VD_AGENT_CAP_CLIPBOARD_BY_DEMAND | vd.VD_AGENT_CAP_CLIPBOARD_SELECTION,
|
|
}
|
|
ourCapabilitiesBytes, err := ourCapabilities.Encode()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ourAgentMessage := vd.VDAgentMessage{
|
|
VDAgentMessageInner: vd.VDAgentMessageInner{
|
|
Protocol: vd.VD_AGENT_PROTOCOL,
|
|
Type: vd.VD_AGENT_ANNOUNCE_CAPABILITIES,
|
|
Size: uint32(len(ourCapabilitiesBytes)),
|
|
},
|
|
Data: ourCapabilitiesBytes,
|
|
}
|
|
ourAgentMessageBytes, err := ourAgentMessage.Encode()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := agent.vdi.Write(ourAgentMessageBytes); err != nil {
|
|
return err
|
|
}
|
|
|
|
zap.S().Debugf("O: VD_AGENT_ANNOUNCE_CAPABILITIES")
|
|
case vd.VD_AGENT_CLIPBOARD_GRAB:
|
|
vdAgentClipboardGrab, err := vd.DecodeVDAgentClipboardGrab(bytes.NewReader(vdiAgentMessage.Data))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
zap.S().Debugf("I: VD_AGENT_CLIPBOARD_GRAB (%d bytes): %s",
|
|
len(vdiAgentMessage.Data), vdAgentClipboardGrab)
|
|
|
|
ourClipboardRequest := vd.VDAgentClipboardRequest{
|
|
Type: 1,
|
|
}
|
|
ourClipboardRequestBytes, err := ourClipboardRequest.Encode()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ourAgentMessage := vd.VDAgentMessage{
|
|
VDAgentMessageInner: vd.VDAgentMessageInner{
|
|
Protocol: vd.VD_AGENT_PROTOCOL,
|
|
Type: vd.VD_AGENT_CLIPBOARD_REQUEST,
|
|
Size: uint32(len(ourClipboardRequestBytes)),
|
|
},
|
|
Data: ourClipboardRequestBytes,
|
|
}
|
|
ourAgentMessageBytes, err := ourAgentMessage.Encode()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := agent.vdi.Write(ourAgentMessageBytes); err != nil {
|
|
return err
|
|
}
|
|
|
|
zap.S().Debugf("O: VD_AGENT_CLIPBOARD_REQUEST")
|
|
case vd.VD_AGENT_CLIPBOARD:
|
|
// Receive clipboard
|
|
vdAgentClipboard, err := vd.DecodeVDAgentClipboard(vdiAgentMessage.Data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
zap.S().Debugf("I: VD_AGENT_CLIPBOARD: %s", vdAgentClipboard)
|
|
|
|
clipboard.Write(clipboard.FmtText, vdAgentClipboard.Data)
|
|
case vd.VD_AGENT_CLIPBOARD_REQUEST:
|
|
vdAgentClipboardRequest, err := vd.DecodeVDAgentClipboardRequest(bytes.NewReader(vdiAgentMessage.Data))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
zap.S().Debugf("I: VD_AGENT_CLIPBOARD_REQUEST: %s", vdAgentClipboardRequest)
|
|
|
|
// Send clipboard
|
|
ourAgentClipboard := vd.VDAgentClipboard{
|
|
VDAgentClipboardInner: vd.VDAgentClipboardInner{
|
|
Selection: vd.VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD,
|
|
Type: vd.VD_AGENT_CLIPBOARD_UTF8_TEXT,
|
|
},
|
|
Data: clipboard.Read(clipboard.FmtText),
|
|
}
|
|
ourAgentClipboardBytes, err := ourAgentClipboard.Encode()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ourAgentMessage := vd.VDAgentMessage{
|
|
VDAgentMessageInner: vd.VDAgentMessageInner{
|
|
Protocol: vd.VD_AGENT_PROTOCOL,
|
|
Type: vd.VD_AGENT_CLIPBOARD,
|
|
Size: uint32(len(ourAgentClipboardBytes)),
|
|
},
|
|
Data: ourAgentClipboardBytes,
|
|
}
|
|
ourAgentMessageBytes, err := ourAgentMessage.Encode()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := agent.vdi.Write(ourAgentMessageBytes); err != nil {
|
|
return err
|
|
}
|
|
|
|
zap.S().Debugf("O: VD_AGENT_CLIPBOARD")
|
|
default:
|
|
zap.S().Debugf("I: unhandled message type: %d", vdiAgentMessage.Type)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (agent *VDAgent) processClipboardState(newClipboardState []byte) error {
|
|
if bytes.Equal(agent.lastClipboardState, newClipboardState) {
|
|
// Nothing changed since the last VD_AGENT_CLIPBOARD_GRAB from us
|
|
return nil
|
|
}
|
|
|
|
ourGrab := vd.VDAgentClipboardGrab{
|
|
Selection: vd.VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD,
|
|
Type: vd.VD_AGENT_CLIPBOARD_UTF8_TEXT,
|
|
}
|
|
ourGrabBytes, err := ourGrab.Encode()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ourAgentMessage := vd.VDAgentMessage{
|
|
VDAgentMessageInner: vd.VDAgentMessageInner{
|
|
Protocol: vd.VD_AGENT_PROTOCOL,
|
|
Type: vd.VD_AGENT_CLIPBOARD_GRAB,
|
|
Size: uint32(len(ourGrabBytes)),
|
|
},
|
|
Data: ourGrabBytes,
|
|
}
|
|
ourAgentMessageBytes, err := ourAgentMessage.Encode()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := agent.vdi.Write(ourAgentMessageBytes); err != nil {
|
|
return err
|
|
}
|
|
|
|
zap.S().Debugf("O: VD_AGENT_CLIPBOARD_GRAB")
|
|
|
|
return nil
|
|
|
|
}
|