use crate::config::DisplayMode;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DisplayBackend {
Spice, Gtk, Vnc, }
impl DisplayBackend {
pub fn qemu_args(&self, vnc_offset: u32) -> Vec<String> {
match self {
DisplayBackend::Spice => vec!["-display".to_string(), "spice-app".to_string()],
DisplayBackend::Gtk => vec!["-display".to_string(), "gtk".to_string()],
DisplayBackend::Vnc => vec![
"-display".to_string(),
format!("vnc=:{}", vnc_offset),
],
}
}
}
pub fn detect_backend() -> DisplayBackend {
if has_display_backend("spice-app") {
return DisplayBackend::Spice;
}
if has_display_backend("gtk") {
return DisplayBackend::Gtk;
}
DisplayBackend::Vnc
}
fn has_display_backend(name: &str) -> bool {
let Ok(output) = std::process::Command::new("qemu-system-x86_64")
.arg("-display")
.arg("help")
.output()
else {
return false;
};
String::from_utf8_lossy(&output.stdout)
.lines()
.any(|line| line.contains(name))
}
pub fn find_viewer(backend: DisplayBackend, vnc_port: u16) -> Option<(&'static str, Vec<String>)> {
match backend {
DisplayBackend::Spice => {
None
}
DisplayBackend::Gtk => {
None
}
DisplayBackend::Vnc => {
let vnc_display = format!("127.0.0.1:{}", vnc_port - 5900);
if which::which("gvncviewer").is_ok() {
Some(("gvncviewer", vec![vnc_display]))
} else if which::which("vinagre").is_ok() {
Some(("vinagre", vec![vnc_display]))
} else if which::which("xtightvncviewer").is_ok() {
Some(("xtightvncviewer", vec![vnc_display]))
} else if which::which("tigervncviewer").is_ok() {
Some(("tigervncviewer", vec![vnc_display]))
} else if which::which("vncviewer").is_ok() {
Some(("vncviewer", vec![vnc_display]))
} else {
None
}
}
}
}
pub fn launch_viewer(backend: DisplayBackend, vnc_port: u16) -> Option<String> {
if backend != DisplayBackend::Vnc {
return None;
}
let (cmd, args) = find_viewer(backend, vnc_port)?;
let _ = std::process::Command::new(cmd).args(&args).spawn();
Some(cmd.to_string())
}
pub fn connection_info(mode: DisplayMode, backend: DisplayBackend, vnc_port: u16) -> String {
match mode {
DisplayMode::Headless => {
match backend {
DisplayBackend::Vnc => format!("VNC server on 127.0.0.1:{vnc_port}"),
_ => format!("{:?} display on 127.0.0.1:{vnc_port}", backend),
}
}
DisplayMode::Headful => {
match backend {
DisplayBackend::Spice => "SPICE window will open automatically".to_string(),
DisplayBackend::Gtk => "GTK window will open automatically".to_string(),
DisplayBackend::Vnc => {
let viewer = find_viewer(backend, vnc_port)
.map(|(name, _)| name)
.unwrap_or("none");
format!("Launching {viewer} for VNC on 127.0.0.1:{vnc_port}")
}
}
}
}
}