Implement SPICE vdagent with clipboard sharing functionality (#3)
* Implement SPICE vdagent clipboard copy-paste functionality * Install libx11-dev * Fix typo in "sucessfully" * Simply print the capabilities as an integer
This commit is contained in:
parent
b59f2c51e8
commit
dbffcb0b08
|
|
@ -1,4 +0,0 @@
|
||||||
load("github.com/cirrus-modules/golang@main", "lint_task")
|
|
||||||
|
|
||||||
def main(ctx):
|
|
||||||
return [lint_task()]
|
|
||||||
26
.cirrus.yml
26
.cirrus.yml
|
|
@ -1,9 +1,29 @@
|
||||||
|
task:
|
||||||
|
name: Lint
|
||||||
|
container:
|
||||||
|
image: golangci/golangci-lint:latest
|
||||||
|
cpu: 2
|
||||||
|
memory: 4096
|
||||||
|
prepare_script:
|
||||||
|
- apt-get update
|
||||||
|
- apt-get install -y libx11-dev
|
||||||
|
lint_script:
|
||||||
|
- golangci-lint run -v --output.json.path golangci.json
|
||||||
|
always:
|
||||||
|
report_artifacts:
|
||||||
|
path: golangci.json
|
||||||
|
type: text/json
|
||||||
|
format: golangci
|
||||||
|
|
||||||
task:
|
task:
|
||||||
name: Test (Linux)
|
name: Test (Linux)
|
||||||
alias: Tests
|
alias: Tests
|
||||||
container:
|
container:
|
||||||
image: golang:latest
|
image: golang:latest
|
||||||
|
|
||||||
|
prepare_script:
|
||||||
|
- apt-get update
|
||||||
|
- apt-get install -y libx11-dev
|
||||||
test_script: go test -v ./...
|
test_script: go test -v ./...
|
||||||
|
|
||||||
task:
|
task:
|
||||||
|
|
@ -24,6 +44,9 @@ task:
|
||||||
depends_on:
|
depends_on:
|
||||||
- Lint
|
- Lint
|
||||||
- Tests
|
- Tests
|
||||||
|
prepare_script:
|
||||||
|
- apt-get update
|
||||||
|
- apt-get install -y libx11-dev
|
||||||
install_script:
|
install_script:
|
||||||
- echo 'deb [trusted=yes] https://repo.goreleaser.com/apt/ /' | tee /etc/apt/sources.list.d/goreleaser.list
|
- echo 'deb [trusted=yes] https://repo.goreleaser.com/apt/ /' | tee /etc/apt/sources.list.d/goreleaser.list
|
||||||
- apt-get update
|
- apt-get update
|
||||||
|
|
@ -45,6 +68,9 @@ task:
|
||||||
GITHUB_TOKEN: ENCRYPTED[!98ace8259c6024da912c14d5a3c5c6aac186890a8d4819fad78f3e0c41a4e0cd3a2537dd6e91493952fb056fa434be7c!]
|
GITHUB_TOKEN: ENCRYPTED[!98ace8259c6024da912c14d5a3c5c6aac186890a8d4819fad78f3e0c41a4e0cd3a2537dd6e91493952fb056fa434be7c!]
|
||||||
FURY_TOKEN: ENCRYPTED[!97fe4497d9aca60a3d64904883b81e21f19706c6aedda625c97f62f67ec46b8efa74c55699956158bbf0a23726e7d9f6!]
|
FURY_TOKEN: ENCRYPTED[!97fe4497d9aca60a3d64904883b81e21f19706c6aedda625c97f62f67ec46b8efa74c55699956158bbf0a23726e7d9f6!]
|
||||||
GORELEASER_KEY: ENCRYPTED[!9b80b6ef684ceaf40edd4c7af93014ee156c8aba7e6e5795f41c482729887b5c31f36b651491d790f1f668670888d9fd!]
|
GORELEASER_KEY: ENCRYPTED[!9b80b6ef684ceaf40edd4c7af93014ee156c8aba7e6e5795f41c482729887b5c31f36b651491d790f1f668670888d9fd!]
|
||||||
|
prepare_script:
|
||||||
|
- apt-get update
|
||||||
|
- apt-get install -y libx11-dev
|
||||||
install_script:
|
install_script:
|
||||||
- echo 'deb [trusted=yes] https://repo.goreleaser.com/apt/ /' | tee /etc/apt/sources.list.d/goreleaser.list
|
- echo 'deb [trusted=yes] https://repo.goreleaser.com/apt/ /' | tee /etc/apt/sources.list.d/goreleaser.list
|
||||||
- apt-get update
|
- apt-get update
|
||||||
|
|
|
||||||
4
go.mod
4
go.mod
|
|
@ -8,6 +8,7 @@ require (
|
||||||
github.com/spf13/cobra v1.9.1
|
github.com/spf13/cobra v1.9.1
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.10.0
|
||||||
go.uber.org/zap v1.27.0
|
go.uber.org/zap v1.27.0
|
||||||
|
golang.design/x/clipboard v0.7.0
|
||||||
golang.org/x/sys v0.32.0
|
golang.org/x/sys v0.32.0
|
||||||
howett.net/plist v1.0.1
|
howett.net/plist v1.0.1
|
||||||
)
|
)
|
||||||
|
|
@ -18,6 +19,9 @@ require (
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.6 // indirect
|
github.com/spf13/pflag v1.0.6 // indirect
|
||||||
go.uber.org/multierr v1.10.0 // indirect
|
go.uber.org/multierr v1.10.0 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 // indirect
|
||||||
|
golang.org/x/image v0.6.0 // indirect
|
||||||
|
golang.org/x/mobile v0.0.0-20230301163155-e0f57694e12c // indirect
|
||||||
golang.org/x/text v0.21.0 // indirect
|
golang.org/x/text v0.21.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
|
||||||
48
go.sum
48
go.sum
|
|
@ -1,3 +1,4 @@
|
||||||
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
|
@ -21,16 +22,63 @@ github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
||||||
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||||
|
golang.design/x/clipboard v0.7.0 h1:4Je8M/ys9AJumVnl8m+rZnIvstSnYj1fvzqYrU3TXvo=
|
||||||
|
golang.design/x/clipboard v0.7.0/go.mod h1:PQIvqYO9GP29yINEfsEn5zSQKAz3UgXmZKzDA6dnq2E=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 h1:estk1glOnSVeJ9tdEZZc5mAMDZk5lNJNyJ6DvrBkTEU=
|
||||||
|
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
|
||||||
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
|
golang.org/x/image v0.6.0 h1:bR8b5okrPI3g/gyZakLZHeWxAR8Dn5CyxXv1hLH5g/4=
|
||||||
|
golang.org/x/image v0.6.0/go.mod h1:MXLdDR43H7cDJq5GEGXEVeeNhPgi+YYEQ2pC1byI1x0=
|
||||||
|
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||||
|
golang.org/x/mobile v0.0.0-20230301163155-e0f57694e12c h1:Gk61ECugwEHL6IiyyNLXNzmu8XslmRP2dS0xjIYhbb4=
|
||||||
|
golang.org/x/mobile v0.0.0-20230301163155-e0f57694e12c/go.mod h1:aAjjkJNdrh3PMckS4B10TGS2nag27cbKR1y2BpUxsiY=
|
||||||
|
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/cirruslabs/tart-guest-agent/internal/diskresizer"
|
"github.com/cirruslabs/tart-guest-agent/internal/diskresizer"
|
||||||
"github.com/cirruslabs/tart-guest-agent/internal/logginglevel"
|
"github.com/cirruslabs/tart-guest-agent/internal/logginglevel"
|
||||||
|
"github.com/cirruslabs/tart-guest-agent/internal/spice/vdagent"
|
||||||
"github.com/cirruslabs/tart-guest-agent/internal/tart"
|
"github.com/cirruslabs/tart-guest-agent/internal/tart"
|
||||||
"github.com/cirruslabs/tart-guest-agent/internal/version"
|
"github.com/cirruslabs/tart-guest-agent/internal/version"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
@ -14,6 +15,8 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var resizeDisk bool
|
||||||
|
var runVdagent bool
|
||||||
var debug bool
|
var debug bool
|
||||||
|
|
||||||
func NewRootCommand() *cobra.Command {
|
func NewRootCommand() *cobra.Command {
|
||||||
|
|
@ -33,6 +36,11 @@ func NewRootCommand() *cobra.Command {
|
||||||
RunE: run,
|
RunE: run,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cmd.Flags().BoolVar(&resizeDisk, "resize-disk", false, "resize disk")
|
||||||
|
cmd.Flags().BoolVar(&runVdagent, "run-vdagent", false, "run vdagent")
|
||||||
|
|
||||||
|
cmd.Flags().BoolVar(&debug, "debug", false, "enable debug logging")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -47,6 +55,7 @@ func run(cmd *cobra.Command, args []string) error {
|
||||||
communicationPoint)
|
communicationPoint)
|
||||||
|
|
||||||
// Perform disk resizing
|
// Perform disk resizing
|
||||||
|
if resizeDisk {
|
||||||
if err := diskresizer.Resize(); err != nil {
|
if err := diskresizer.Resize(); err != nil {
|
||||||
if errors.Is(err, diskresizer.ErrUnsupported) || errors.Is(err, diskresizer.ErrAlreadyResized) {
|
if errors.Is(err, diskresizer.ErrUnsupported) || errors.Is(err, diskresizer.ErrAlreadyResized) {
|
||||||
zap.S().Infof("skipping disk resizing: %v", err)
|
zap.S().Infof("skipping disk resizing: %v", err)
|
||||||
|
|
@ -54,7 +63,19 @@ func run(cmd *cobra.Command, args []string) error {
|
||||||
zap.S().Warnf("failed to resize disk: %v", err)
|
zap.S().Warnf("failed to resize disk: %v", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
zap.S().Infof("sucessfully resized the disk")
|
zap.S().Infof("successfully resized the disk")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if runVdagent {
|
||||||
|
vdAgent, err := vdagent.New()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := vdAgent.Run(cmd.Context()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait indefinitely
|
// Wait indefinitely
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
package vd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VDAgentMessage struct {
|
||||||
|
VDAgentMessageInner
|
||||||
|
Data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type VDAgentMessageInner struct {
|
||||||
|
Protocol uint32
|
||||||
|
Type uint32
|
||||||
|
Opaque uint64
|
||||||
|
Size uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadVDAgentMessage(r io.Reader) (*VDAgentMessage, error) {
|
||||||
|
var vdiAgentMessage VDAgentMessage
|
||||||
|
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, &vdiAgentMessage.VDAgentMessageInner); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
vdiAgentMessage.Data = make([]byte, vdiAgentMessage.Size)
|
||||||
|
|
||||||
|
if _, err := io.ReadFull(r, vdiAgentMessage.Data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &vdiAgentMessage, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vdiAgentMessage *VDAgentMessage) Encode() ([]byte, error) {
|
||||||
|
buffer := &bytes.Buffer{}
|
||||||
|
|
||||||
|
if err := binary.Write(buffer, binary.LittleEndian, vdiAgentMessage.VDAgentMessageInner); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := buffer.Write(vdiAgentMessage.Data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vdiAgentMessage VDAgentMessage) String() string {
|
||||||
|
return fmt.Sprintf("%#+v", vdiAgentMessage.VDAgentMessageInner)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
package vd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VDAgentAnnounceCapabilities struct {
|
||||||
|
Request uint32
|
||||||
|
Caps uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadVDAgentAnnounceCapabilities(buf []byte) (*VDAgentAnnounceCapabilities, error) {
|
||||||
|
var vdAgentAnnounceCapabilities VDAgentAnnounceCapabilities
|
||||||
|
|
||||||
|
if err := binary.Read(bytes.NewReader(buf), binary.LittleEndian, &vdAgentAnnounceCapabilities); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &vdAgentAnnounceCapabilities, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vdAgentAnnounceCapabilities VDAgentAnnounceCapabilities) Encode() ([]byte, error) {
|
||||||
|
buffer := &bytes.Buffer{}
|
||||||
|
|
||||||
|
if err := binary.Write(buffer, binary.LittleEndian, &vdAgentAnnounceCapabilities); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vdAgentAnnounceCapabilities VDAgentAnnounceCapabilities) String() string {
|
||||||
|
return fmt.Sprintf("VDAgentAnnounceCapabilities(request=%d, capabilities=%d)",
|
||||||
|
vdAgentAnnounceCapabilities.Request, vdAgentAnnounceCapabilities.Caps)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
package vd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VDAgentClipboard struct {
|
||||||
|
VDAgentClipboardInner
|
||||||
|
Data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type VDAgentClipboardInner struct {
|
||||||
|
Selection uint8
|
||||||
|
_ [3]uint8
|
||||||
|
Type uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func DecodeVDAgentClipboard(buf []byte) (*VDAgentClipboard, error) {
|
||||||
|
var vdAgentClipboard VDAgentClipboard
|
||||||
|
|
||||||
|
r := bufio.NewReader(bytes.NewReader(buf))
|
||||||
|
|
||||||
|
err := binary.Read(r, binary.LittleEndian, &vdAgentClipboard.VDAgentClipboardInner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
vdAgentClipboard.Data, err = io.ReadAll(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &vdAgentClipboard, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vdAgentClipboard VDAgentClipboard) Encode() ([]byte, error) {
|
||||||
|
buffer := &bytes.Buffer{}
|
||||||
|
|
||||||
|
if err := binary.Write(buffer, binary.LittleEndian, &vdAgentClipboard.VDAgentClipboardInner); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := buffer.Write(vdAgentClipboard.Data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vdAgentClipboard VDAgentClipboard) String() string {
|
||||||
|
return fmt.Sprintf("VDAgentClipboard(selection=%d, type=%d, data=%d bytes)",
|
||||||
|
vdAgentClipboard.Selection, vdAgentClipboard.Type, len(vdAgentClipboard.Data))
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
package vd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VDAgentClipboardGrab struct {
|
||||||
|
Selection uint8
|
||||||
|
_ [3]uint8
|
||||||
|
Type uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func DecodeVDAgentClipboardGrab(r io.Reader) (*VDAgentClipboardGrab, error) {
|
||||||
|
var vdAgentClipboardGrab VDAgentClipboardGrab
|
||||||
|
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, &vdAgentClipboardGrab); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &vdAgentClipboardGrab, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vdAgentClipboardGrab VDAgentClipboardGrab) Encode() ([]byte, error) {
|
||||||
|
buffer := &bytes.Buffer{}
|
||||||
|
|
||||||
|
if err := binary.Write(buffer, binary.LittleEndian, &vdAgentClipboardGrab); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vdAgentClipboardGrab VDAgentClipboardGrab) String() string {
|
||||||
|
return fmt.Sprintf("VDAgentClipboardGrab(selection=%d, type=%d)",
|
||||||
|
vdAgentClipboardGrab.Selection, vdAgentClipboardGrab.Type)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
package vd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VDAgentClipboardRequest struct {
|
||||||
|
Selection uint8
|
||||||
|
_ [3]uint8
|
||||||
|
Type uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func DecodeVDAgentClipboardRequest(r io.Reader) (*VDAgentClipboardRequest, error) {
|
||||||
|
var vdAgentClipboardRequest VDAgentClipboardRequest
|
||||||
|
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, &vdAgentClipboardRequest); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &vdAgentClipboardRequest, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vdAgentClipboardRequest VDAgentClipboardRequest) Encode() ([]byte, error) {
|
||||||
|
buffer := &bytes.Buffer{}
|
||||||
|
|
||||||
|
if err := binary.Write(buffer, binary.LittleEndian, &vdAgentClipboardRequest); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vdAgentClipboardRequest VDAgentClipboardRequest) String() string {
|
||||||
|
return fmt.Sprintf("VDAgentClipboardRequest(selection=%d, type=%d)",
|
||||||
|
vdAgentClipboardRequest.Selection, vdAgentClipboardRequest.Type)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
package vd
|
||||||
|
|
||||||
|
// Protocols
|
||||||
|
const VD_AGENT_PROTOCOL = 1
|
||||||
|
|
||||||
|
// Ports
|
||||||
|
const (
|
||||||
|
VDP_CLIENT_PORT = iota + 1
|
||||||
|
VDP_SERVER_PORT
|
||||||
|
VDP_END_PORT
|
||||||
|
)
|
||||||
|
|
||||||
|
// Message types
|
||||||
|
const (
|
||||||
|
VD_AGENT_MOUSE_STATE = iota + 1
|
||||||
|
VD_AGENT_MONITORS_CONFIG
|
||||||
|
VD_AGENT_REPLY
|
||||||
|
VD_AGENT_CLIPBOARD
|
||||||
|
VD_AGENT_DISPLAY_CONFIG
|
||||||
|
VD_AGENT_ANNOUNCE_CAPABILITIES
|
||||||
|
VD_AGENT_CLIPBOARD_GRAB
|
||||||
|
VD_AGENT_CLIPBOARD_REQUEST
|
||||||
|
VD_AGENT_CLIPBOARD_RELEASE
|
||||||
|
VD_AGENT_FILE_XFER_START
|
||||||
|
VD_AGENT_FILE_XFER_STATUS
|
||||||
|
VD_AGENT_FILE_XFER_DATA
|
||||||
|
VD_AGENT_CLIENT_DISCONNECTED
|
||||||
|
VD_AGENT_MAX_CLIPBOARD
|
||||||
|
VD_AGENT_AUDIO_VOLUME_SYNC
|
||||||
|
VD_AGENT_GRAPHICS_DEVICE_INFO
|
||||||
|
VD_AGENT_END_MESSAGE
|
||||||
|
)
|
||||||
|
|
||||||
|
// Capabilities
|
||||||
|
const (
|
||||||
|
VD_AGENT_CAP_MOUSE_STATE = 1 << iota
|
||||||
|
VD_AGENT_CAP_MONITORS_CONFIG
|
||||||
|
VD_AGENT_CAP_REPLY
|
||||||
|
VD_AGENT_CAP_CLIPBOARD
|
||||||
|
VD_AGENT_CAP_DISPLAY_CONFIG
|
||||||
|
VD_AGENT_CAP_CLIPBOARD_BY_DEMAND
|
||||||
|
VD_AGENT_CAP_CLIPBOARD_SELECTION
|
||||||
|
VD_AGENT_CAP_SPARSE_MONITORS_CONFIG
|
||||||
|
VD_AGENT_CAP_GUEST_LINEEND_LF
|
||||||
|
VD_AGENT_CAP_GUEST_LINEEND_CRLF
|
||||||
|
VD_AGENT_CAP_MAX_CLIPBOARD
|
||||||
|
VD_AGENT_CAP_AUDIO_VOLUME_SYNC
|
||||||
|
VD_AGENT_CAP_MONITORS_CONFIG_POSITION
|
||||||
|
VD_AGENT_CAP_FILE_XFER_DISABLED
|
||||||
|
VD_AGENT_CAP_FILE_XFER_DETAILED_ERRORS
|
||||||
|
VD_AGENT_CAP_GRAPHICS_DEVICE_INFO
|
||||||
|
VD_AGENT_CAP_CLIPBOARD_NO_RELEASE_ON_REGRAB
|
||||||
|
VD_AGENT_CAP_CLIPBOARD_GRAB_SERIAL
|
||||||
|
VD_AGENT_END_CAP
|
||||||
|
)
|
||||||
|
|
||||||
|
// Clipboard selections
|
||||||
|
const (
|
||||||
|
VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD = iota
|
||||||
|
VD_AGENT_CLIPBOARD_SELECTION_PRIMARY
|
||||||
|
VD_AGENT_CLIPBOARD_SELECTION_SECONDARY
|
||||||
|
)
|
||||||
|
|
||||||
|
// Clipboard content types
|
||||||
|
const (
|
||||||
|
VD_AGENT_CLIPBOARD_NONE = iota
|
||||||
|
VD_AGENT_CLIPBOARD_UTF8_TEXT
|
||||||
|
VD_AGENT_CLIPBOARD_IMAGE_PNG
|
||||||
|
VD_AGENT_CLIPBOARD_IMAGE_BMP
|
||||||
|
VD_AGENT_CLIPBOARD_IMAGE_TIFF
|
||||||
|
VD_AGENT_CLIPBOARD_IMAGE_JPG
|
||||||
|
VD_AGENT_CLIPBOARD_FILE_LIST
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,237 @@
|
||||||
|
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
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
package vdi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"github.com/cirruslabs/tart-guest-agent/internal/spice/vd"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VDI struct {
|
||||||
|
inner io.ReadWriter
|
||||||
|
remaining uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
type chunkHeader struct {
|
||||||
|
Port uint32
|
||||||
|
Size uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(inner io.ReadWriter) *VDI {
|
||||||
|
return &VDI{
|
||||||
|
inner: inner,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vdi *VDI) Read(buf []byte) (int, error) {
|
||||||
|
// Read payload
|
||||||
|
readPayload:
|
||||||
|
if vdi.remaining > 0 {
|
||||||
|
toRead := min(len(buf), int(vdi.remaining))
|
||||||
|
|
||||||
|
n, err := vdi.inner.Read(buf[:toRead])
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
vdi.remaining -= uint64(n)
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read header
|
||||||
|
var vdiChunkHeader chunkHeader
|
||||||
|
|
||||||
|
if err := binary.Read(vdi.inner, binary.LittleEndian, &vdiChunkHeader); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
vdi.remaining = uint64(vdiChunkHeader.Size)
|
||||||
|
|
||||||
|
goto readPayload
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vdi *VDI) Write(buf []byte) (int, error) {
|
||||||
|
// Write header
|
||||||
|
buffer := &bytes.Buffer{}
|
||||||
|
|
||||||
|
vdiChunkHeader := chunkHeader{
|
||||||
|
Port: vd.VDP_CLIENT_PORT,
|
||||||
|
Size: uint32(len(buf)),
|
||||||
|
}
|
||||||
|
if err := binary.Write(buffer, binary.LittleEndian, &vdiChunkHeader); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write payload
|
||||||
|
return vdi.inner.Write(append(buffer.Bytes(), buf...))
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue