Skip to main content

x11docker_rs/
lib.rs

1use core::fmt;
2use std::{
3    collections::HashSet,
4    io::{Error, ErrorKind},
5    process::{Child, Command},
6};
7
8use libc::kill;
9
10#[derive(Default)]
11pub struct X11docker {
12    arguments: Option<HashSet<X11dockerOption>>,
13    proc_child: Option<Child>,
14}
15
16#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
17pub enum X11dockerOption {
18    Internet,
19    ImageName(String),
20    ContainerName(String),
21    // Basic settings:
22    Desktop,
23    Interactive,
24    Backend(String),
25    Rootless(bool),
26    Xc(String),
27    Xonly,
28    // Host integration:
29    Alsa(String),
30    Clipboard(String),
31    Gpu(String),
32    Network(String),
33    Lang(String),
34    Printer(String),
35    Pulseaudio(String),
36    Webcam,
37    // Shared host folders or volumes:
38    Home(String),
39    Share(String),
40    // X Server options:
41    Auto,
42    Hostdisplay,
43    Xpra,
44    Xpra2,
45    XpraXwayland,
46    Xpra2Xwayland,
47    Nxagent,
48    Xephyr,
49    WestonXwayland,
50    Xorg,
51    // Special X server options:
52    Tty,
53    Xvfb,
54    Xwayland,
55    Xwin,
56    Runx,
57    // Wayland instead of X:
58    Wayland,
59    Weston,
60    Kwin,
61    Hostwayland,
62    // X and Wayland appearance options:
63    Border(String),
64    Dpi(String),
65    Fullscreen,
66    OutputCount(String),
67    Rotate(String),
68    Scale(String),
69    Size(String),
70    Wm(String),
71    Xfishtank,
72    // X and Wayland special configuration:
73    Checkwindow(String),
74    CleanXhost,
75    Composite(String),
76    Display(String),
77    Keymap(String),
78    Vt(String),
79    Westonini(String),
80    Xhost(String),
81    Xoverip(String),
82    Xauth(String),
83    Xtest(String),
84    // Container user settings:
85    GroupAdd(String),
86    Hostuser(String),
87    Password(String),
88    Sudouser(String),
89    User(String),
90    // Container capabilities:
91    CapDefault,
92    Ipc(String),
93    Limit(String),
94    Newprivileges(String),
95    // Container init system, elogind and DBus daemon:
96    Dbus(String),
97    Hostdbus,
98    Init(String),
99    Sharecgroup,
100    //Container special configuration:
101    Env(String),
102    Name(String),
103    NoEntrypoint,
104    NoSetup,
105    Runtime(String),
106    Shell(String),
107    Snap,
108    Stdin,
109    Workdir(String),
110    // Additional commands: (you might need to move them to background with 'CMD &'.)
111    Runasroot(String),
112    Runasuser(String),
113    Runfromhost(String),
114    // Miscellaneous:
115    Build(String),
116    Cachebasedir(String),
117    Homebasedir(String),
118    Fallback(String),
119    Launcher,
120    Mobyvm,
121    Preset(String),
122    // Output of parseable information on stdout:
123    Printenv(String),
124    Printid(String),
125    Printinfofile(String),
126    Printpid1(String),
127    // Verbosity options:
128    Debug,
129    Printcheck,
130    Quiet,
131    Verbose,
132    // Cleanup options (might need root permissions):
133    Cleanup,
134    // Installation options (need root permissions):
135    Install,
136    Update(String),
137    UpdateMaster(String),
138    Remove,
139    RemoveOldprefix,
140}
141
142impl X11docker {
143    /// Sets the complete set of arguments, replacing any existing arguments
144    ///
145    /// # Arguments
146    /// * `args` - HashSet of X11dockerOption values to use as arguments
147    pub fn arguments(mut self, args: HashSet<X11dockerOption>) -> Self {
148        self.arguments = Some(args);
149        self
150    }
151
152    /// Adds additional arguments to the existing set
153    /// If no arguments exist yet, initializes a new set
154    ///
155    /// # Arguments
156    /// * `args` - HashSet of X11dockerOption values to append
157    pub fn append_arguments(mut self, args: HashSet<X11dockerOption>) -> Self {
158        let mut arguments = self.arguments.unwrap_or_default();
159        arguments.extend(args);
160        self.arguments = Some(arguments);
161        self
162    }
163
164    /// Enables internet access for the container
165    pub fn internet(mut self) -> Self {
166        let mut args = self.arguments.unwrap_or_default();
167        args.insert(X11dockerOption::Internet);
168        self.arguments = Some(args);
169        self
170    }
171
172    /// Sets the name of the Docker image to use
173    ///
174    /// # Arguments
175    /// * `name` - Name of the Docker image
176    pub fn image_name(mut self, name: &str) -> Self {
177        let mut args = self.arguments.unwrap_or_default();
178        args.insert(X11dockerOption::ImageName(name.to_string()));
179        self.arguments = Some(args);
180        self
181    }
182
183    /// Sets a custom name for the container
184    ///
185    /// # Arguments
186    /// * `name` - Name to assign to the container
187    pub fn container_name(mut self, name: &str) -> Self {
188        let mut args = self.arguments.unwrap_or_default();
189        args.insert(X11dockerOption::ContainerName(name.to_string()));
190        self.arguments = Some(args);
191        self
192    }
193
194    /// Enables Xpra display server
195    pub fn xpra(mut self) -> Self {
196        let mut args = self.arguments.unwrap_or_default();
197        args.insert(X11dockerOption::Xpra);
198        self.arguments = Some(args);
199        self
200    }
201
202    /// Enables desktop mode
203    pub fn desktop(mut self) -> Self {
204        let mut args = self.arguments.unwrap_or_default();
205        args.insert(X11dockerOption::Desktop);
206        self.arguments = Some(args);
207        self
208    }
209
210    /// Enables interactive mode
211    pub fn interactive(mut self) -> Self {
212        let mut args = self.arguments.unwrap_or_default();
213        args.insert(X11dockerOption::Interactive);
214        self.arguments = Some(args);
215        self
216    }
217
218    /// Sets the X server backend to use
219    ///
220    /// # Arguments
221    /// * `backend` - Name of the X server backend
222    pub fn backend(mut self, backend: &str) -> Self {
223        let mut args = self.arguments.unwrap_or_default();
224        args.insert(X11dockerOption::Backend(backend.to_owned()));
225        self.arguments = Some(args);
226        self
227    }
228
229    /// Configures rootless mode
230    ///
231    /// # Arguments
232    /// * `value` - Enable (true) or disable (false) rootless mode
233    pub fn rootless(mut self, value: bool) -> Self {
234        let mut args = self.arguments.unwrap_or_default();
235        args.insert(X11dockerOption::Rootless(value));
236        self.arguments = Some(args);
237        self
238    }
239
240    /// Sets the X client command
241    ///
242    /// # Arguments
243    /// * `value` - Command to run as X client
244    pub fn xc(mut self, value: &str) -> Self {
245        let mut args = self.arguments.unwrap_or_default();
246        args.insert(X11dockerOption::Xc(value.to_owned()));
247        self.arguments = Some(args);
248        self
249    }
250
251    /// Enables X server only mode without container
252    pub fn xonly(mut self) -> Self {
253        let mut args = self.arguments.unwrap_or_default();
254        args.insert(X11dockerOption::Xonly);
255        self.arguments = Some(args);
256        self
257    }
258
259    /// Executes x11docker with the configured arguments
260    ///
261    /// # Errors
262    /// * Returns `std::io::Error` if no arguments are provided
263    /// * Returns `std::io::Error` if the x11docker process fails to spawn
264    pub fn exec(&mut self) -> std::io::Result<()> {
265        if self.arguments.is_none() {
266            return Err(Error::new(ErrorKind::NotFound, "No arguments provided."));
267        }
268        let args = self.arguments.clone().unwrap_or_default();
269        let argsvec = args.iter().map(|x| x.to_string()).collect::<Vec<String>>();
270
271        // Spawn x11docker script with arguments
272        self.proc_child = Some(Command::new("x11docker").args(argsvec).spawn()?);
273        Ok(())
274    }
275
276    /// Terminates the running x11docker process
277    ///
278    /// # Errors
279    /// * Returns `std::io::Error` if no child process exists
280    /// * Returns `std::io::Error` if waiting for the process to exit fails
281    pub fn terminate(self) -> std::io::Result<()> {
282        match self.proc_child {
283            Some(mut child) => {
284                let id = child.id() as i32;
285                unsafe {
286                    kill(id, libc::SIGTERM);
287                }
288                child.wait()?;
289                Ok(())
290            }
291            None => Err(std::io::Error::new(
292                std::io::ErrorKind::NotFound,
293                "No child found to terminate.",
294            )),
295        }
296    }
297}
298
299impl fmt::Display for X11dockerOption {
300    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
301        match self {
302            X11dockerOption::Internet => write!(f, "-I"),
303            X11dockerOption::ImageName(i) => write!(f, "{i}"),
304            X11dockerOption::ContainerName(c) => write!(f, "--name={c}"),
305            X11dockerOption::Xpra => write!(f, "--xpra"),
306            X11dockerOption::Desktop => write!(f, "--desktop"),
307            X11dockerOption::Interactive => write!(f, "--interactive"),
308            X11dockerOption::Backend(b) => write!(f, "--backend={b}"),
309            X11dockerOption::Rootless(val) => {
310                write!(f, "--rootless={}", if *val { "yes" } else { "no" })
311            }
312            X11dockerOption::Xc(b) => write!(f, "--xc={b}"),
313            X11dockerOption::Xonly => write!(f, "--xonly"),
314            X11dockerOption::Alsa(a) => write!(f, "--alsa={a}"),
315            X11dockerOption::Clipboard(c) => write!(f, "--clipboard={c}"),
316            X11dockerOption::Gpu(g) => write!(f, "--gpu={g}"),
317            X11dockerOption::Network(n) => write!(f, "--network={n}"),
318            X11dockerOption::Lang(l) => write!(f, "--lang={l}"),
319            X11dockerOption::Printer(p) => write!(f, "--printer={p}"),
320            X11dockerOption::Pulseaudio(p) => write!(f, "--pulseaudio={p}"),
321            X11dockerOption::Webcam => write!(f, "--webcam"),
322            X11dockerOption::Home(h) => write!(f, "--home={h}"),
323            X11dockerOption::Share(s) => write!(f, "--share={s}"),
324            X11dockerOption::Auto => write!(f, "--auto"),
325            X11dockerOption::Hostdisplay => write!(f, "--hostdisplay"),
326            X11dockerOption::Xpra2 => write!(f, "--xpra2"),
327            X11dockerOption::XpraXwayland => write!(f, "--xpra-xwayland"),
328            X11dockerOption::Xpra2Xwayland => write!(f, "--xpra2-xwayland"),
329            X11dockerOption::Nxagent => write!(f, "--nxagent"),
330            X11dockerOption::Xephyr => write!(f, "--xephyr"),
331            X11dockerOption::WestonXwayland => write!(f, "--weston-xwayland"),
332            X11dockerOption::Xorg => write!(f, "--xorg"),
333            X11dockerOption::Tty => write!(f, "--tty"),
334            X11dockerOption::Xvfb => write!(f, "--xvfb"),
335            X11dockerOption::Xwayland => write!(f, "--xwayland"),
336            X11dockerOption::Xwin => write!(f, "--xwin"),
337            X11dockerOption::Runx => write!(f, "--runx"),
338            X11dockerOption::Wayland => write!(f, "--wayland"),
339            X11dockerOption::Weston => write!(f, "--weston"),
340            X11dockerOption::Kwin => write!(f, "--kwin"),
341            X11dockerOption::Hostwayland => write!(f, "--hostwayland"),
342            X11dockerOption::Border(b) => write!(f, "--border={b}"),
343            X11dockerOption::Dpi(d) => write!(f, "--dpi={d}"),
344            X11dockerOption::Fullscreen => write!(f, "--fullscreen"),
345            X11dockerOption::OutputCount(o) => write!(f, "--output-count={o}"),
346            X11dockerOption::Rotate(r) => write!(f, "--rotate={r}"),
347            X11dockerOption::Scale(s) => write!(f, "--scale={s}"),
348            X11dockerOption::Size(s) => write!(f, "--size={s}"),
349            X11dockerOption::Wm(w) => write!(f, "--wm={w}"),
350            X11dockerOption::Xfishtank => write!(f, "--xfishtank"),
351            X11dockerOption::Checkwindow(c) => write!(f, "--checkwindow={c}"),
352            X11dockerOption::CleanXhost => write!(f, "--clean-xhost"),
353            X11dockerOption::Composite(c) => write!(f, "--composite={c}"),
354            X11dockerOption::Display(d) => write!(f, "--display={d}"),
355            X11dockerOption::Keymap(k) => write!(f, "--keymap={k}"),
356            X11dockerOption::Vt(v) => write!(f, "--vt={v}"),
357            X11dockerOption::Westonini(w) => write!(f, "--westonini={w}"),
358            X11dockerOption::Xhost(x) => write!(f, "--xhost={x}"),
359            X11dockerOption::Xoverip(x) => write!(f, "--xoverip={x}"),
360            X11dockerOption::Xauth(x) => write!(f, "--xauth={x}"),
361            X11dockerOption::Xtest(x) => write!(f, "--xtest={x}"),
362            X11dockerOption::GroupAdd(g) => write!(f, "--group-add={g}"),
363            X11dockerOption::Hostuser(h) => write!(f, "--hostuser={h}"),
364            X11dockerOption::Password(p) => write!(f, "--password={p}"),
365            X11dockerOption::Sudouser(s) => write!(f, "--sudouser={s}"),
366            X11dockerOption::User(u) => write!(f, "--user={u}"),
367            X11dockerOption::CapDefault => write!(f, "--cap-default"),
368            X11dockerOption::Ipc(i) => write!(f, "--ipc={i}"),
369            X11dockerOption::Limit(l) => write!(f, "--limit={l}"),
370            X11dockerOption::Newprivileges(n) => write!(f, "--newprivileges={n}"),
371            X11dockerOption::Dbus(d) => write!(f, "--dbus={d}"),
372            X11dockerOption::Hostdbus => write!(f, "--hostdbus"),
373            X11dockerOption::Init(i) => write!(f, "--init={i}"),
374            X11dockerOption::Sharecgroup => write!(f, "--sharecgroup"),
375            X11dockerOption::Env(e) => write!(f, "--env={e}"),
376            X11dockerOption::Name(n) => write!(f, "--name={n}"),
377            X11dockerOption::NoEntrypoint => write!(f, "--no-entrypoint"),
378            X11dockerOption::NoSetup => write!(f, "--no-setup"),
379            X11dockerOption::Runtime(r) => write!(f, "--runtime={r}"),
380            X11dockerOption::Shell(s) => write!(f, "--shell={s}"),
381            X11dockerOption::Snap => write!(f, "--snap"),
382            X11dockerOption::Stdin => write!(f, "--stdin"),
383            X11dockerOption::Workdir(w) => write!(f, "--workdir={w}"),
384            X11dockerOption::Runasroot(r) => write!(f, "--runasroot={r}"),
385            X11dockerOption::Runasuser(r) => write!(f, "--runasuser={r}"),
386            X11dockerOption::Runfromhost(r) => write!(f, "--runfromhost={r}"),
387            X11dockerOption::Build(b) => write!(f, "--build={b}"),
388            X11dockerOption::Cachebasedir(c) => write!(f, "--cachebasedir={c}"),
389            X11dockerOption::Homebasedir(h) => write!(f, "--homebasedir={h}"),
390            X11dockerOption::Fallback(b) => write!(f, "--fallback={b}"),
391            X11dockerOption::Launcher => write!(f, "--launcher"),
392            X11dockerOption::Mobyvm => write!(f, "--mobyvm"),
393            X11dockerOption::Preset(p) => write!(f, "--preset={p}"),
394            X11dockerOption::Printenv(p) => write!(f, "--printenv={p}"),
395            X11dockerOption::Printid(p) => write!(f, "--printid={p}"),
396            X11dockerOption::Printinfofile(p) => write!(f, "--printinfofile={p}"),
397            X11dockerOption::Printpid1(p) => write!(f, "--printpid1={p}"),
398            X11dockerOption::Debug => write!(f, "--debug"),
399            X11dockerOption::Printcheck => write!(f, "--printcheck"),
400            X11dockerOption::Quiet => write!(f, "--quiet"),
401            X11dockerOption::Verbose => write!(f, "--verbose"),
402            X11dockerOption::Cleanup => write!(f, "--cleanup"),
403            X11dockerOption::Install => write!(f, "--install"),
404            X11dockerOption::Update(u) => write!(f, "--update={u}"),
405            X11dockerOption::UpdateMaster(u) => write!(f, "--update-master={u}"),
406            X11dockerOption::Remove => write!(f, "--remove"),
407            X11dockerOption::RemoveOldprefix => write!(f, "--remove-oldprefix"),
408        }
409    }
410}
411
412#[cfg(test)]
413mod test {
414    use super::*;
415
416    #[test]
417    fn test_default_x11docker() {
418        let x11docker = X11docker::default();
419        assert!(x11docker.arguments.is_none());
420        assert!(x11docker.proc_child.is_none());
421    }
422
423    #[test]
424    fn test_add_internet_argument() {
425        let x11docker = X11docker::default().internet();
426        assert!(x11docker.arguments.is_some());
427        let args = x11docker.arguments.unwrap();
428        assert!(args.contains(&X11dockerOption::Internet));
429    }
430
431    #[test]
432    fn test_add_image_name_argument() {
433        let x11docker = X11docker::default().image_name("test_image");
434        assert!(x11docker.arguments.is_some());
435        let args = x11docker.arguments.unwrap();
436        assert!(args.contains(&X11dockerOption::ImageName("test_image".to_string())));
437    }
438
439    #[test]
440    fn test_add_container_name_argument() {
441        let x11docker = X11docker::default().container_name("test_container");
442        assert!(x11docker.arguments.is_some());
443        let args = x11docker.arguments.unwrap();
444        assert!(args.contains(&X11dockerOption::ContainerName(
445            "test_container".to_string()
446        )));
447    }
448
449    #[test]
450    fn test_add_xpra_argument() {
451        let x11docker = X11docker::default().xpra();
452        assert!(x11docker.arguments.is_some());
453        let args = x11docker.arguments.unwrap();
454        assert!(args.contains(&X11dockerOption::Xpra));
455    }
456
457    #[test]
458    fn test_multiple_arguments() {
459        let x11docker = X11docker::default()
460            .internet()
461            .image_name("test_image")
462            .container_name("test_container")
463            .xpra();
464        assert!(x11docker.arguments.is_some());
465        let args = x11docker.arguments.unwrap();
466        assert!(args.contains(&X11dockerOption::Internet));
467        assert!(args.contains(&X11dockerOption::ImageName("test_image".to_string())));
468        assert!(args.contains(&X11dockerOption::ContainerName(
469            "test_container".to_string()
470        )));
471        assert!(args.contains(&X11dockerOption::Xpra));
472    }
473
474    #[test]
475    fn test_exec_without_arguments() {
476        let mut x11docker = X11docker::default();
477        let result = x11docker.exec();
478        assert!(result.is_err());
479    }
480}