use sysinfo::System;
pub(crate) fn detect_terminal(sys: &System) -> Option<String> {
if let Ok(prog) = std::env::var("TERM_PROGRAM") {
if !prog.is_empty() {
return Some(prog);
}
}
if let Ok(prog) = std::env::var("TERMINAL_EMULATOR") {
if !prog.is_empty() {
return Some(prog);
}
}
if std::env::var("ALACRITTY_LOG").is_ok() || std::env::var("ALACRITTY_WINDOW_ID").is_ok() {
return Some("alacritty".to_string());
}
let current_pid = sysinfo::Pid::from_u32(std::process::id());
let mut current_proc = sys.process(current_pid);
let known_terms = [
"kitty",
"alacritty",
"wezterm",
"gnome-terminal",
"konsole",
"iterm2",
"Terminal",
"rio",
"foot",
"tilix",
"xfce4-terminal",
"terminator",
"st",
"urxvt",
"ptyxis",
];
let mut depth = 0;
while let Some(proc) = current_proc {
if depth > 5 {
break;
}
let name = proc.name().to_string_lossy().to_lowercase();
for term in &known_terms {
if name == *term || name.ends_with(term) || (name.contains(term) && term.len() > 3) {
return Some(term.to_string());
}
}
if let Some(parent_pid) = proc.parent() {
current_proc = sys.process(parent_pid);
} else {
break;
}
depth += 1;
}
if let Ok(term) = std::env::var("TERM") {
if term != "xterm-256color" && term != "xterm" && term != "linux" && term != "cygwin" {
if let Some(stripped) = term.strip_prefix("xterm-") {
return Some(stripped.to_string());
}
return Some(term);
}
}
None
}
pub(crate) fn detect_terminal_font(terminal: Option<&str>) -> Option<String> {
let term = terminal?;
let term_lower = term.to_lowercase();
let home = dirs::home_dir()?;
if term_lower.contains("kitty") {
let conf_path = home.join(".config/kitty/kitty.conf");
if let Ok(content) = std::fs::read_to_string(&conf_path) {
let mut family = None;
let mut size = None;
for line in content.lines() {
let line = line.trim();
if line.starts_with("font_family") {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 2 {
family = Some(parts[1..].join(" "));
}
} else if line.starts_with("font_size") {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 2 {
size = Some(parts[1].to_string());
}
}
}
match (family, size) {
(Some(f), Some(s)) => return Some(format!("{} ({})", f, s)),
(Some(f), None) => return Some(f),
(None, Some(s)) => {
let fallback = crate::theme::get_default_monospace_font()
.unwrap_or_else(|| "Default".to_string());
return Some(format!("{} ({})", fallback, s));
}
(None, None) => {}
}
}
} else if term_lower.contains("alacritty") {
let paths = [
home.join(".config/alacritty/alacritty.toml"),
home.join(".config/alacritty/alacritty.yml"),
home.join(".alacritty.toml"),
home.join(".alacritty.yml"),
];
for path in paths {
if let Ok(content) = std::fs::read_to_string(&path) {
let mut family = None;
let mut size = None;
for line in content.lines() {
let line = line.trim();
if line.starts_with("family") {
if let Some(idx) = line.find('=') {
let val = line[idx + 1..].trim().trim_matches('"').trim_matches('\'');
family = Some(val.to_string());
} else if let Some(idx) = line.find(':') {
let val = line[idx + 1..].trim().trim_matches('"').trim_matches('\'');
family = Some(val.to_string());
}
} else if line.starts_with("size") {
if let Some(idx) = line.find('=') {
size = Some(line[idx + 1..].trim().to_string());
} else if let Some(idx) = line.find(':') {
size = Some(line[idx + 1..].trim().to_string());
}
}
}
match (family, size) {
(Some(f), Some(s)) => return Some(format!("{} ({})", f, s)),
(Some(f), None) => return Some(f),
(None, Some(s)) => {
let fallback = crate::theme::get_default_monospace_font()
.unwrap_or_else(|| "Default".to_string());
return Some(format!("{} ({})", fallback, s));
}
(None, None) => {}
}
}
}
} else if term_lower.contains("wezterm") {
let paths = [
home.join(".wezterm.lua"),
home.join(".config/wezterm/wezterm.lua"),
];
for path in paths {
if let Ok(content) = std::fs::read_to_string(&path) {
let mut family = None;
let mut size = None;
for line in content.lines() {
if line.contains("wezterm.font") {
if let Some(start) = line.find("wezterm.font") {
let rest = &line[start..];
if let Some(quote1) = rest.find('\'').or(rest.find('"')) {
let quote_char = rest.chars().nth(quote1).unwrap();
if let Some(quote2) = rest[quote1 + 1..].find(quote_char) {
family =
Some(rest[quote1 + 1..quote1 + 1 + quote2].to_string());
}
}
}
}
if line.contains("font_size") {
if let Some(idx) = line.find('=') {
let val = line[idx + 1..].trim().trim_end_matches(',');
size = Some(val.to_string());
}
}
}
match (family, size) {
(Some(f), Some(s)) => return Some(format!("{} ({})", f, s)),
(Some(f), None) => return Some(f),
(None, Some(s)) => {
let fallback = crate::theme::get_default_monospace_font()
.unwrap_or_else(|| "Default".to_string());
return Some(format!("{} ({})", fallback, s));
}
(None, None) => {}
}
}
}
} else if term_lower.contains("foot") {
let conf_path = home.join(".config/foot/foot.ini");
if let Ok(content) = std::fs::read_to_string(&conf_path) {
for line in content.lines() {
let line = line.trim();
if line.starts_with("font=") {
let val = line.trim_start_matches("font=");
let parts: Vec<&str> = val.split(':').collect();
let family = parts[0].trim();
let mut size = None;
for part in &parts[1..] {
if part.starts_with("size=") {
size = Some(part.trim_start_matches("size=").trim());
}
}
if let Some(s) = size {
return Some(format!("{} ({})", family, s));
} else {
return Some(family.to_string());
}
}
}
}
} else if term_lower.contains("ptyxis") {
#[cfg(target_os = "linux")]
if let Ok(output) = std::process::Command::new("gsettings")
.args(["get", "org.gnome.Ptyxis", "use-system-font"])
.output()
{
if output.status.success() {
let s = String::from_utf8_lossy(&output.stdout).trim().to_string();
if s == "false" {
if let Ok(font_out) = std::process::Command::new("gsettings")
.args(["get", "org.gnome.Ptyxis", "font-name"])
.output()
{
if font_out.status.success() {
let mut font_str =
String::from_utf8_lossy(&font_out.stdout).trim().to_string();
font_str = font_str.trim_matches('\'').to_string();
if !font_str.is_empty() {
if let Some(last_space) = font_str.rfind(' ') {
let family = &font_str[..last_space];
let size = &font_str[last_space + 1..];
if size.chars().all(|c| c.is_ascii_digit() || c == '.') {
return Some(format!("{} ({})", family, size));
}
}
return Some(font_str);
}
}
}
} else {
if let Ok(font_out) = std::process::Command::new("gsettings")
.args(["get", "org.gnome.desktop.interface", "monospace-font-name"])
.output()
{
if font_out.status.success() {
let mut font_str =
String::from_utf8_lossy(&font_out.stdout).trim().to_string();
font_str = font_str.trim_matches('\'').to_string();
if !font_str.is_empty() {
if let Some(last_space) = font_str.rfind(' ') {
let family = &font_str[..last_space];
let size = &font_str[last_space + 1..];
if size.chars().all(|c| c.is_ascii_digit() || c == '.') {
return Some(format!("{} ({})", family, size));
}
}
return Some(font_str);
}
}
}
return crate::theme::get_default_monospace_font();
}
}
}
} else if term_lower.contains("konsole") {
let rc_path = home.join(".config/konsolerc");
let mut profile_name = "Default.profile".to_string();
if let Ok(content) = std::fs::read_to_string(&rc_path) {
for line in content.lines() {
let line = line.trim();
if line.starts_with("DefaultProfile=") {
profile_name = line.trim_start_matches("DefaultProfile=").to_string();
break;
}
}
}
let profile_path = home.join(".local/share/konsole").join(profile_name);
if let Ok(content) = std::fs::read_to_string(&profile_path) {
for line in content.lines() {
let line = line.trim();
if line.starts_with("Font=") {
let val = line.trim_start_matches("Font=");
let parts: Vec<&str> = val.split(',').collect();
if !parts.is_empty() {
let family = parts[0];
if parts.len() > 1 {
let size = parts[1];
return Some(format!("{} ({})", family, size));
}
return Some(family.to_string());
}
}
}
}
return crate::theme::get_default_monospace_font();
}
#[cfg(target_os = "macos")]
if term_lower == "iterm.app" || term_lower.contains("iterm2") {
if let Ok(output) = std::process::Command::new("defaults")
.args(["read", "com.googlecode.iterm2", "Normal Font"])
.output()
{
if let Ok(s) = String::from_utf8(output.stdout) {
let font = s.trim();
if !font.is_empty() {
return Some(font.to_string());
}
}
}
}
None
}