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