use std::collections::HashMap;
use std::env;
use std::ffi::{c_char, CStr};
use std::fmt::Write as FmtWrite;
use std::fs;
use std::io::{self, BufRead, BufReader, Write};
use std::path::{Path, PathBuf};
use std::process::Command;
#[allow(dead_code)]
struct Colors {
red: &'static str,
s_red: &'static str,
steelblue: &'static str,
steelblue_lt: &'static str,
s_yellow: &'static str,
s_black: &'static str,
s_brightblue: &'static str,
purple: &'static str,
blue: &'static str,
s_blue: &'static str,
teal: &'static str,
teal_dim: &'static str,
white: &'static str,
orange: &'static str,
s_orange: &'static str,
green: &'static str,
s_green: &'static str,
reset_c: &'static str,
label_color: &'static str,
info_color: &'static str,
}
impl Colors {
fn grey() -> Self {
Self {
red: "", s_red: "", steelblue: "", steelblue_lt: "",
s_yellow: "", s_black: "", s_brightblue: "", purple: "",
blue: "", s_blue: "", teal: "", teal_dim: "", white: "",
orange: "", s_orange: "", green: "", s_green: "", reset_c: "",
label_color: "", info_color: "",
}
}
fn custom(hex: &str) -> Option<Self> {
let c = ansi_rgb(hex, false)?;
let cb = ansi_rgb(hex, true)?;
Some(Self {
red: c, s_red: c, steelblue: c, steelblue_lt: c,
s_yellow: c, s_black: c, s_brightblue: c, purple: c,
blue: c, s_blue: c, teal: c, teal_dim: c, white: c,
orange: c, s_orange: c, green: c, s_green: c,
reset_c: "\x1b[0m",
label_color: cb,
info_color: c,
})
}
fn default() -> Self {
Self {
s_yellow: "\x1b[33m",
s_black: "\x1b[39m",
s_brightblue: "\x1b[38;5;153m",
purple: "\x1b[35m",
blue: "\x1b[1;34m",
s_blue: "\x1b[34m",
teal: "\x1b[38;5;43m",
teal_dim: "\x1b[36m",
white: "\x1b[1;37m",
orange: "\x1b[1;38;5;208m",
s_orange: "\x1b[38;5;208m",
green: "\x1b[1;32m",
s_green: "\x1b[32m",
reset_c: "\x1b[0m",
red: "\x1b[1;31m",
s_red: "\x1b[31m",
steelblue: "\x1b[38;5;68m",
steelblue_lt: "\x1b[38;5;111m",
label_color: "",
info_color: "",
}
}
}
fn ansi_rgb(hex: &str, bold: bool) -> Option<&'static str> {
let h = hex.strip_prefix('#').unwrap_or(hex);
if h.len() != 6 { return None; }
let r = u8::from_str_radix(&h[0..2], 16).ok()?;
let g = u8::from_str_radix(&h[2..4], 16).ok()?;
let b = u8::from_str_radix(&h[4..6], 16).ok()?;
if bold {
Some(Box::leak(format!("\x1b[1;38;2;{};{};{}m", r, g, b).into_boxed_str()))
} else {
Some(Box::leak(format!("\x1b[38;2;{};{};{}m", r, g, b).into_boxed_str()))
}
}
fn read_file(path: &str) -> io::Result<String> {
fs::read_to_string(path).map(|s| s.trim().to_owned())
}
fn read_first_line(path: &str) -> io::Result<String> {
let f = fs::File::open(path)?;
let mut buf = String::new();
BufReader::new(f).read_line(&mut buf)?;
Ok(buf.trim().to_owned())
}
fn cmd_output(cmd: &str, args: &[&str]) -> String {
Command::new(cmd).args(args).output()
.ok()
.and_then(|o| if o.status.success() {
String::from_utf8(o.stdout).ok()
} else { None })
.map(|s| s.trim().to_owned())
.unwrap_or_default()
}
fn cmd_output_sh(cmdline: &str) -> String {
Command::new("sh").args(["-c", cmdline]).output()
.ok()
.and_then(|o| if o.status.success() {
String::from_utf8(o.stdout).ok()
} else { None })
.map(|s| s.trim().to_owned())
.unwrap_or_default()
}
fn sysctl_val(raw: &str, key: &str) -> String {
let prefix = format!("{}: ", key);
raw.lines()
.find(|l| l.starts_with(&prefix))
.and_then(|l| l.strip_prefix(&prefix))
.map(|s| s.trim().to_owned())
.unwrap_or_default()
}
struct MacCache {
sysctl: String,
vm_stat: String,
}
fn fetch_macos_cache() -> MacCache {
MacCache {
sysctl: cmd_output("sysctl", &[
"hw.memsize", "hw.pagesize", "hw.model",
"machdep.cpu.brand_string", "kern.boottime",
]),
vm_stat: cmd_output("vm_stat", &[]),
}
}
fn os_release_fields() -> (String, String, String, String) {
if env::var("BEDROCK_RESTRICT").map(|v| v != "1").unwrap_or(true) {
if let Ok(content) = read_file("/bedrock/strata/bedrock/etc/os-release") {
let mut id = String::new();
let mut id_like = String::new();
let mut name = String::new();
let mut version = String::new();
for line in content.lines() {
let line = line.trim();
if let Some(val) = line.strip_prefix("ID=") {
id = val.trim_matches('"').to_owned();
} else if let Some(val) = line.strip_prefix("ID_LIKE=") {
id_like = val.trim_matches('"').to_owned();
} else if let Some(val) = line.strip_prefix("NAME=") {
name = val.trim_matches('"').to_owned();
} else if let Some(val) = line.strip_prefix("VERSION=") {
version = val.trim_matches('"').to_owned();
}
}
if id == "bedrock" {
return (id, id_like, name, version);
}
}
}
let content = read_file("/etc/os-release")
.or_else(|_| read_file("/usr/lib/os-release"))
.unwrap_or_default();
let mut id = String::new();
let mut id_like = String::new();
let mut name = String::new();
let mut version = String::new();
for line in content.lines() {
let line = line.trim();
if let Some(val) = line.strip_prefix("ID=") {
id = val.trim_matches('"').to_owned();
} else if let Some(val) = line.strip_prefix("ID_LIKE=") {
id_like = val.trim_matches('"').to_owned();
} else if let Some(val) = line.strip_prefix("NAME=") {
name = val.trim_matches('"').to_owned();
} else if let Some(val) = line.strip_prefix("VERSION=") {
version = val.trim_matches('"').to_owned();
}
}
(id, id_like, name, version)
}
fn detect_proxmox() -> Option<String> {
if !Path::new("/usr/bin/pveversion").exists()
&& !Path::new("/usr/sbin/pveversion").exists()
&& !Path::new("/usr/local/bin/pveversion").exists()
{
return None;
}
let out = cmd_output("pveversion", &[]);
if out.is_empty() { return None; }
let ver = out.split('/').nth(1)?
.split('-').next()?;
Some(format!("Proxmox VE {}", ver))
}
fn detect_android() -> bool {
if let Ok(prefix) = env::var("PREFIX") {
if prefix.contains("com.termux") { return true; }
}
Path::new("/system/build.prop").exists()
}
fn extract_plist_value(plist: &str, key: &str) -> Option<String> {
let key_pat = format!("<key>{}</key>", key);
let start = plist.find(&key_pat)?;
let after = &plist[start + key_pat.len()..];
let open = after.find("<string>")?;
let val_start = &after[open + 8..];
let close = val_start.find("</string>")?;
Some(val_start[..close].to_owned())
}
fn detect_macos() -> Option<String> {
if let Ok(plist) = read_file("/System/Library/CoreServices/SystemVersion.plist") {
let ver = extract_plist_value(&plist, "ProductUserVisibleVersion")
.or_else(|| extract_plist_value(&plist, "ProductVersion"));
if let Some(v) = ver {
if v.starts_with("16.") {
return Some(format!("26.{}", &v[3..]));
}
return Some(v);
}
}
let out = cmd_output("sw_vers", &["-productVersion"]);
if !out.is_empty() {
return Some(out);
}
let uname = cmd_output("uname", &[]);
if uname.eq_ignore_ascii_case("darwin") {
let ver = cmd_output("uname", &["-r"]);
return Some(ver);
}
None
}
fn detect_container() -> Option<&'static str> {
if Path::new("/.dockerenv").exists() { return Some("Docker"); }
let cgroup = read_file("/proc/1/cgroup").unwrap_or_default();
if cgroup.contains("docker") { return Some("Docker"); }
if cgroup.contains("podman") { return Some("Podman"); }
if cgroup.contains("lxc") { return Some("LXC"); }
None
}
fn detect_virt(cpuinfo: &str, device_name: &str, mac: Option<&MacCache>) -> String {
let hv = read_first_line("/sys/hypervisor/type").unwrap_or_default();
if !hv.is_empty() { return hv; }
if cpuinfo.lines().any(|l| l.contains("hypervisor")) {
match device_name {
"KVM" => return "KVM".into(),
"VirtualBox" => return "VirtualBox".into(),
"VMware" | "VMware Virtual Platform" => return "VMware".into(),
_ => return "VM".into(),
}
}
let _ = mac;
let virt = cmd_output("sysctl", &["-n", "hw.virtualization"]);
if !virt.is_empty() && virt != "0" { return "VM".into(); }
String::new()
}
fn get_uname_release() -> String {
read_first_line("/proc/sys/kernel/osrelease")
.unwrap_or_default()
}
fn get_device_name(mac: Option<&MacCache>) -> String {
read_first_line("/sys/devices/virtual/dmi/id/product_name")
.or_else(|_| read_first_line("/sys/firmware/devicetree/base/model"))
.unwrap_or_else(|_| {
if let Some(m) = mac {
let model = sysctl_val(&m.sysctl, "hw.model");
if !model.is_empty() { return model; }
}
"Unknown".into()
})
}
fn get_uptime(mac: Option<&MacCache>) -> String {
if let Ok(content) = read_first_line("/proc/uptime") {
if let Some(secs) = content.split_whitespace().next()
.and_then(|s| s.parse::<f64>().ok()) {
let days = (secs / 86400.0) as u64;
let hours = ((secs % 86400.0) / 3600.0) as u64;
let mins = ((secs % 3600.0) / 60.0) as u64;
let mut out = String::new();
if days > 0 { write!(out, "{}d ", days).ok(); }
if hours > 0 || days > 0 { write!(out, "{}h ", hours).ok(); }
write!(out, "{}m", mins).ok();
if !out.is_empty() { return out; }
}
}
if let Some(m) = mac {
let boot_str = sysctl_val(&m.sysctl, "kern.boottime");
let boot_secs: i64 = boot_str.split("sec = ").nth(1)
.and_then(|s| s.split(',').next())
.and_then(|s| s.trim().parse().ok())
.unwrap_or(0);
if boot_secs > 0 {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs() as i64;
let secs = (now - boot_secs) as f64;
let days = (secs / 86400.0) as u64;
let hours = ((secs % 86400.0) / 3600.0) as u64;
let mins = ((secs % 3600.0) / 60.0) as u64;
let mut out = String::new();
if days > 0 { write!(out, "{}d ", days).ok(); }
if hours > 0 || days > 0 { write!(out, "{}h ", hours).ok(); }
write!(out, "{}m", mins).ok();
if !out.is_empty() { return out; }
}
}
"unknown".into()
}
fn get_cpu(cpuinfo: &str, mac: Option<&MacCache>) -> String {
if !cpuinfo.is_empty() {
for line in cpuinfo.lines() {
if let Some(val) = line.strip_prefix("model name\t: ") {
return val.trim().to_owned();
}
}
}
if let Some(m) = mac {
let brand = sysctl_val(&m.sysctl, "machdep.cpu.brand_string");
if !brand.is_empty() { return brand; }
}
"Unknown".into()
}
fn read_pci_ids() -> Option<String> {
fs::read_to_string("/usr/share/hwdata/pci.ids")
.or_else(|_| fs::read_to_string("/usr/share/misc/pci.ids"))
.ok()
}
fn clean_gpu_name(name: &str) -> String {
let mut s = name.to_owned();
for pat in &["Corporation ", "Technologies "] { s = s.replace(pat, ""); }
if let Some(open) = s.find('[') {
if let Some(close) = s[open+1..].find(']').map(|p| p + open + 1) {
s = s[open+1..close].to_owned();
}
}
s.trim().to_owned()
}
const GPU_DRIVERS: &[&str] = &[
"i915", "nvidia", "nvidia_drm", "nouveau",
"amdgpu", "radeon", "mgag200", "ast",
"vmwgfx", "bochs", "hyperv_fb",
];
fn get_gpu_from_sysfs() -> Vec<(String, String)> {
let mut gpus: Vec<(String, String, String)> = Vec::new();
for drv in GPU_DRIVERS {
if gpus.len() >= 2 { break; }
let drv_path = format!("/sys/bus/pci/drivers/{}", drv);
let Ok(dir) = fs::read_dir(&drv_path) else { continue; };
for entry in dir.flatten() {
let path = entry.path();
if !path.is_symlink() { continue; }
let class = read_first_line(&path.join("class").to_string_lossy()).unwrap_or_default();
if class != "0x030000" && class != "0x038000" { continue; }
let vid = read_first_line(&path.join("vendor").to_string_lossy())
.unwrap_or_default();
let did = read_first_line(&path.join("device").to_string_lossy())
.unwrap_or_default();
let vid_clean = vid.strip_prefix("0x").unwrap_or("");
let did_clean = did.strip_prefix("0x").unwrap_or("");
gpus.push((vid_clean.to_owned(), did_clean.to_owned(), drv.to_string()));
if gpus.len() >= 2 { break; }
}
}
if gpus.is_empty() {
if let Ok(entries) = fs::read_dir("/sys/bus/pci/devices") {
for entry in entries.flatten() {
if gpus.len() >= 2 { break; }
let p = entry.path();
let class = read_first_line(&p.join("class").to_string_lossy()).unwrap_or_default();
if class != "0x030000" && class != "0x038000" { continue; }
let vid = read_first_line(&p.join("vendor").to_string_lossy())
.unwrap_or_default();
let did = read_first_line(&p.join("device").to_string_lossy())
.unwrap_or_default();
let vid_clean = vid.strip_prefix("0x").unwrap_or("");
let did_clean = did.strip_prefix("0x").unwrap_or("");
let driver = p.join("driver").read_link().ok()
.and_then(|l| l.file_name().map(|f| f.to_string_lossy().into_owned()))
.unwrap_or_default();
gpus.push((vid_clean.to_owned(), did_clean.to_owned(), driver));
}
}
}
let ids = if gpus.is_empty() { None } else { read_pci_ids() };
let names = ids.as_ref().map(|c| {
let mut names: Vec<Option<&str>> = vec![None; gpus.len()];
let mut in_vendor: Option<usize> = None;
for line in c.lines() {
if line.is_empty() || line.starts_with('#') { continue; }
if !line.starts_with('\t') {
let parts: Vec<&str> = line.splitn(2, ' ').collect();
in_vendor = if parts.len() == 2 {
if parts[0].eq_ignore_ascii_case(&gpus[0].0) { Some(0) }
else if gpus.len() > 1 && parts[0].eq_ignore_ascii_case(&gpus[1].0) { Some(1) }
else { None }
} else { None };
continue;
}
if let Some(idx) = in_vendor {
if names[idx].is_some() { continue; }
let rest = line[1..].trim_start();
if let Some(dev_id) = rest.splitn(2, ' ').next() {
if dev_id.eq_ignore_ascii_case(&gpus[idx].1) {
names[idx] = rest.splitn(2, ' ').nth(1)
.map(|n| n.trim().trim_matches('"'));
if names.iter().all(|n| n.is_some()) { return names; }
}
}
}
}
names
});
gpus.into_iter().enumerate().map(|(i, (_, _, drv))| {
let name = names.as_ref()
.and_then(|n| n[i])
.map(|n| clean_gpu_name(n))
.unwrap_or_else(|| "Unknown".into());
(name, drv)
}).collect()
}
fn get_gpu(mac_sp: Option<&str>) -> (String, String) {
let mut gpus = get_gpu_from_sysfs();
if gpus.is_empty() {
let raw = cmd_output_sh("lspci 2>/dev/null | grep -iE 'vga|3d|display'");
let clean = |s: &str| -> String {
let mut s = s.to_owned();
if let Some(pos) = s.find(" (rev ") { s.truncate(pos); }
for pat in &["Corporation ", "Technologies ", "Inc.", "Inc ", "Co.", "Co "] {
s = s.replace(pat, "");
}
if let Some(open) = s.find('[') {
if let Some(close) = s[open+1..].find(']').map(|p| p + open + 1) {
s = s[open+1..close].to_owned();
}
}
s.trim().to_owned()
};
for line in raw.lines() {
if let Some(name) = line.split(": ").nth(1).map(clean) {
if !name.is_empty() { gpus.push((name, String::new())); }
}
}
}
#[cfg(target_os = "macos")]
if gpus.is_empty() {
if let Some(name) = macos_gpu::get_gpu_name() {
gpus.push((name, String::new()));
}
}
if gpus.is_empty() {
if let Some(out) = mac_sp {
for line in out.lines() {
let line = line.trim();
if let Some(name) = line.strip_prefix("Chipset Model:") {
let name = name.trim().to_owned();
if !name.is_empty() {
gpus.push((name, String::new()));
break;
}
}
}
}
}
let prime = env::var("DRI_PRIME").ok();
let swapped = prime.as_deref().map(|p| p != "0").unwrap_or(false);
let gpu_a = gpus.first().map(|g| g.0.clone()).unwrap_or_else(|| "Unknown".into());
let gpu_b = gpus.get(1).map(|g| g.0.clone()).unwrap_or_default();
if swapped && !gpu_b.is_empty() { (gpu_b, gpu_a) } else { (gpu_a, gpu_b) }
}
fn get_ram(mac: Option<&MacCache>) -> String {
fn human(bytes: u64) -> String {
if bytes >= 1_000_000_000 {
format!("{:.1} GiB", bytes as f64 / 1_073_741_824.0)
} else if bytes >= 1_000_000 {
format!("{:.0} MiB", bytes as f64 / 1_048_576.0)
} else {
format!("{:.0} KiB", bytes / 1024)
}
}
if let Ok(content) = read_file("/proc/meminfo") {
let parse_kb = |prefix: &str| -> Option<u64> {
content.lines()
.find(|l| l.starts_with(prefix))?
.split_whitespace()
.nth(1)?
.parse().ok()
};
let total_kb = parse_kb("MemTotal:").unwrap_or(0);
let avail_kb = parse_kb("MemAvailable:").unwrap_or(0);
if total_kb > 0 {
let used_kb = total_kb.saturating_sub(avail_kb);
return format!("{} / {}", human(used_kb * 1024), human(total_kb * 1024));
}
}
if let Some(m) = mac {
let total_bytes: u64 = sysctl_val(&m.sysctl, "hw.memsize").parse().unwrap_or(0);
if total_bytes > 0 {
let page_size: u64 = sysctl_val(&m.sysctl, "hw.pagesize").parse().unwrap_or(16384);
let parse_pages = |label: &str| -> u64 {
m.vm_stat.lines()
.find(|l| l.starts_with(label))
.and_then(|l| l.split_whitespace().nth(2))
.and_then(|s| s.trim_end_matches('.').parse().ok())
.unwrap_or(0)
};
let free_pages = parse_pages("Pages free:");
let inactive_pages = parse_pages("Pages inactive:");
let speculative_pages = parse_pages("Pages speculative:");
let available = (free_pages + inactive_pages + speculative_pages) * page_size;
let used = total_bytes.saturating_sub(available);
return format!("{} / {}", human(used), human(total_bytes));
}
}
String::new()
}
fn get_shell() -> String {
env::var("SHELL").ok()
.and_then(|s| {
Path::new(&s).file_name()
.map(|f| f.to_string_lossy().into_owned())
})
.unwrap_or_else(|| "unknown".into())
}
fn get_local_ip() -> String {
let content = read_file("/proc/net/fib_trie").unwrap_or_default();
let local_idx = match content.find("\nLocal:\n") {
Some(i) => i + 8,
None => return String::new(),
};
let section = &content[local_idx..];
let section_end = section.find("\n\n").unwrap_or(section.len());
let section = §ion[..section_end];
let mut prev_ip = "";
for line in section.lines() {
let trimmed = line.trim();
if trimmed.starts_with("/32") && trimmed.contains("LOCAL") && !prev_ip.is_empty() {
if prev_ip != "127.0.0.1" && prev_ip.contains('.') {
return prev_ip.to_string();
}
}
if trimmed.starts_with("|-- ") {
prev_ip = trimmed.trim_start_matches("|-- ").trim();
}
}
String::new()
}
fn capitalize_first(s: &str) -> String {
let mut c = s.chars();
match c.next() {
None => String::new(),
Some(f) => f.to_ascii_uppercase().to_string() + c.as_str(),
}
}
fn known_term(term: &str) -> Option<&'static str> {
let bare = term.strip_prefix("xterm-").unwrap_or(term);
match bare {
"ghostty" => Some("Ghostty"),
"kitty" => Some("kitty"),
"alacritty" => Some("Alacritty"),
"foot" | "foot-extra" => Some("foot"),
_ => None,
}
}
fn get_terminal() -> String {
if env::var("KITTY_PID").is_ok() || env::var("KITTY_INSTALLATION_DIR").is_ok() {
return "kitty".into();
}
if env::var("ALACRITTY_SOCKET").is_ok() || env::var("ALACRITTY_LOG").is_ok() || env::var("ALACRITTY_WINDOW_ID").is_ok() {
return "Alacritty".into();
}
if env::var("KONSOLE_VERSION").is_ok() {
return "Konsole".into();
}
if env::var("GNOME_TERMINAL_SCREEN").is_ok() || env::var("GNOME_TERMINAL_SERVICE").is_ok() {
return "GNOME Terminal".into();
}
if env::var("WT_SESSION").is_ok() || env::var("WT_PROFILE_ID").is_ok() {
return "Windows Terminal".into();
}
if env::var("TERMUX_VERSION").is_ok() {
return "Termux".into();
}
if let Ok(term) = env::var("TERM") {
if let Some(name) = known_term(&term) {
return name.into();
}
}
if let Ok(tp) = env::var("TERM_PROGRAM") {
if !tp.is_empty() { return capitalize_first(&tp); }
}
if let Ok(lc) = env::var("LC_TERMINAL") {
if !lc.is_empty() { return lc; }
}
let fallback = env::var("TERM").unwrap_or_else(|_| "unknown".into());
if let Some(suffix) = fallback.strip_prefix("xterm-") {
capitalize_first(suffix)
} else {
fallback
}
}
fn get_locale() -> String {
env::var("LANG").unwrap_or_default()
}
fn get_init() -> String {
if let Ok(s) = read_first_line("/proc/1/comm") {
return s;
}
if Path::new("/sbin/launchd").exists() {
return "launchd".into();
}
String::new()
}
fn get_bedrock_inits() -> String {
let curr = get_init();
let Ok(dir) = fs::read_dir("/bedrock/strata") else { return curr; };
let mut inits = if curr.is_empty() { Vec::new() } else { vec![curr.clone()] };
let mut seen: Vec<String> = Vec::new();
for entry in dir.flatten() {
let stratum = entry.file_name().to_string_lossy().into_owned();
if stratum == "bedrock" { continue; }
let init_path = format!("/bedrock/strata/{}/sbin/init", stratum);
if let Ok(real) = fs::canonicalize(&init_path) {
let r = real.to_string_lossy().into_owned();
if seen.contains(&r) { continue; }
seen.push(r);
if let Some(name) = real.file_name().map(|f| f.to_string_lossy().into_owned()) {
if !inits.contains(&name) { inits.push(name); }
}
}
}
inits.join(", ")
}
const KNOWN_WMS: &[&str] = &[
"Hyprland", "Sway", "i3", "Niri", "River", "Qtile", "Wayfire", "LabWC",
"dwl", "awesome", "bspwm", "herbstluftwm", "openbox", "fluxbox", "jwm",
"pekwm", "fvwm", "2bwm", "dwm", "cwm", "wmii", "weston", "Xorg", "X",
];
fn get_wm_de() -> (String, String) {
let mut wm = String::new();
let mut de = String::new();
if env::var("HYPRLAND_INSTANCE_SIGNATURE").is_ok() {
wm = "Hyprland".into();
} else if env::var("SWAYSOCK").is_ok() {
wm = "Sway".into();
} else if env::var("I3SOCK").is_ok() || env::var("i3SOCK").is_ok() {
wm = "i3".into();
} else if env::var("NIRI_SOCKET").is_ok() {
wm = "Niri".into();
} else if env::var("RIVER_SOCKET").is_ok() {
wm = "River".into();
} else if env::var("QTILE_SOCKET").is_ok() {
wm = "Qtile".into();
} else if env::var("WAYFIRE_SOCKET").is_ok() {
wm = "Wayfire".into();
} else if env::var("LABWC_PID_FILE").is_ok() || Path::new("/tmp/labwc-").exists() {
wm = "LabWC".into();
}
if let Ok(de_var) = env::var("XDG_CURRENT_DESKTOP") {
if !de_var.is_empty() && !KNOWN_WMS.contains(&de_var.as_str()) {
de = de_var;
}
}
if de.is_empty() {
if let Ok(ds) = env::var("DESKTOP_SESSION") {
if !ds.is_empty() && !KNOWN_WMS.contains(&ds.as_str()) {
de = ds;
}
}
}
if de.is_empty() {
if env::var("GNOME_DESKTOP_SESSION_ID").is_ok() {
de = "GNOME".into();
} else if env::var("KDE_FULL_SESSION").is_ok() {
de = "KDE".into();
} else if env::var("MATE_DESKTOP_SESSION_ID").is_ok() {
de = "MATE".into();
} else if env::var("CINNAMON_VERSION").is_ok() {
de = "Cinnamon".into();
} else if env::var("XFCE_SESSION").is_ok() || env::var("XFCONF").is_ok() {
de = "XFCE".into();
} else if env::var("LXQT_SESSION_DIR").is_ok() {
de = "LXQt".into();
} else if Path::new("/usr/bin/lxsession").exists() || env::var("LXDE").is_ok() {
de = "LXDE".into();
} else if Path::new("/usr/bin/budgie-wm").exists() {
de = "Budgie".into();
} else if Path::new("/usr/bin/deepin-wm").exists() {
de = "Deepin".into();
} else if Path::new("/usr/bin/muffin").exists() {
de = "Cinnamon".into();
}
if KNOWN_WMS.contains(&de.as_str()) { de.clear(); }
}
if wm.is_empty() && !de.is_empty() && env::var("WAYLAND_DISPLAY").is_err() {
let check_wm = &["Xorg", "X", "weston", "dwl", "awesome", "bspwm",
"herbstluftwm", "openbox", "fluxbox", "jwm", "pekwm",
"fvwm", "2bwm", "dwm", "cwm", "qtile", "wmii"];
for wm_bin in check_wm {
if !Path::new(&format!("/usr/bin/{}", wm_bin)).exists() &&
!Path::new(&format!("/usr/local/bin/{}", wm_bin)).exists() { continue; }
if cmd_output("pidof", &[wm_bin]).trim().is_empty() { continue; }
wm = wm_bin.to_string();
break;
}
}
(de, wm)
}
fn get_display_resolutions(mac_sp: Option<&str>) -> (String, String) {
if let Ok(entries) = fs::read_dir("/sys/class/drm") {
let mut nd = 0;
let mut disp1 = String::new();
let mut disp2 = String::new();
for entry in entries.flatten() {
let status_path = entry.path().join("status");
let modes_path = entry.path().join("modes");
if !status_path.exists() || !modes_path.exists() { continue; }
let status = read_first_line(&status_path.to_string_lossy()).unwrap_or_default();
if status != "connected" { continue; }
let mode = read_first_line(&modes_path.to_string_lossy()).unwrap_or_default();
if mode.is_empty() { continue; }
if nd == 0 { disp1 = mode; }
else { disp2 = mode; }
nd += 1;
if nd >= 2 { break; }
}
if !disp1.is_empty() { return (disp1, disp2); }
}
if env::var("HYPRLAND_INSTANCE_SIGNATURE").is_ok() {
let out = cmd_output("hyprctl", &["monitors"]);
let mut nd = 0;
let mut disp1 = String::new();
let mut disp2 = String::new();
for line in out.lines() {
if let Some(at) = line.trim().strip_prefix("Monitor ") {
if let Some(res) = at.split_whitespace().next() {
if let Some(paren) = res.find('(') {
let inner = &res[paren+1..];
if let Some(res_str) = inner.split('@').next() {
if nd == 0 { disp1 = res_str.to_owned(); }
else { disp2 = res_str.to_owned(); }
nd += 1;
}
}
if nd >= 2 { break; }
}
}
}
if !disp1.is_empty() { return (disp1, disp2); }
}
if env::var("DISPLAY").is_ok() {
let out = cmd_output("xrandr", &["--nograb"]);
if !out.is_empty() {
let mut res: Vec<(&str, bool)> = Vec::new();
for line in out.lines() {
if !line.contains(" connected") { continue; }
let primary = line.contains(" primary");
for part in line.split_whitespace() {
if let Some(res_str) = part.split(|c| c == '+' || c == '@').next() {
if res_str.contains('x') && res_str.chars().all(|c| c.is_digit(10) || c == 'x') {
res.push((res_str, primary));
break;
}
}
}
}
if !res.is_empty() {
let pri_idx = res.iter().position(|r| r.1).unwrap_or(0);
let disp1 = res[pri_idx].0.to_owned();
let disp2 = if res.len() > 1 {
res.iter().enumerate().find(|(i, _)| *i != pri_idx).map(|(_, r)| r.0.to_owned()).unwrap_or_default()
} else { String::new() };
return (disp1, disp2);
}
}
}
#[cfg(target_os = "macos")]
if let Some((d1, d2)) = macos_gpu::get_resolution() {
return (d1, d2);
}
if let Some(out) = mac_sp {
let mut displays = Vec::new();
for line in out.lines() {
let line = line.trim();
if let Some(res) = line.strip_prefix("Resolution:") {
let res = res.trim();
if let Some(dims) = res.split(" Retina").next() {
let dims = dims.replace(" ", "");
if !dims.is_empty() { displays.push(dims); }
}
}
}
if !displays.is_empty() {
let d1 = displays.remove(0);
let d2 = displays.first().cloned().unwrap_or_default();
return (d1, d2);
}
}
("unknown".into(), String::new())
}
fn count_packages_dir(path: &str) -> usize {
fs::read_dir(path).ok()
.map(|e| e.flatten().filter(|e| e.file_type().map(|t| t.is_dir()).unwrap_or(false)).count())
.unwrap_or(0)
}
fn count_dpkg_packages() -> usize {
fs::read_to_string("/var/lib/dpkg/status").ok()
.map(|c| c.lines().filter(|l| l.trim() == "Status: install ok installed").count())
.unwrap_or(0)
}
fn get_bedrock_packages() -> String {
let Ok(dir) = fs::read_dir("/bedrock/strata") else { return String::new(); };
let mut counts: Vec<String> = Vec::new();
let mut seen_pacman: Vec<String> = Vec::new();
for entry in dir.flatten() {
let stratum = entry.file_name().to_string_lossy().into_owned();
if stratum == "bedrock" { continue; }
let base = format!("/bedrock/strata/{}", stratum);
let pacman = format!("{}/var/lib/pacman/local", base);
let pacman_real = fs::canonicalize(&pacman).ok()
.map(|p| p.to_string_lossy().into_owned());
let already_seen = pacman_real.as_ref()
.map(|r| seen_pacman.contains(r))
.unwrap_or(false);
if !already_seen {
let n = count_packages_dir(&pacman);
if n > 0 {
if let Some(r) = pacman_real { seen_pacman.push(r); }
counts.push(format!("{} (pacman, {})", n, stratum));
continue;
}
}
let dpkg = format!("{}/var/lib/dpkg/status", base);
if Path::new(&dpkg).exists() {
if let Ok(c) = fs::read_to_string(&dpkg) {
let n = c.lines().filter(|l| l.trim() == "Status: install ok installed").count();
if n > 0 { counts.push(format!("{} (dpkg, {})", n, stratum)); }
}
}
}
counts.join(", ")
}
fn get_packages(arch_based: bool, deb_based: bool) -> String {
let mut counts: Vec<String> = Vec::new();
if arch_based {
let n = count_packages_dir("/var/lib/pacman/local");
if n > 0 { counts.push(format!("{} (pacman)", n)); }
} else if deb_based {
let n = count_dpkg_packages();
if n > 0 { counts.push(format!("{} (dpkg)", n)); }
else {
let out = cmd_output_sh("dpkg-query -f '${db:Status-Status}' -W 2>/dev/null | grep -c 'installed'");
let n: usize = out.trim().parse().unwrap_or(0);
if n > 0 { counts.push(format!("{} (dpkg)", n)); }
}
} else {
let n = count_packages_dir("/var/lib/pacman/local");
if n > 0 { counts.push(format!("{} (pacman)", n)); }
}
if counts.is_empty() && Path::new("/var/lib/rpm").exists() {
let out = cmd_output_sh("rpm -qa 2>/dev/null | wc -l");
let n: usize = out.trim().parse().unwrap_or(0);
if n > 0 { counts.push(format!("{} (rpm)", n)); }
}
if Path::new("/usr/bin/flatpak").exists() {
let mut n = 0usize;
for base in &["/var/lib/flatpak"] {
n += count_packages_dir(&format!("{}/app", base));
n += count_packages_dir(&format!("{}/runtime", base));
}
if let Ok(home) = env::var("HOME") {
let user = format!("{}/.local/share/flatpak", home);
n += count_packages_dir(&format!("{}/app", user));
n += count_packages_dir(&format!("{}/runtime", user));
}
if n > 0 { counts.push(format!("{} (flatpak)", n)); }
}
if Path::new("/usr/bin/snap").exists() {
let out = cmd_output("snap", &["list"]);
let n = out.lines().filter(|l| !l.is_empty()).count();
let actual = n.saturating_sub(1);
if actual > 0 { counts.push(format!("{} (snap)", actual)); }
}
counts.join(", ")
}
unsafe extern "C" {
fn statvfs(path: *const c_char, buf: *mut libc_statvfs) -> i32;
fn gethostname(name: *mut c_char, len: usize) -> i32;
}
#[cfg(unix)]
unsafe extern "C" {
fn signal(sig: i32, handler: usize) -> usize;
}
#[cfg(unix)]
fn reset_sigpipe() {
const SIGPIPE: i32 = 13;
const SIG_DFL: usize = 0;
unsafe { signal(SIGPIPE, SIG_DFL); }
}
#[cfg(target_os = "macos")]
#[repr(C)]
struct libc_utsname {
sysname: [i8; 256],
nodename: [i8; 256],
release: [i8; 256],
version: [i8; 256],
machine: [i8; 256],
}
#[cfg(target_os = "macos")]
unsafe extern "C" {
fn uname(buf: *mut libc_utsname) -> i32;
}
#[cfg(target_os = "linux")]
#[repr(C)]
struct libc_statvfs {
f_bsize: u64,
f_frsize: u64,
f_blocks: u64,
f_bfree: u64,
f_bavail: u64,
f_files: u64,
f_ffree: u64,
f_favail: u64,
f_fsid: u64,
f_flag: u64,
f_namemax: u64,
__f_spare: [i32; 6],
}
#[cfg(target_os = "macos")]
#[repr(C)]
struct libc_statvfs {
f_bsize: u64,
f_frsize: u64,
f_blocks: u32,
f_bfree: u32,
f_bavail: u32,
f_files: u32,
f_ffree: u32,
f_favail: u32,
f_fsid: u64,
f_flag: u64,
f_namemax: u64,
}
#[cfg(target_os = "macos")]
#[allow(non_camel_case_types)]
mod macos_gpu {
use std::ffi::{c_void, CStr};
type io_object_t = u32;
type io_iterator_t = u32;
type cf_type_ref = *const c_void;
type cf_allocator_ref = *const c_void;
type cg_direct_display_id = u32;
const KERN_SUCCESS: i32 = 0;
const K_CF_ENCODING_UTF8: u32 = 0x08000100;
#[link(name = "IOKit", kind = "framework")]
#[link(name = "CoreGraphics", kind = "framework")]
#[link(name = "CoreFoundation", kind = "framework")]
unsafe extern "C" {
fn IOServiceMatching(name: *const i8) -> *mut c_void;
fn IOServiceGetMatchingServices(port: u32, m: *mut c_void, iter: *mut io_iterator_t) -> i32;
fn IOIteratorNext(iter: io_iterator_t) -> io_object_t;
fn IORegistryEntryCreateCFProperty(entry: io_object_t, key: cf_type_ref, alloc: cf_allocator_ref, opts: u32) -> cf_type_ref;
fn IOObjectRelease(obj: io_object_t) -> i32;
fn CGGetActiveDisplayList(max: u32, displays: *mut cg_direct_display_id, count: *mut u32) -> i32;
fn CGDisplayPixelsWide(d: cg_direct_display_id) -> usize;
fn CGDisplayPixelsHigh(d: cg_direct_display_id) -> usize;
fn CFRelease(cf: cf_type_ref);
fn CFStringCreateWithCString(alloc: cf_allocator_ref, s: *const i8, enc: u32) -> cf_type_ref;
fn CFStringGetCString(cf: cf_type_ref, buf: *mut i8, len: usize, enc: u32) -> bool;
}
pub fn get_gpu_name() -> Option<String> {
unsafe {
let key = CFStringCreateWithCString(
std::ptr::null(), b"model\0".as_ptr() as *const i8, K_CF_ENCODING_UTF8);
if key.is_null() { return None; }
let cls_a = b"IOGPUDevice\0".as_ptr() as *const i8;
let cls_b = b"AGXAccelerator\0".as_ptr() as *const i8;
let classes: &[*const i8] = &[cls_a, cls_b, std::ptr::null()];
for cls in classes {
if cls.is_null() { break; }
let mut iter: io_iterator_t = 0;
let m = IOServiceMatching(*cls);
if m.is_null() { continue; }
if IOServiceGetMatchingServices(0, m, &mut iter) != KERN_SUCCESS { continue; }
loop {
let svc = IOIteratorNext(iter);
if svc == 0 { break; }
let r = IORegistryEntryCreateCFProperty(svc, key, std::ptr::null(), 0);
if !r.is_null() {
let mut buf = [0i8; 256];
let ok = CFStringGetCString(r, buf.as_mut_ptr(), buf.len(), K_CF_ENCODING_UTF8);
CFRelease(r);
IOObjectRelease(svc);
IOObjectRelease(iter);
if ok {
CFRelease(key);
return Some(CStr::from_ptr(buf.as_ptr()).to_string_lossy().into_owned());
}
CFRelease(key);
return None;
}
IOObjectRelease(svc);
}
IOObjectRelease(iter);
}
CFRelease(key);
None
}
}
pub fn get_resolution() -> Option<(String, String)> {
unsafe {
let mut displays = [0u32; 16];
let mut count = 0u32;
if CGGetActiveDisplayList(16, displays.as_mut_ptr(), &mut count) != 0 { return None; }
let mut res = Vec::new();
for i in 0..count as usize {
let w = CGDisplayPixelsWide(displays[i]);
let h = CGDisplayPixelsHigh(displays[i]);
if w > 0 && h > 0 { res.push(format!("{}x{}", w, h)); }
}
if res.is_empty() { return None; }
let d1 = res.remove(0);
let d2 = res.first().cloned().unwrap_or_default();
Some((d1, d2))
}
}
}
fn get_disk_usage() -> String {
let mut buf = std::mem::MaybeUninit::<libc_statvfs>::zeroed();
let path = "/\0";
let ret = unsafe { statvfs(path.as_ptr() as *const c_char, buf.as_mut_ptr()) };
if ret != 0 { return String::new(); }
let s = unsafe { buf.assume_init() };
let total = (s.f_blocks as u64).saturating_mul(s.f_frsize);
let avail = (s.f_bavail as u64).saturating_mul(s.f_frsize);
let used = total.saturating_sub(avail);
if total == 0 { return String::new(); }
let pct = used as f64 / total as f64 * 100.0;
fn fmt_size(bytes: u64) -> String {
const UNITS: &[&str] = &["B", "KiB", "MiB", "GiB", "TiB", "PiB"];
let mut v = bytes as f64;
let mut i = 0usize;
while v >= 1024.0 && i < UNITS.len() - 1 {
v /= 1024.0;
i += 1;
}
format!("{:.0}{}", v, UNITS[i])
}
format!("{} / {} ({:.0}%)", fmt_size(used), fmt_size(total), pct)
}
fn choose_label_color(distro_id: &str, distro_base: &str, colors: &Colors, is_proxmox: bool, is_android: bool, is_macos: bool) -> &'static str {
if is_proxmox { return colors.orange; }
if is_android { return colors.green; }
if is_macos { return colors.label_color; }
let id = distro_id;
if id == "sparky" { return colors.red; }
if matches!(id, "arch" | "endeavouros" | "manjaro" | "garuda") { return colors.blue; }
if id == "artix" { return colors.blue; }
if id == "bedrock" { return colors.s_black; }
if id == "fedora" { return colors.blue; }
if id == "ubuntu" { return colors.orange; }
if id == "linuxmint" { return colors.green; }
if id == "pop" { return colors.teal; }
if id == "cachyos" { return colors.teal; }
if id == "void" { return colors.green; }
if id == "nixos" { return colors.blue; }
if id == "debian" { return colors.s_red; }
if id == "gentoo" { return colors.steelblue; }
if id.contains("opensuse") { return colors.green; }
if id == "alpine" { return colors.s_blue; }
if id == "slackware" { return colors.s_black; }
if id == "solus" { return colors.s_brightblue; }
if id == "rhel" || id == "centos" { return colors.red; }
if distro_base.contains("arch") { return colors.blue; }
if distro_base.contains("suse") || distro_base.contains("opensuse") { return colors.green; }
if distro_base.contains("debian") || distro_base.contains("ubuntu") { return colors.s_red; }
colors.purple
}
type AsciiFn = fn(&Colors, &mut Vec<String>);
fn ascii_proxmox(c: &Colors, out: &mut Vec<String>) {
let (o, w, r) = (c.orange, c.white, c.reset_c);
out.push(format!(" {o}@@@@{r} {o}@@@@{r}", o=o, r=r));
out.push(format!(" {w}++++{r} {o}@@@@@@@{r} {w}++++{r}", w=w, o=o, r=r));
out.push(format!(" {w}++++{r} {o}@@@{r} {w}++++{r}", w=w, o=o, r=r));
out.push(format!(" {w}++++ ++++{r}", w=w, r=r));
out.push(format!(" {w}++++{r}{o}@@@{r}{w}++++{r}", w=w, o=o, r=r));
out.push(format!(" {w}++++{r} {o}@@@@@{r} {w}++++{r}", w=w, o=o, r=r));
out.push(format!(" {w}+++{r} {o}@@@@{r} {o}@@@@{r} {w}+++{r}", w=w, o=o, r=r));
out.push(format!(" {o}@@@{r} {o}@@@{r}", o=o, r=r));
out.push(String::new());
}
fn ascii_android(c: &Colors, out: &mut Vec<String>) {
let (g, r) = (c.s_green, c.reset_c);
out.push(format!(" {g}-o o-{r}", g=g, r=r));
out.push(format!(" {g}+hydNNNNdyh+{r}", g=g, r=r));
out.push(format!(" {g}+mMMMMMMMMMMMMm+{r}", g=g, r=r));
out.push(format!(" {g}dMMm:NMMMMMMN:mMMd{r}", g=g, r=r));
out.push(format!(" {g}hMMMMMMMMMMMMMMMMMMh{r}", g=g, r=r));
out.push(format!(" {g}.. yyyyyyyyyyyyyyyyyyyy ..{r}", g=g, r=r));
out.push(format!("{g}.mMMm MMMMMMMMMMMMMMMMMMMM mMMm.{r}", g=g, r=r));
out.push(format!("{g}:MMMM-MMMMMMMMMMMMMMMMMMMM-MMMM:{r}", g=g, r=r));
out.push(format!("{g}:MMMM-MMMMMMMMMMMMMMMMMMMM-MMMM:{r}", g=g, r=r));
out.push(format!("{g}:MMMM-MMMMMMMMMMMMMMMMMMMM-MMMM:{r}", g=g, r=r));
out.push(format!("{g}:MMMM-MMMMMMMMMMMMMMMMMMMM-MMMM:{r}", g=g, r=r));
out.push(format!("{g}-MMMM-MMMMMMMMMMMMMMMMMMMM-MMMM-{r}", g=g, r=r));
out.push(format!(" {g}+yy+ MMMMMMMMMMMMMMMMMMMM +yy+{r}", g=g, r=r));
out.push(format!(" {g}mMMMMMMMMMMMMMMMMMMm{r}", g=g, r=r));
out.push(format!(" {g}/++MMMMh++hMMMM++/{r}", g=g, r=r));
out.push(format!(" {g}MMMMo oMMMM{r}", g=g, r=r));
out.push(format!(" {g}MMMMo oMMMM{r}", g=g, r=r));
out.push(format!(" {g}oNMm- -mMNs{r}", g=g, r=r));
}
fn ascii_arch(c: &Colors, out: &mut Vec<String>) {
let b = c.s_blue; let r = c.reset_c;
out.push(format!(" {b}-'{r}", b=b, r=r));
out.push(format!(" {b}.o+'{r}", b=b, r=r));
out.push(format!(" {b}'ooo/{r}", b=b, r=r));
out.push(format!(" {b}'+oooo:{r}", b=b, r=r));
out.push(format!(" {b}'+oooooo:{r}", b=b, r=r));
out.push(format!(" {b}-+oooooo+:{r}", b=b, r=r));
out.push(format!(" {b}'/:-:++oooo+:{r}", b=b, r=r));
out.push(format!(" {b}'/++++/+++++++:{r}", b=b, r=r));
out.push(format!(" {b}'/++++++++++++++:{r}", b=b, r=r));
out.push(format!(" {b}'/+++ooooooooooooo/'{r}", b=b, r=r));
out.push(format!(" {b}./ooosssso++osssssso+'{r}", b=b, r=r));
out.push(format!(" {b}.oossssso-''''/ossssss+'{r}", b=b, r=r));
out.push(format!(" {b}-osssssso. :ssssssso.{r}", b=b, r=r));
out.push(format!(" {b}:osssssss/ osssso+++.{r}", b=b, r=r));
out.push(format!(" {b}/ossssssss/ +ssssooo/-{r}", b=b, r=r));
out.push(format!(" {b}'/ossssso+/:- -:/+osssso+-{r}", b=b, r=r));
out.push(format!(" {b}'+sso+:-' '.-/+oso:{r}", b=b, r=r));
out.push(format!(" {b}'++:. '-/+/{r}", b=b, r=r));
out.push(format!(" {b}.' '{r}", b=b, r=r));
}
fn ascii_artix(c: &Colors, out: &mut Vec<String>) {
let b = c.s_blue; let r = c.reset_c;
out.push(String::new());
out.push(format!(" {b}+{r}", b=b, r=r));
out.push(format!(" {b}=++{r}", b=b, r=r));
out.push(format!(" {b}=+=+={r}", b=b, r=r));
out.push(format!(" {b}-+===+-{r}", b=b, r=r));
out.push(format!(" {b}-++====+-{r}", b=b, r=r));
out.push(format!(" {b}:+++-====+:{r}", b=b, r=r));
out.push(format!(" {b}=+***+===++.{r}", b=b, r=r));
out.push(format!(" {b}:****==++.{r}", b=b, r=r));
out.push(format!(" {b}.+. .***=++.{r}", b=b, r=r));
out.push(format!(" {b}+++=++- +*++{r}", b=b, r=r));
out.push(format!(" {b}.=++=====++++ -+.{r}", b=b, r=r));
out.push(format!(" {b}++++=======++++++.{r}", b=b, r=r));
out.push(format!(" {b}=+++=======-+++++++=.{r}", b=b, r=r));
out.push(format!(" {b}=+++======+****+: :+={r}", b=b, r=r));
out.push(format!(" {b}-+++====+***=. -++==+-{r}", b=b, r=r));
out.push(format!(" {b}-+++==***: =+++++===+-{r}", b=b, r=r));
out.push(format!(" {b}:+++*+. :+**+=+.{r}", b=b, r=r));
out.push(format!(" {b}:+- .=+:{r}", b=b, r=r));
out.push(String::new());
}
fn ascii_ubuntu(c: &Colors, out: &mut Vec<String>) {
let o = c.s_orange; let r = c.reset_c;
out.push(String::new());
out.push(format!(" {o}==========={r}", o=o, r=r));
out.push(format!(" {o}==================={r}", o=o, r=r));
out.push(format!(" {o}======================={r}", o=o, r=r));
out.push(format!(" {o}================= ====={r}", o=o, r=r));
out.push(format!(" {o}========== = ======{r}", o=o, r=r));
out.push(format!(" {o}========= -= ==========={r}", o=o, r=r));
out.push(format!(" {o}======== .=======. ========{r}", o=o, r=r));
out.push(format!(" {o}======== =========== ========{r}", o=o, r=r));
out.push(format!(" {o}==== = ============= :======={r}", o=o, r=r));
out.push(format!(" {o}=== = ========================{r}", o=o, r=r));
out.push(format!(" {o}==== = ============= :======={r}", o=o, r=r));
out.push(format!(" {o}======== =========== ========{r}", o=o, r=r));
out.push(format!(" {o}======== .=======: ========{r}", o=o, r=r));
out.push(format!(" {o}========= -= ==========={r}", o=o, r=r));
out.push(format!(" {o}========== = ======{r}", o=o, r=r));
out.push(format!(" {o}================= ====={r}", o=o, r=r));
out.push(format!(" {o}======================={r}", o=o, r=r));
out.push(format!(" {o}==================={r}", o=o, r=r));
out.push(format!(" {o}==========={r}", o=o, r=r));
out.push(String::new());
out.push(String::new());
}
fn ascii_mint(c: &Colors, out: &mut Vec<String>) {
let g = c.s_green; let r = c.reset_c;
out.push(String::new());
out.push(format!(" {g}==============={r}", g=g, r=r));
out.push(format!(" {g}====================={r}", g=g, r=r));
out.push(format!(" {g}========================={r}", g=g, r=r));
out.push(format!(" {g}==={r}...{g}====================={r}", g=g, r=r));
out.push(format!(" {g}===={r}...{g}====={r}.....{g}={r}.....{g}======{r}", g=g, r=r));
out.push(format!(" {g}====={r}...{g}==={r}...............{g}====={r}", g=g, r=r));
out.push(format!(" {g}======{r}...{g}==={r}...{g}==={r}...{g}==={r}...{g}======{r}", g=g, r=r));
out.push(format!(" {g}======{r}...{g}==={r}...{g}==={r}...{g}==={r}...{g}======{r}", g=g, r=r));
out.push(format!(" {g}======{r}...{g}==={r}...{g}==={r}...{g}==={r}...{g}======{r}", g=g, r=r));
out.push(format!(" {g}======{r}...{g}==={r}...{g}==={r}...{g}==={r}...{g}======{r}", g=g, r=r));
out.push(format!(" {g}======{r}...{g}==={r}...{g}==={r}...{g}==={r}...{g}======{r}", g=g, r=r));
out.push(format!(" {g}====={r}...{g}==============={r}...{g}====={r}", g=g, r=r));
out.push(format!(" {g}====={r}...................{g}====={r}", g=g, r=r));
out.push(format!(" {g}======{r}...............{g}======{r}", g=g, r=r));
out.push(format!(" {g}========================={r}", g=g, r=r));
out.push(format!(" {g}====================={r}", g=g, r=r));
out.push(format!(" {g}==============={r}", g=g, r=r));
out.push(String::new());
out.push(String::new());
}
fn ascii_pop(c: &Colors, out: &mut Vec<String>) {
let b = c.s_brightblue; let r = c.reset_c;
out.push(String::new());
out.push(format!(" {b}.++++++++++++*..{r}", b=b, r=r));
out.push(format!(" {b}.++++++++++++++*****.{r}", b=b, r=r));
out.push(format!(" {b}.+++{r}.........{b}++*********.{r}", b=b, r=r));
out.push(format!(" {b}+++{r}.....{b}+-{r}.....{b}+***********{r}", b=b, r=r));
out.push(format!(" {b}++++{r}.....{b}+++{r}.....{b}************{r}", b=b, r=r));
out.push(format!(" {b}++++++{r}.....{b}+**{r}....{b}***{r}....{b}******{r}", b=b, r=r));
out.push(format!(" {b}.+++++++{r}.....{b}*={r}...{b}+**{r}.....{b}******.{r}", b=b, r=r));
out.push(format!(" {b}+++++++++{r}........{b}****{r}....{b}********{r}", b=b, r=r));
out.push(format!(" {b}++++++++**{r}.....{b}******{r}...{b}*********{r}", b=b, r=r));
out.push(format!(" {b}++++++*****{r}....{b}******{r}..{b}**********{r}", b=b, r=r));
out.push(format!(" {b}.+++********{r}....{b}****************.{r}", b=b, r=r));
out.push(format!(" {b}+***********{r}....{b}***{r}..{b}**********{r}", b=b, r=r));
out.push(format!(" {b}*****************************{r}", b=b, r=r));
out.push(format!(" {b}*****{r}.................{b}*****{r}", b=b, r=r));
out.push(format!(" {b}.***{r}.................{b}***.{r}", b=b, r=r));
out.push(format!(" {b}.*******************.{r}", b=b, r=r));
out.push(format!(" {b}..*************..{r}", b=b, r=r));
out.push(String::new());
out.push(String::new());
out.push(String::new());
}
fn ascii_cachyos(c: &Colors, out: &mut Vec<String>) {
let t = c.teal; let d = c.teal_dim; let r = c.reset_c;
out.push(format!(" {t}**{d}++++++++++++++++{t}*{r}", t=t, d=d, r=r));
out.push(format!(" {d}=++++{t}*{d}+++++++++++++ {d}={r}", d=d, t=t, r=r));
out.push(format!(" {d}==+++==++{t}*{d}+++++++++ {d}=--{r}", d=d, t=t, r=r));
out.push(format!(" {d}===++++====++++++++ {d}={r}", d=d, r=r));
out.push(format!(" {d}====+++++++++++++++{r}", d=d, r=r));
out.push(format!(" {d}=====++++ {d}+++{r}", d=d, r=r));
out.push(format!(" {d}===+++{t}*{d}++ {d}====={r}", d=d, t=t, r=r));
out.push(format!(" {d}+++++++{t}*{d}+ {d}---{r}", d=d, t=t, r=r));
out.push(format!("{d}========{t}*{r}", d=d, t=t, r=r));
out.push(format!(" {d}===++++++ {d}++++{r}", d=d, r=r));
out.push(format!(" {d}+{t}*{d}+++++++ {d}======{r}", d=d, t=t, r=r));
out.push(format!(" {d}+++{t}***{d}+++= {d}----{r}", d=d, t=t, r=r));
out.push(format!(" {d}+++++===+++================={r}", d=d, r=r));
out.push(format!(" {d}++++==+++++{t}*{d}+============{r}", d=d, t=t, r=r));
out.push(format!(" {d}+++=+++++++++++========{r}", d=d, r=r));
out.push(format!(" {d}+++++++++++++++++===={r}", d=d, r=r));
out.push(format!(" {d}+++++++++++++++++++{r}", d=d, r=r));
}
fn ascii_void(_c: &Colors, out: &mut Vec<String>) {
out.push(" -------".into());
out.push(" -----------".into());
out.push(" -----------".into());
out.push(" * -- ------".into());
out.push(" ** ----".into());
out.push(" **** - ----".into());
out.push(" @@****@@*++=-@@#+++@@".into());
out.push(" @@@**@@@%%+@@=@@@@+#@@".into());
out.push(" @@%%%%@@@=-@@#@@@%%-#@@".into());
out.push(" @@#* @@*%%@+@@@@%%#@@@".into());
out.push(" **** @*=--*@@%%++=".into());
out.push(" **** - ----".into());
out.push(" **** --".into());
out.push(" ****** * -".into());
out.push(" ***********".into());
out.push(" ***********".into());
out.push(" *******".into());
out.push(String::new());
}
fn ascii_star(_c: &Colors, out: &mut Vec<String>) {
out.push(format!("⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠"));
out.push(format!("⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣾⣿⡆"));
out.push(format!("⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠉⠓"));
out.push(format!("⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣿⣿⣷⣶⣦"));
out.push(format!("⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠙⠉⠉⠙⠻"));
out.push(format!("⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⣾⣿⣿⣿⣷⣲⣄"));
out.push(format!("⠀⠀⠀⠀⠀⡀⢀⣀⣠⣤⠄⠀⡠⣦⣶⣶⡶⠿⠛⠙⠉⠁⠉⠉⠙⠛⢧"));
out.push(format!("⠀⠐⢾⣾⠇⣰⣿⣿⡿⠃⣠⣼⣿⡿⠋⠁⣠⣶⣾⣿⣿⣿⣿⣿⣶⣄⠀⠂"));
out.push(format!("⠀⠀⠀⠉⠀⣿⣿⡟⠁⣰⣾⣿⡟⠁⣠⣾⡿⠋⢉⣁⣤⣤⣄⡉⠛⢿⣗⡄⠙⣦⡄"));
out.push(format!("⠀⠀⠀⠀⠀⠀⠙⠁⢠⣿⣿⡏⢀⣾⡿⠃⢀⡾⠟⠋⣉⡙⠻⢿⣧⡀⢛⣿⡄⢹⣿⡇⠠⣄⡀"));
out.push(format!("⠀⠀⠀⠀⠀⠀⠀⠀⠘⢿⣿⠇⢘⣿⠀⢰⡿⠁⣰⡟⢋⡙⢷⣆⠉⣷⠀⢽⡇⠈⣿⣿⠀⣿⣿⣦⠀⣀"));
out.push(format!("⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⣿⡆⢸⣿⡄⢸⣷⡀⢹⣇⠘⣻⠇⣿⡄⣿⡇⢸⣯⠀⣿⣿⠀⣹⣿⠯⠀⠹⠓"));
out.push(format!("⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⡇⠈⢿⣇⠀⢻⣷⣄⠉⠉⢁⣠⠏⢀⡿⠀⣿⡇⠐⠟⠋"));
out.push(format!("⠀⠀⠀⠀⠀⠀⠀⢠⡄⠸⣿⣿⣦⡀⠻⣷⣆⣈⠉⠙⠛⠛⢁⣠⡾⠃⣘⡛⠃"));
out.push(format!("⠀⠀⠀⠀⠀⠀⠀⣾⣿⡄⠉⠿⣿⣷⣄⠈⠙⠿⠿⠾⠷⠿⠛⠋⢀⣼⡟⠃⣴"));
out.push(format!("⠀⠀⠀⠀⠀⠀⠈⢟⣿⣿⣧⣄⡈⠛⢿⠟⠃⠀⠀⠀⢠⣤⣴⣶⣿⠟⠀⣺⡿"));
out.push(format!("⠀⠀⠀⠀⠀⢰⡧⠀⠙⢟⡿⠿⠃⠁⠀⠀⠀⠀⠀⠀⠀⠉⠛⠉⢁⣀⣾⠟⠁"));
out.push(format!("⠀⠀⠀⠀⠀⣼⣿⠇⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠰⢿⠟⠋⢀⡶"));
out.push(format!("⠀⠀⠀⠀⠔⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠤⣿⡏"));
out.push(format!("⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠃"));
out.push(String::new());
}
fn ascii_nixos(c: &Colors, out: &mut Vec<String>) {
let b = c.blue; let w = c.white; let r = c.reset_c;
out.push(format!(" {b}+++{r} {w}----- ---{r}", b=b, w=w, r=r));
out.push(format!(" {b}+++*{r} {w}----- ----{r}", b=b, w=w, r=r));
out.push(format!(" {b}++**{r} {w}--------{r}", b=b, w=w, r=r));
out.push(format!(" {b}+++++++********{r}{w}---==={r} {b}+{r}", b=b, w=w, r=r));
out.push(format!(" {b}++++++++*********{r}{w}-===={r} {b}+++{r}", b=b, w=w, r=r));
out.push(format!(" {w}====={r} {w}====={r} {b}+++++{r}", w=w, b=b, r=r));
out.push(format!(" {w}===={r} {w}===={r}{b}++++{r}", w=w, b=b, r=r));
out.push(format!(" {w}--=-======={r} {w}=={r}{b}**+++++++{r}", w=w, b=b, r=r));
out.push(format!("{w}-------===={r} {b}******+++++{r}", w=w, b=b, r=r));
out.push(format!(" {w}--------={r}{b}**{r} {b}**********+{r}", w=w, b=b, r=r));
out.push(format!(" {w}----{r}{b}****{r} {b}****{r}", w=w, b=b, r=r));
out.push(format!(" {w}----{r} {b}*****{r} {b}*****{r}", w=w, b=b, r=r));
out.push(format!(" {w}---{r} {b}****+{r}{w}========={r}{w}--------{r}", w=w, b=b, r=r));
out.push(format!(" {w}-{r} {b}*****+{r}{w}======={r}{w}--------{r}", w=w, b=b, r=r));
out.push(format!(" {b}**++++++{r} {w}=---{r}", b=b, w=w, r=r));
out.push(format!(" {b}++++ +++++{r} {w}----{r}", b=b, w=w, r=r));
out.push(format!(" {b}+++ +++++{r} {w}---{r}", b=b, w=w, r=r));
}
fn ascii_debian(c: &Colors, out: &mut Vec<String>) {
let rd = c.s_red; let r = c.reset_c;
out.push(format!(" {rd}##### #{r}", rd=rd, r=r));
out.push(format!(" {rd}##################{r}", rd=rd, r=r));
out.push(format!(" {rd}######## ########{r}", rd=rd, r=r));
out.push(format!(" {rd}##### #####{r}", rd=rd, r=r));
out.push(format!(" {rd}##### ######{r}", rd=rd, r=r));
out.push(format!(" {rd}#### ## #{r}", rd=rd, r=r));
out.push(format!(" {rd}### ##### ###{r}", rd=rd, r=r));
out.push(format!(" {rd}### # ##{r}", rd=rd, r=r));
out.push(format!(" {rd}### ## ###{r}", rd=rd, r=r));
out.push(format!(" {rd}## # ##{r}", rd=rd, r=r));
out.push(format!(" {rd}## # ##{r}", rd=rd, r=r));
out.push(format!(" {rd}## ## ###{r}", rd=rd, r=r));
out.push(format!(" {rd}### # ## ##{r}", rd=rd, r=r));
out.push(format!(" {rd}### # ## ###{r}", rd=rd, r=r));
out.push(format!(" {rd}## # ###{r}", rd=rd, r=r));
out.push(format!(" {rd}###{r}", rd=rd, r=r));
out.push(format!(" {rd}####{r}", rd=rd, r=r));
out.push(format!(" {rd}##{r}", rd=rd, r=r));
out.push(format!(" {rd}###{r}", rd=rd, r=r));
out.push(format!(" {rd}##{r}", rd=rd, r=r));
out.push(format!(" {rd}##{r}", rd=rd, r=r));
out.push(format!(" {rd}##{r}", rd=rd, r=r));
out.push(String::new());
}
fn ascii_gentoo(c: &Colors, out: &mut Vec<String>) {
let s = c.steelblue; let l = c.steelblue_lt; let d = c.s_blue; let r = c.reset_c;
out.push(format!(" {l}%%%%%%%{r}", l=l, r=r));
out.push(format!(" {l}%%{r}{s}##########{r}{l}%%%{r}", l=l, s=s, r=r));
out.push(format!(" {l}%{r}{s}################{r}{l}%%%{r}", l=l, s=s, r=r));
out.push(format!(" {l}%{r}{s}####################{r}{l}%%%{r}", l=l, s=s, r=r));
out.push(format!(" {l}%{r}{s}###############{r}{l}%%{r}{s}####{r}{l}%%%%%{r}", l=l, s=s, r=r));
out.push(format!(" {l}%{r}{s}#############{r}{l}%%%%%{r}{d}@{r}{s}###{r}{l}%%%%%{r}{s}##{r}", l=l, s=s, r=r, d=d));
out.push(format!(" {l}%%{r}{s}###########{r}{l}%%{r} {l}%%%{r}{d}@{r}{s}###{r}{l}%%%%%%%{r}{s}#{r}{l}%{r}", l=l, s=s, r=r, d=d));
out.push(format!(" {l}%%%%%{r}{s}###########{r}{l}%%{r}{s}####{r}{l}%%%%%%%%%{r}{s}##{r}", l=l, s=s, r=r));
out.push(format!(" {l}%%%%{r}{s}################{r}{l}%%%%%%%%%{r}{s}##{r}{l}%{r}", l=l, s=s, r=r));
out.push(format!(" {l}%%%%{r}{s}#############{r}{l}%%%%%%%%{r}{s}##{r}{l}%%{r}", l=l, s=s, r=r));
out.push(format!(" {s}##############{r}{l}%%%%%%%{r}{s}###{r}{l}%%{r}", s=s, l=l, r=r));
out.push(format!(" {s}################{r}{l}%%%%%{r}{s}###{r}{l}%%%{r}", s=s, l=l, r=r));
out.push(format!(" {s}#################{r}{l}%%%%{r}{s}###{r}{l}%%%{r}", s=s, l=l, r=r));
out.push(format!(" {s}##################{r}{l}%%%{r}{s}###{r}{l}%%%{r}", s=s, l=l, r=r));
out.push(format!(" {s}##################{r}{l}%{r}{s}####{r}{l}%%%{r}", s=s, l=l, r=r));
out.push(format!(" {l}%{r}{s}####################{r}{l}%%%%{r}", l=l, s=s, r=r));
out.push(format!(" {l}%{r}{s}################{r}{l}%%%%%{r}", l=l, s=s, r=r));
out.push(format!(" {l}%{r}{s}###########{r}{l}%%%%%%{r}", l=l, s=s, r=r));
out.push(format!(" {l}%%%%%%%%{r}{d}@@@@@@{r}", l=l, d=d, r=r));
out.push(format!(" {d}@@@@@@{r}", d=d, r=r));
}
fn ascii_opensuse(c: &Colors, out: &mut Vec<String>) {
let g = c.green; let sg = c.s_green; let r = c.reset_c;
out.push(String::new());
out.push(format!(" {g}#########{r}", g=g, r=r));
out.push(format!(" {g}#####{r}{sg}*-----*{r}{g}#####{r}", g=g, sg=sg, r=r));
out.push(format!(" {g}####{r}{sg}---------------{r}{g}####{r}", g=g, sg=sg, r=r));
out.push(format!(" {g}###{r}{sg}-------------------{r}{g}###{r}", g=g, sg=sg, r=r));
out.push(format!(" {g}#####{r}{sg}--{r}{g}########{r}{sg}-----------{r}{g}###{r}", g=g, sg=sg, r=r));
out.push(format!(" {g}####################{r}{sg}+------{r}{g}##{r}", g=g, sg=sg, r=r));
out.push(format!(" {g}###############{r}{sg}+--{r}{g}#{r}{sg}=-={r}{g}#{r}{sg}*-----{r}{g}##{r}", g=g, sg=sg, r=r));
out.push(format!(" {g}###############{r}{sg}+-{r}{g}##{r}{sg}--{r}{g}#{r}{sg}-={r}{g}#{r}{sg}=----{r}{g}###{r}", g=g, sg=sg, r=r));
out.push(format!(" {g}###############{r}{sg}=-{r}{g}#####{r}{sg}--{r}{g}##{r}{sg}-----{r}{g}##{r}", g=g, sg=sg, r=r));
out.push(format!(" {g}###########{r}{sg}-{r}{g}####{r}{sg}---{r}{g}#{r}{sg}=--{r}{g}####{r}{sg}----{r}{g}##{r}", g=g, sg=sg, r=r));
out.push(format!(" {g}############{r}{sg}----{r}{g}#########{r}{sg}+----{r}{g}###{r}", g=g, sg=sg, r=r));
out.push(format!(" {g}###############{r}{sg}=--------{r}{g}#{r}{sg}+---{r}{g}##{r}", g=g, sg=sg, r=r));
out.push(format!(" {g}#######################{r}{sg}=---{r}{g}##{r}", g=g, sg=sg, r=r));
out.push(format!(" {g}###################{r}{sg}-------{r}{g}###{r}", g=g, sg=sg, r=r));
out.push(format!(" {g}###{r}{sg}-------------------{r}{g}###{r}", g=g, sg=sg, r=r));
out.push(format!(" {g}####{r}{sg}---------------{r}{g}####{r}", g=g, sg=sg, r=r));
out.push(format!(" {g}#####{r}{sg}*-----*{r}{g}#####{r}", g=g, sg=sg, r=r));
out.push(format!(" {g}#########{r}", g=g, r=r));
out.push(String::new());
}
fn ascii_fedora(c: &Colors, out: &mut Vec<String>) {
let b = c.blue; let r = c.reset_c;
out.push(format!(" {b}###########{r}", b=b, r=r));
out.push(format!(" {b}###################{r}", b=b, r=r));
out.push(format!(" {b}################....#####{r}", b=b, r=r));
out.push(format!(" {b}##############..........**###{r}", b=b, r=r));
out.push(format!(" {b}##############..........****###{r}", b=b, r=r));
out.push(format!(" {b}##############....########****###{r}", b=b, r=r));
out.push(format!(" {b}##############....*########*****###{r}", b=b, r=r));
out.push(format!(" {b}##############....#########*****###{r}", b=b, r=r));
out.push(format!("{b}###############....#########****#####{r}", b=b, r=r));
out.push(format!("{b}###############.....#####******######{r}", b=b, r=r));
out.push(format!("{b}#####*****...............*****#######{r}", b=b, r=r));
out.push(format!("{b}####******..............***##########{r}", b=b, r=r));
out.push(format!("{b}###****########....#################{r}", b=b, r=r));
out.push(format!("{b}##****#########....#################{r}", b=b, r=r));
out.push(format!("{b}##****#########....################{r}", b=b, r=r));
out.push(format!("{b}##*****########....###############{r}", b=b, r=r));
out.push(format!("{b}###******###+.....###############{r}", b=b, r=r));
out.push(format!("{b}####***..........##############{r}", b=b, r=r));
out.push(format!("{b}#######*......##############{r}", b=b, r=r));
out.push(format!(" {b}######################{r}", b=b, r=r));
out.push(String::new());
}
fn ascii_alpine(c: &Colors, out: &mut Vec<String>) {
let b = c.s_blue; let r = c.reset_c;
out.push(format!(" {b} .hdddddddddddddddddddddddh.{r}", b=b, r=r));
out.push(format!(" {b} .dddddddddddddddddddddddddddd.{r}", b=b, r=r));
out.push(format!(" {b} .dddddddddddddddddddddddddddddd.{r}", b=b, r=r));
out.push(format!(" {b} .dddddddddddddddddddddddddddddddd.{r}", b=b, r=r));
out.push(format!(" {b} .dddddddddddddddddddddddddddddddddd.{r}", b=b, r=r));
out.push(format!(" {b} .dddddddddddddddddddddddddddddddddddd.{r}", b=b, r=r));
out.push(format!(" {b} .ddddddddddddddddddddddddddddddddddddd.{r}", b=b, r=r));
out.push(format!(" {b} .dddddddddddddddddddddddddddddddddddddd.{r}", b=b, r=r));
out.push(format!("{b} .dddddddddddddddddddddddddddddddddddddddd.{r}", b=b, r=r));
out.push(format!("{b} .dddddddddddddddddddddddddddddddddddddddd.{r}", b=b, r=r));
out.push(format!("{b} .dddddddddddddddddddddddddddddddddddddddd.{r}", b=b, r=r));
out.push(format!("{b} .dddddddhs++++++++++++++++++syddddddddddd.{r}", b=b, r=r));
out.push(format!("{b} .dddddd/ :dddddddddd.{r}", b=b, r=r));
out.push(format!("{b} .dddddd/ :dddddddddd.{r}", b=b, r=r));
out.push(format!("{b} .dddddd/ :dddddddddd.{r}", b=b, r=r));
out.push(format!("{b} .dddddd/ :dddddddddd.{r}", b=b, r=r));
out.push(format!("{b} .dddddd/ :dddddddddd.{r}", b=b, r=r));
out.push(format!("{b} 'yyyyy+' :yyyyyyyyyy.{r}", b=b, r=r));
out.push(format!("{b} .{r}", b=b, r=r));
}
fn ascii_linux(c: &Colors, out: &mut Vec<String>) {
let b = c.s_black; let r = c.reset_c;
out.push(format!(" {b}a8888b.{r}", b=b, r=r));
out.push(format!(" {b}d888888b.{r}", b=b, r=r));
out.push(format!(" {b}8P{r}\"{b}YP{r}\"{b}Y88{r}", b=b, r=r));
out.push(" 8|o||o|88".into());
out.push(" 8' .88".into());
out.push(" 8'._.' Y8.".into());
out.push(" d/ '8b.".into());
out.push(" .dP . Y8b.".into());
out.push(" d8:' \" '::88b.".into());
out.push(" d8\" 'Y88b".into());
out.push(" :8P ' :888".into());
out.push(" 8a. : _a88P".into());
out.push(" ._/\"Yaa_ : .| 88P|".into());
out.push(" \\ YP\" '| 8P '.".into());
out.push(" / \\._____.d| .'".into());
out.push(" '--..__)888888P'._.'".into());
}
fn ascii_macos(c: &Colors, out: &mut Vec<String>) {
let w = c.white; let r = c.reset_c;
out.push(format!("{w} ..'{r}", w=w, r=r));
out.push(format!("{w} ,xNMM.{r}", w=w, r=r));
out.push(format!("{w} .OMMMMo{r}", w=w, r=r));
out.push(format!("{w} lMM\"{r}", w=w, r=r));
out.push(format!("{w} .;loddo:. .olloddol;.{r}", w=w, r=r));
out.push(format!("{w} cKMMMMMMMMMMNWMMMMMMMMMM0:{r}", w=w, r=r));
out.push(format!("{w} .KMMMMMMMMMMMMMMMMMMMMMMMWd.{r}", w=w, r=r));
out.push(format!("{w} XMMMMMMMMMMMMMMMMMMMMMMMX.{r}", w=w, r=r));
out.push(format!("{w};MMMMMMMMMMMMMMMMMMMMMMMM:{r}", w=w, r=r));
out.push(format!("{w}:MMMMMMMMMMMMMMMMMMMMMMMM:{r}", w=w, r=r));
out.push(format!("{w}.MMMMMMMMMMMMMMMMMMMMMMMMX.{r}", w=w, r=r));
out.push(format!("{w} kMMMMMMMMMMMMMMMMMMMMMMMMWd.{r}", w=w, r=r));
out.push(format!("{w} 'XMMMMMMMMMMMMMMMMMMMMMMMMMMk{r}", w=w, r=r));
out.push(format!("{w} 'XMMMMMMMMMMMMMMMMMMMMMMMMK.{r}", w=w, r=r));
out.push(format!("{w} kMMMMMMMMMMMMMMMMMMMMMMd{r}", w=w, r=r));
out.push(format!("{w} ;KMMMMMMMWXXWMMMMMMMk.{r}", w=w, r=r));
out.push(format!("{w} \"cooc*\" \"*coo'{r}", w=w, r=r));
}
fn ascii_sparky(c: &Colors, out: &mut Vec<String>) {
let rd = c.s_red; let r = c.reset_c;
out.push(format!(" {rd}+ ###{r}", rd=rd, r=r));
out.push(format!(" {rd}++ #####{r}", rd=rd, r=r));
out.push(format!(" {rd}+++ **** ####{r}", rd=rd, r=r));
out.push(format!(" {rd}+++ **** #################{r}", rd=rd, r=r));
out.push(format!(" {rd}++= +++* *****{r}", rd=rd, r=r));
out.push(format!(" {rd}+=== +++ ++ ......:::::{r}", rd=rd, r=r));
out.push(format!(" {rd}** +=== ++ ++ .. :----={r}", rd=rd, r=r));
out.push(format!(" {rd}++++ === == == . ..::::-- ==++{r}", rd=rd, r=r));
out.push(format!(" {rd}===== === == -.: --== +*{r}", rd=rd, r=r));
out.push(format!(" {rd}----- ---= - ::: ==== *#{r}", rd=rd, r=r));
out.push(format!(" {rd}-::::: --: : -:: =++* #{r}", rd=rd, r=r));
out.push(format!(" {rd}:::........ -==---- +++*{r}", rd=rd, r=r));
out.push(format!(" {rd}+++++ ==== +**{r}", rd=rd, r=r));
out.push(format!(" {rd}############****** +++++ **#{r}", rd=rd, r=r));
out.push(format!(" {rd}###### ***+* #{r}", rd=rd, r=r));
out.push(format!(" {rd}****{r}", rd=rd, r=r));
out.push(format!(" {rd}###{r}", rd=rd, r=r));
}
fn ascii_bedrock(c: &Colors, out: &mut Vec<String>) {
let b = c.s_black; let r = c.reset_c;
out.push(format!(" "));
out.push(format!(r" {b}\\\\\\\\\\\\{r}", b=b, r=r));
out.push(format!(r" {b}\\\{r} {b}\\\{r}", b=b, r=r));
out.push(format!(r" {b}\\\{r} {b}\\\{r}", b=b, r=r));
out.push(format!(r" {b}\\\{r} {b}\\\\\\\\\\\\\\\\\{r}", b=b, r=r));
out.push(format!(r" {b}\\\{r} {b}\\\{r}", b=b, r=r));
out.push(format!(r" {b}\\\{r} {b}\\\{r}", b=b, r=r));
out.push(format!(r" {b}\\\{r} ______ {b}\\\{r}", b=b, r=r));
out.push(format!(r" {b}\\\{r} ///", b=b, r=r));
out.push(format!(r" {b}\\\{r} ///", b=b, r=r));
out.push(format!(r" {b}\\\{r} ///", b=b, r=r));
out.push(format!(r" {b}\\\////////////////{r}", b=b, r=r));
}
fn choose_ascii(distro_id: &str, distro_base: &str, is_proxmox: bool, is_android: bool, is_macos: bool) -> AsciiFn {
if is_proxmox { return ascii_proxmox; }
if is_android { return ascii_android; }
if is_macos { return ascii_macos; }
match distro_id {
"arch" | "endeavouros" | "manjaro" | "garuda" => return ascii_arch,
"artix" => return ascii_artix,
"bedrock" => return ascii_bedrock,
"sparky" => return ascii_sparky,
"fedora" => return ascii_fedora,
"ubuntu" => return ascii_ubuntu,
"linuxmint" => return ascii_mint,
"pop" => return ascii_pop,
"cachyos" => return ascii_cachyos,
"void" => return ascii_void,
"nixos" => return ascii_nixos,
"debian" => return ascii_debian,
"gentoo" => return ascii_gentoo,
"alpine" => return ascii_alpine,
_ => {}
}
if distro_id.contains("opensuse") { return ascii_opensuse; }
if distro_base.contains("suse") || distro_base.contains("opensuse") { return ascii_opensuse; }
if distro_base.contains("arch") { return ascii_arch; }
if distro_base.contains("debian") || distro_base.contains("ubuntu") { return ascii_debian; }
ascii_linux
}
fn match_ascii(name: &str) -> AsciiFn {
match name {
"proxmox" => ascii_proxmox,
"android" => ascii_android,
"macos" => ascii_macos,
"arch" | "endeavouros" | "manjaro" | "garuda" => ascii_arch,
"artix" => ascii_artix,
"bedrock" => ascii_bedrock,
"sparky" => ascii_sparky,
"fedora" => ascii_fedora,
"ubuntu" => ascii_ubuntu,
"mint" | "linuxmint" => ascii_mint,
"pop" => ascii_pop,
"cachyos" => ascii_cachyos,
"void" => ascii_void,
"nixos" => ascii_nixos,
"debian" => ascii_debian,
"gentoo" => ascii_gentoo,
"opensuse" | "suse" => ascii_opensuse,
"alpine" => ascii_alpine,
"star" => ascii_star,
_ => ascii_linux,
}
}
fn build_info(
info: &SysInfo, colors: &Colors, label_color: &str,
overrides: &HashMap<String, String>, spacing: &str,
) -> Vec<String> {
let lc = label_color;
let rc = colors.reset_c;
let ic = colors.info_color;
let aligned = spacing == "aligned";
let mut entries: Vec<(&'static str, String)> = Vec::new();
macro_rules! push_entry {
($label:expr, $val:expr) => {{
let val = $val;
let display = overrides.get($label).map(|s| s.as_str()).unwrap_or(&val);
if display != "false" && !display.is_empty() {
entries.push(($label, display.to_owned()));
}
}};
}
push_entry!("Hostname", &info.hostname);
push_entry!("OS", &info.os);
push_entry!("Kernel", &info.kernel);
push_entry!("Device", &info.device);
push_entry!("Uptime", &info.uptime);
push_entry!("Locale", &info.locale);
if !info.packages.is_empty() {
push_entry!("Packages", &info.packages);
}
push_entry!("Shell", &info.shell);
push_entry!("Init", &info.init);
if !info.de.is_empty() {
push_entry!("DE", &info.de);
} else if !info.wm.is_empty() {
push_entry!("WM", &info.wm);
}
push_entry!("Terminal", &info.terminal);
push_entry!("Resolution", &info.disp1);
if !info.disp2.is_empty() {
push_entry!("Resolution-2", &info.disp2);
}
push_entry!("CPU", &info.cpu);
push_entry!("GPU", &info.gpu);
if !info.gpu2.is_empty() {
push_entry!("GPU-2", &info.gpu2);
}
push_entry!("RAM", &info.ram);
if !info.local_ip.is_empty() {
push_entry!("Local IP", &info.local_ip);
}
if !info.disk.is_empty() {
push_entry!("Disk (/)", &info.disk);
}
if !info.virt.is_empty() {
push_entry!("Virtualization", &info.virt);
}
if !info.container.is_empty() {
push_entry!("Container", &info.container);
}
let max_label_len = if aligned {
entries.iter().map(|(l, _)| l.len()).max().unwrap_or(0)
} else {
0
};
entries.into_iter().map(|(label, display)| {
if aligned {
let pad = " ".repeat(max_label_len - label.len());
format!("{lc}{label}:{pad}{rc} {ic}{display}{rc}")
} else {
format!("{lc}{label}:{rc} {ic}{display}{rc}")
}
}).collect()
}
struct SysInfo {
os: String,
hostname: String,
local_ip: String,
kernel: String,
device: String,
uptime: String,
locale: String,
shell: String,
terminal: String,
init: String,
de: String,
wm: String,
disp1: String,
disp2: String,
cpu: String,
gpu: String,
gpu2: String,
ram: String,
packages: String,
disk: String,
virt: String,
container: String,
distro_id: String,
distro_base: String,
is_proxmox: bool,
is_android: bool,
is_macos: bool,
}
fn vis_len(s: &str) -> usize {
let mut len = 0;
let mut esc = false;
for b in s.bytes() {
if b == 0x1b { esc = true; continue; }
if esc {
if b == b'm' { esc = false; }
continue;
}
if b < 0x80 || b & 0xC0 != 0x80 {
len += 1;
}
}
len
}
fn get_hostname() -> String {
if let Ok(h) = read_first_line("/proc/sys/kernel/hostname") {
if !h.is_empty() { return h; }
}
unsafe {
let mut buf = [0 as c_char; 256];
if gethostname(buf.as_mut_ptr(), buf.len()) == 0 {
let full = CStr::from_ptr(buf.as_ptr()).to_string_lossy();
return full.split('.').next().unwrap_or(&full).to_owned();
}
}
String::new()
}
fn config_path() -> PathBuf {
let base = env::var("XDG_CONFIG_HOME")
.map(PathBuf::from)
.unwrap_or_else(|_| {
let home = env::var("HOME").unwrap_or_else(|_| "/tmp".into());
Path::new(&home).join(".config")
});
base.join("bfetch").join("config")
}
fn load_config() -> HashMap<String, String> {
let path = config_path();
let content = match fs::read_to_string(&path) {
Ok(c) => c,
Err(_) => return HashMap::new(),
};
let mut map = HashMap::new();
for line in content.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') { continue; }
if let Some(eq) = line.find('=') {
let key = line[..eq].trim().to_string();
let val = line[eq+1..].trim().to_string();
map.insert(key, val);
}
}
map
}
fn save_config(map: &HashMap<String, String>) -> io::Result<()> {
let path = config_path();
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
let mut content = String::new();
let mut keys: Vec<&String> = map.keys().collect();
keys.sort();
for key in keys {
if let Some(val) = map.get(key) {
let _ = writeln!(content, "{}={}", key, val);
}
}
fs::write(&path, content)
}
const VALID_LABELS: &[&str] = &[
"Hostname", "Local IP", "OS", "Kernel", "Device", "Uptime", "Locale",
"Packages", "Shell", "Init", "DE", "WM", "Terminal",
"Resolution", "Resolution-2", "CPU", "GPU", "GPU-2", "RAM",
"Disk (/)", "Virtualization", "Container",
];
fn handle_set(arg: &str) {
if arg == "reset" {
let path = config_path();
if !path.exists() {
eprintln!("No config to reset.");
return;
}
eprint!("Reset all settings? [y/N] ");
io::stderr().flush().ok();
let mut input = String::new();
io::stdin().read_line(&mut input).ok();
if input.trim().eq_ignore_ascii_case("y") || input.trim().eq_ignore_ascii_case("yes") {
let _ = fs::remove_file(&path);
println!("Config reset.");
} else {
println!("Aborted.");
}
return;
}
if arg == "show" {
let config = load_config();
if config.is_empty() {
println!("No config file found at {}", config_path().display());
return;
}
println!("Config file: {}", config_path().display());
println!();
let mut keys: Vec<&String> = config.keys().collect();
keys.sort();
for key in keys {
if let Some(val) = config.get(key) {
println!("{} = {}", key, val);
}
}
return;
}
if let Some(colon) = arg.find(':') {
let key = &arg[..colon];
let val = &arg[colon+1..];
if key != "default" && key != "ascii" && key != "spacing" && key != "labelcolor" && !VALID_LABELS.contains(&key) {
eprintln!("Unknown label: {}. Valid: default, ascii, spacing, labelcolor, {}", key, VALID_LABELS.join(", "));
return;
}
let mut config = load_config();
if val.is_empty() {
config.remove(key);
} else {
config.insert(key.to_string(), val.to_string());
}
match save_config(&config) {
Ok(()) => println!("Set {} = {}", key, if val.is_empty() { "(removed)" } else { val }),
Err(e) => eprintln!("Error saving config: {}", e),
}
} else {
eprintln!("Invalid --set format. Use key:value, show, or reset.");
}
}
fn main() {
#[cfg(unix)]
reset_sigpipe();
let args: Vec<String> = env::args().collect();
for i in 1..args.len() {
if args[i] == "--set" && i + 1 < args.len() {
let set_arg = &args[i + 1];
if let Some(colon) = set_arg.find(':') {
if colon + 1 == set_arg.len() && i + 2 < args.len() && !args[i + 2].starts_with('-') {
eprintln!("Error: value after '{}:' appears empty. Use quotes around values with spaces!", &set_arg[..colon]);
eprintln!(" Correct: bfetch --set \"{}:<value>\"", &set_arg[..colon]);
return;
}
}
handle_set(set_arg);
return;
}
}
let mut show_help = false;
let mut show_version = false;
for arg in &args[1..] {
if arg == "--help" || arg == "-h" { show_help = true; }
if arg == "--version" || arg == "-v" { show_version = true; }
}
if show_version {
println!("bfetch {}", env!("CARGO_PKG_VERSION"));
return;
}
if show_help {
println!("Usage: bfetch [OPTIONS]");
println!();
println!("Options:");
println!(" --help, -h Show this help message");
println!(" --version, -v Print version");
println!(" --grey Monochrome (no color) output");
println!(" --json Machine-readable JSON output");
println!(" --no-box Hide the border box around info");
println!(" --set <key:val> Set persistent config (e.g. --set Kernel:false, --set default:--no-box)
--set show Show current config and path");
println!(" --color:<hex> Custom accent color (e.g. --color:5f5f5f)");
println!(" --label-color:<hex> Custom label color (e.g. --label-color:848484)");
println!(" --ascii:<name> Force specific ASCII art (e.g. --ascii:sparky, --ascii:debian)");
println!(" --debug:<os> Simulate OS detection (e.g. --debug:macos, --debug:arch)");
return;
}
let config = load_config();
let mut grey = false;
let mut json = false;
let mut color_hex: Option<String> = None;
let mut label_hex: Option<String> = None;
let mut debug_os: Option<String> = None;
let mut ascii_override: Option<String> = None;
let mut no_box = false;
if let Some(defaults) = config.get("default") {
for flag in defaults.split_whitespace() {
if flag == "--grey" { grey = true; }
if flag == "--json" { json = true; }
if flag == "--no-box" { no_box = true; }
if let Some(hex) = flag.strip_prefix("--color:") { color_hex = Some(hex.to_string()); }
if let Some(hex) = flag.strip_prefix("--label-color:") { label_hex = Some(hex.to_string()); }
if let Some(val) = flag.strip_prefix("--debug:") { debug_os = Some(val.to_string()); }
if let Some(val) = flag.strip_prefix("--ascii:") { ascii_override = Some(val.to_string()); }
}
}
for arg in &args[1..] {
if arg == "--grey" { grey = true; }
if arg == "--json" { json = true; }
if let Some(hex) = arg.strip_prefix("--color:") { color_hex = Some(hex.to_string()); }
if let Some(hex) = arg.strip_prefix("--label-color:") { label_hex = Some(hex.to_string()); }
if let Some(val) = arg.strip_prefix("--debug:") { debug_os = Some(val.to_string()); }
if let Some(val) = arg.strip_prefix("--ascii:") { ascii_override = Some(val.to_string()); }
if arg == "--no-box" { no_box = true; }
}
let spacing: &str = &config.get("spacing").cloned().unwrap_or_default();
let overrides: HashMap<String, String> = config.iter()
.filter(|(k, _)| *k != "default" && *k != "ascii" && *k != "spacing" && *k != "labelcolor")
.map(|(k, v)| (k.clone(), v.clone()))
.collect();
if label_hex.is_none() {
if let Some(lc) = config.get("labelcolor") {
if !lc.is_empty() { label_hex = Some(lc.clone()); }
}
}
let colors = if grey {
Colors::grey()
} else if let Some(ref hex) = color_hex {
Colors::custom(hex).unwrap_or_else(Colors::default)
} else {
Colors::default()
};
let distro_id;
let distro_base;
let is_proxmox;
let is_android;
let is_macos;
let os_display;
if let Some(ref dbg) = debug_os {
is_proxmox = dbg == "proxmox";
is_android = dbg == "android";
is_macos = dbg == "macos";
distro_id = if is_proxmox || is_android || is_macos {
String::new()
} else {
dbg.to_owned()
};
distro_base = String::new();
os_display = if is_proxmox {
"Proxmox VE (debug)".into()
} else if is_android {
"Android (Termux, debug)".into()
} else if is_macos {
"macOS (debug)".into()
} else {
format!("{} (debug)", dbg)
};
} else {
let (id, base, name, ver) = os_release_fields();
distro_id = id;
distro_base = base;
let pv = detect_proxmox();
let mv = detect_macos();
is_proxmox = pv.is_some();
is_android = detect_android();
is_macos = mv.is_some();
os_display = if is_proxmox {
pv.unwrap()
} else if is_android {
"Android (Termux)".into()
} else if is_macos {
format!("macOS {}", mv.as_ref().unwrap())
} else {
let n = if name.is_empty() { "Linux".into() } else { name };
if ver.is_empty() { n } else { format!("{} {}", n, ver) }
};
}
let kernel = if is_macos {
#[cfg(target_os = "macos")]
{
unsafe {
let mut buf: libc_utsname = std::mem::zeroed();
if uname(&mut buf) == 0 {
let rel = CStr::from_ptr(buf.release.as_ptr()).to_string_lossy();
if !rel.is_empty() {
format!("Darwin {}", rel)
} else { String::new() }
} else { String::new() }
}
}
#[cfg(not(target_os = "macos"))]
{
let ver = cmd_output("uname", &["-r"]);
if ver.is_empty() { String::new() } else { format!("Darwin {}", ver) }
}
} else {
let mut k = get_uname_release();
if !k.is_empty() { k = format!("Linux {}", k); }
k
};
let mac_cache: Option<MacCache> = if is_macos { Some(fetch_macos_cache()) } else { None };
let mc = mac_cache.as_ref();
let cpuinfo = read_file("/proc/cpuinfo").unwrap_or_default();
let (de, wm) = get_wm_de();
let (disp1, disp2) = get_display_resolutions(None);
let (gpu1, gpu2) = get_gpu(None);
let arch_based = !is_macos && (matches!(distro_id.as_str(),
"arch" | "endeavouros" | "manjaro" | "garuda" | "artix" | "cachyos")
|| distro_base.contains("arch"));
let deb_based = !is_macos && !arch_based && (matches!(distro_id.as_str(),
"debian" | "ubuntu" | "linuxmint" | "pop")
|| distro_base.contains("debian"));
let device_name = get_device_name(mc);
let info = SysInfo {
os: os_display,
hostname: get_hostname(),
local_ip: get_local_ip(),
kernel,
device: device_name.clone(),
uptime: get_uptime(mc),
locale: get_locale(),
shell: get_shell(),
terminal: get_terminal(),
init: if distro_id == "bedrock" { get_bedrock_inits() } else { get_init() },
de,
wm,
disp1,
disp2,
cpu: get_cpu(&cpuinfo, mc),
gpu: gpu1,
gpu2,
ram: get_ram(mc),
packages: if distro_id == "bedrock" { get_bedrock_packages() } else { get_packages(arch_based, deb_based) },
disk: get_disk_usage(),
virt: detect_virt(&cpuinfo, &device_name, mc),
container: detect_container().unwrap_or("").to_string(),
distro_id,
distro_base,
is_proxmox,
is_android,
is_macos,
};
if json {
print_json(&info);
return;
}
let label_color = choose_label_color(
&info.distro_id, &info.distro_base, &colors, info.is_proxmox, info.is_android, info.is_macos
);
let final_label_color = if grey {
""
} else if let Some(ref hex) = label_hex {
ansi_rgb(hex, true).unwrap_or_else(|| {
if color_hex.is_some() && !colors.label_color.is_empty() {
colors.label_color
} else {
label_color
}
})
} else if color_hex.is_some() && !colors.label_color.is_empty() {
colors.label_color
} else {
label_color
};
let mut ascii_lines: Vec<String> = Vec::new();
let ascii_fn = if let Some(ref name) = ascii_override {
match_ascii(name)
} else {
match overrides.get("ascii").map(|s| s.as_str()) {
Some(name) if !name.is_empty() => match_ascii(name),
_ => choose_ascii(&info.distro_id, &info.distro_base, info.is_proxmox, info.is_android, info.is_macos),
}
};
ascii_fn(&colors, &mut ascii_lines);
let n_ascii = ascii_lines.len();
let left_width = ascii_lines.iter().map(|l| vis_len(l)).max().unwrap_or(0) + 3;
let info_lines = build_info(&info, &colors, final_label_color, &overrides, spacing);
let n_info = info_lines.len();
let max_vlen = info_lines.iter().map(|l| vis_len(l)).max().unwrap_or(0);
let box_w = max_vlen;
let inner = box_w + 2;
let border: String = std::iter::repeat('\u{2500}').take(inner).collect();
let ic = colors.info_color;
let rst = if ic.is_empty() { "" } else { colors.reset_c };
let box_h = n_info + 2;
let rows = if n_ascii > box_h { n_ascii } else { box_h };
for i in 0..rows {
let left = ascii_lines.get(i).map(|s| s.as_str()).unwrap_or("");
let mut right = String::new();
if i < box_h {
if no_box {
if i > 0 && i < box_h - 1 {
right = info_lines[i - 1].clone();
}
} else if i == 0 {
let _ = write!(right, "{ic}\u{256d}{border}\u{256e}{rst}", ic=ic, rst=rst);
} else if i == box_h - 1 {
let _ = write!(right, "{ic}\u{2570}{border}\u{256f}{rst}", ic=ic, rst=rst);
} else {
let content = &info_lines[i - 1];
let cvlen = vis_len(content);
let spaces = box_w.saturating_sub(cvlen);
let _ = write!(right, "{ic}\u{2502}{rst} {content}{spc} {ic}\u{2502}{rst}",
ic=ic, rst=rst, content=content,
spc = " ".repeat(spaces));
}
}
let left_vlen = vis_len(left);
let pad = left_width.saturating_sub(left_vlen);
println!("{}{:pad$}{}", left, "", right, pad=pad);
}
println!();
}
fn escape_json(s: &str) -> String {
let mut out = String::with_capacity(s.len() + 2);
out.push('"');
for c in s.chars() {
match c {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
c if c.is_control() => {
let _ = write!(out, "\\u{:04x}", c as u32);
}
c => out.push(c),
}
}
out.push('"');
out
}
fn print_json(info: &SysInfo) {
let mut j = String::new();
j.push('{');
j.push_str(&format!("\"os\":{},", escape_json(&info.os)));
j.push_str(&format!("\"hostname\":{},", escape_json(&info.hostname)));
j.push_str(&format!("\"local_ip\":{},", escape_json(&info.local_ip)));
j.push_str(&format!("\"kernel\":{},", escape_json(&info.kernel)));
j.push_str(&format!("\"device\":{},", escape_json(&info.device)));
j.push_str(&format!("\"uptime\":{},", escape_json(&info.uptime)));
j.push_str(&format!("\"locale\":{},", escape_json(&info.locale)));
j.push_str(&format!("\"packages\":{},", escape_json(&info.packages)));
j.push_str(&format!("\"shell\":{},", escape_json(&info.shell)));
j.push_str(&format!("\"terminal\":{},", escape_json(&info.terminal)));
j.push_str(&format!("\"init\":{},", escape_json(&info.init)));
j.push_str(&format!("\"de\":{},", escape_json(&info.de)));
j.push_str(&format!("\"wm\":{},", escape_json(&info.wm)));
j.push_str(&format!("\"resolution\":{},", escape_json(&info.disp1)));
j.push_str(&format!("\"resolution_2\":{},", escape_json(&info.disp2)));
j.push_str(&format!("\"cpu\":{},", escape_json(&info.cpu)));
j.push_str(&format!("\"gpu\":{},", escape_json(&info.gpu)));
j.push_str(&format!("\"gpu_2\":{},", escape_json(&info.gpu2)));
j.push_str(&format!("\"ram\":{},", escape_json(&info.ram)));
j.push_str(&format!("\"disk\":{},", escape_json(&info.disk)));
j.push_str(&format!("\"virtualization\":{},", escape_json(&info.virt)));
j.push_str(&format!("\"container\":{}", escape_json(&info.container)));
j.push('}');
println!("{}", j);
}