qemu_command_builder/
spice.rs

1use crate::common::{AutoNeverAlways, OnOff, OnOffDefaultOff, OnOffDefaultOn};
2use crate::to_command::ToArg;
3use crate::to_command::ToCommand;
4use bon::Builder;
5use std::path::PathBuf;
6
7pub enum Channel {
8    Main,
9    Display,
10    Cursor,
11    Inputs,
12    Record,
13    Playback,
14}
15
16impl ToArg for Channel {
17    fn to_arg(&self) -> &str {
18        match self {
19            Channel::Main => "main",
20            Channel::Display => "display",
21            Channel::Cursor => "cursor",
22            Channel::Inputs => "inputs",
23            Channel::Record => "record",
24            Channel::Playback => "playback",
25        }
26    }
27}
28#[derive(Default)]
29pub enum ImageCompression {
30    AutoGlz,
31    #[default]
32    AutoLz,
33    Quic,
34    Glz,
35    Lz,
36    Off,
37}
38
39impl ToArg for ImageCompression {
40    fn to_arg(&self) -> &str {
41        match self {
42            ImageCompression::AutoGlz => "auto_glz",
43            ImageCompression::AutoLz => "auto_lz",
44            ImageCompression::Quic => "quic",
45            ImageCompression::Glz => "glz",
46            ImageCompression::Lz => "lz",
47            ImageCompression::Off => "off",
48        }
49    }
50}
51#[derive(Default)]
52pub enum OffAllFilter {
53    #[default]
54    Off,
55    All,
56    Filter,
57}
58
59impl ToArg for OffAllFilter {
60    fn to_arg(&self) -> &str {
61        match self {
62            OffAllFilter::Off => "off",
63            OffAllFilter::All => "all",
64            OffAllFilter::Filter => "filter",
65        }
66    }
67}
68/// Enable the spice remote desktop protocol.
69#[derive(Default, Builder)]
70pub struct Spice {
71    /// Set the TCP port spice is listening on for plaintext channels.
72    port: Option<u16>,
73
74    /// Set the IP address spice is listening on. Default is any address.
75    addr: Option<String>,
76
77    /// Force using the specified IP version.
78    ipv4: Option<OnOff>,
79    ipv6: Option<OnOff>,
80    unix: Option<OnOff>,
81
82    /// Set the ID of the ``secret`` object containing the password
83    /// you need to authenticate.
84    password_secret: Option<String>,
85
86    /// Require that the client use SASL to authenticate with the spice.
87    /// The exact choice of authentication method used is controlled
88    /// from the system / user's SASL configuration file for the 'qemu'
89    /// service. This is typically found in /etc/sasl2/qemu.conf. If
90    /// running QEMU as an unprivileged user, an environment variable
91    /// SASL\_CONF\_PATH can be used to make it search alternate
92    /// locations for the service config. While some SASL auth methods
93    /// can also provide data encryption (eg GSSAPI), it is recommended
94    /// that SASL always be combined with the 'tls' and 'x509' settings
95    /// to enable use of SSL and server certificates. This ensures a
96    /// data encryption preventing compromise of authentication
97    /// credentials.
98    sasl: Option<OnOff>,
99
100    /// Allow client connects without authentication.
101    disable_ticketing: Option<OnOff>,
102
103    /// Disable copy paste between the client and the guest.
104    disable_copy_paste: Option<OnOff>,
105
106    /// Disable spice-vdagent based file-xfer between the client and the
107    /// guest.
108    disable_agent_file_xfer: Option<OnOff>,
109
110    /// Set the TCP port spice is listening on for encrypted channels.
111    tls_port: Option<u16>,
112
113    /// Set the x509 file directory. Expects same filenames as -vnc
114    /// $display,x509=$dir
115    x509_dir: Option<PathBuf>,
116
117    /// The x509 file names can also be configured individually.
118    x509_key_file: Option<PathBuf>,
119    x509_key_password: Option<PathBuf>,
120    x509_cert_file: Option<PathBuf>,
121    x509_cacert_file: Option<PathBuf>,
122    x509_dh_key_file: Option<PathBuf>,
123
124    /// Specify which ciphers to use.
125    tls_ciphers: Option<String>,
126
127    /// Force specific channel to be used with or without TLS
128    /// encryption. The options can be specified multiple times to
129    /// configure multiple channels. The special name "default" can be
130    /// used to set the default mode. For channels which are not
131    /// explicitly forced into one mode the spice client is allowed to
132    /// pick tls/plaintext as he pleases.
133    tls_channel: Option<Channel>,
134    plaintext_channel: Option<Channel>,
135
136    /// Configure image compression (lossless). Default is auto\_glz.
137    image_compression: Option<ImageCompression>,
138
139    /// Configure wan image compression (lossy for slow links). Default
140    /// is auto.
141    jpeg_wan_compression: Option<AutoNeverAlways>,
142    zlib_glz_wan_compression: Option<AutoNeverAlways>,
143
144    /// Configure video stream detection. Default is off.
145    streaming_video: Option<OffAllFilter>,
146
147    /// Enable/disable passing mouse events via vdagent. Default is on.
148    agent_mouse: Option<OnOffDefaultOn>,
149
150    /// Enable/disable audio stream compression (using celt 0.5.1).
151    /// Default is on.
152    playback_compression: Option<OnOffDefaultOn>,
153
154    /// Enable/disable spice seamless migration. Default is off.
155    seamless_migration: Option<OnOffDefaultOff>,
156
157    /// Enable/disable OpenGL context. Default is off.
158    gl: Option<OnOffDefaultOn>,
159
160    /// DRM render node for OpenGL rendering. If not specified, it will
161    /// pick the first available. (Since 2.9)
162    rendernode: Option<PathBuf>,
163}
164
165impl ToCommand for Spice {
166    fn to_command(&self) -> Vec<String> {
167        let mut cmd = vec![];
168
169        cmd.push("-spice".to_string());
170
171        let mut args = vec![];
172        if let Some(port) = &self.port {
173            args.push(format!("port={}", port));
174        }
175        if let Some(addr) = &self.addr {
176            args.push(format!("addr={}", addr));
177        }
178        if let Some(ipv4) = &self.ipv4 {
179            args.push(format!("ipv4={}", ipv4.to_arg()));
180        }
181        if let Some(ipv6) = &self.ipv6 {
182            args.push(format!("ipv6={}", ipv6.to_arg()));
183        }
184        if let Some(unix) = &self.unix {
185            args.push(format!("unix={}", unix.to_arg()));
186        }
187        if let Some(password_secret) = &self.password_secret {
188            args.push(format!("password-secret={}", password_secret));
189        }
190        if let Some(sasl) = &self.sasl {
191            args.push(format!("sasl={}", sasl.to_arg()));
192        }
193        if let Some(disable_ticketing) = &self.disable_ticketing {
194            args.push(format!("disable-ticketing={}", disable_ticketing.to_arg()));
195        }
196        if let Some(disable_copy_paste) = &self.disable_copy_paste {
197            args.push(format!(
198                "disable-copy-paste={}",
199                disable_copy_paste.to_arg()
200            ));
201        }
202        if let Some(disable_agent_file_xfer) = &self.disable_agent_file_xfer {
203            args.push(format!(
204                "disable-agent-file-xfer={}",
205                disable_agent_file_xfer.to_arg()
206            ));
207        }
208        if let Some(tls_port) = &self.tls_port {
209            args.push(format!("tls-port={}", tls_port));
210        }
211        if let Some(x509_dir) = &self.x509_dir {
212            args.push(format!("x509-dir={}", x509_dir.display()));
213        }
214        if let Some(x509_key_file) = &self.x509_key_file {
215            args.push(format!("x509-key-file={}", x509_key_file.display()));
216        }
217        if let Some(x509_key_password) = &self.x509_key_password {
218            args.push(format!("x509-key-password={}", x509_key_password.display()));
219        }
220        if let Some(x509_cert_file) = &self.x509_cert_file {
221            args.push(format!("x509-cert-file={}", x509_cert_file.display()));
222        }
223        if let Some(x509_cacert_file) = &self.x509_cacert_file {
224            args.push(format!("x509-caert-file={}", x509_cacert_file.display()));
225        }
226        if let Some(x509_dh_key_file) = &self.x509_dh_key_file {
227            args.push(format!("x509-dh-key-file={}", x509_dh_key_file.display()));
228        }
229        if let Some(tls_ciphers) = &self.tls_ciphers {
230            args.push(format!("tls-ciphers={}", tls_ciphers));
231        }
232        if let Some(tls_channel) = &self.tls_channel {
233            args.push(format!("tls-channel={}", tls_channel.to_arg()));
234        }
235        if let Some(plaintext_channel) = &self.plaintext_channel {
236            args.push(format!("plaintext-channel={}", plaintext_channel.to_arg()));
237        }
238        if let Some(image_compression) = &self.image_compression {
239            args.push(format!("image-compression={}", image_compression.to_arg()));
240        }
241        if let Some(jpeg_wan_compression) = &self.jpeg_wan_compression {
242            args.push(format!(
243                "jpeg-wan-compression={}",
244                jpeg_wan_compression.to_arg()
245            ));
246        }
247        if let Some(zlib_glz_wan_compression) = &self.zlib_glz_wan_compression {
248            args.push(format!(
249                "zlib-glz-wan-compression={}",
250                zlib_glz_wan_compression.to_arg()
251            ));
252        }
253        if let Some(streaming_video) = &self.streaming_video {
254            args.push(format!("streaming-video={}", streaming_video.to_arg()));
255        }
256        if let Some(agent_mouse) = &self.agent_mouse {
257            args.push(format!("agent-mouse={}", agent_mouse.to_arg()));
258        }
259        if let Some(playback_compression) = &self.playback_compression {
260            args.push(format!(
261                "playback-compression={}",
262                playback_compression.to_arg()
263            ));
264        }
265        if let Some(seamless_migration) = &self.seamless_migration {
266            args.push(format!(
267                "seamless-migration={}",
268                seamless_migration.to_arg()
269            ));
270        }
271        if let Some(gl) = &self.gl {
272            args.push(format!("gl={}", gl.to_arg()));
273        }
274        if let Some(rendernode) = &self.rendernode {
275            args.push(format!("rendernode={}", rendernode.display()));
276        }
277
278        cmd.push(args.join(","));
279
280        cmd
281    }
282}