From a1b7e755b25e451e4025121eefc89eb5d09c14fe Mon Sep 17 00:00:00 2001 From: neoarz Date: Mon, 5 Jan 2026 20:42:12 -0500 Subject: [PATCH] feat: halfway there? --- Cargo.lock | 132 +++++++++++++++++++++++++++++++++++ Cargo.toml | 4 ++ src/helpers/cursor.rs | 50 +++++++++++++ src/helpers/desktop_env.rs | 22 ++++++ src/helpers/font.rs | 25 +++++++ src/helpers/mod.rs | 7 ++ src/helpers/terminal.rs | 38 ++++++++++ src/helpers/terminal_font.rs | 32 +++++++++ src/helpers/wm.rs | 47 +++++++++++++ src/helpers/wm_theme.rs | 40 +++++++++++ src/main.rs | 25 ++++++- 11 files changed, 420 insertions(+), 2 deletions(-) create mode 100644 src/helpers/cursor.rs create mode 100644 src/helpers/desktop_env.rs create mode 100644 src/helpers/font.rs create mode 100644 src/helpers/terminal.rs create mode 100644 src/helpers/terminal_font.rs create mode 100644 src/helpers/wm.rs create mode 100644 src/helpers/wm_theme.rs diff --git a/Cargo.lock b/Cargo.lock index 773d59e..0b65560 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bitflags" version = "1.3.2" @@ -54,6 +60,15 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" +[[package]] +name = "deranged" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +dependencies = [ + "powerfmt", +] + [[package]] name = "dispatch2" version = "0.3.0" @@ -91,6 +106,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + [[package]] name = "errno" version = "0.3.14" @@ -116,6 +137,28 @@ dependencies = [ "byteorder", ] +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "indexmap" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + [[package]] name = "libc" version = "0.2.179" @@ -155,6 +198,10 @@ version = "0.1.0" dependencies = [ "colored", "display-info", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "plist", "sysinfo", ] @@ -167,6 +214,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "objc2" version = "0.6.3" @@ -351,6 +404,25 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "plist" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" +dependencies = [ + "base64", + "indexmap", + "quick-xml 0.38.4", + "serde", + "time", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "proc-macro2" version = "1.0.104" @@ -406,6 +478,35 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "shlex" version = "1.3.0" @@ -488,6 +589,37 @@ dependencies = [ "syn", ] +[[package]] +name = "time" +version = "0.3.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" + +[[package]] +name = "time-macros" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "unicode-ident" version = "1.0.22" diff --git a/Cargo.toml b/Cargo.toml index f35c021..f624248 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,4 +6,8 @@ edition = "2024" [dependencies] colored = "3.0.0" display-info = "0.5.7" +objc2 = "0.6.3" +objc2-app-kit = "0.3.2" +objc2-foundation = "0.3.2" +plist = "1.8.0" sysinfo = "0.37.2" diff --git a/src/helpers/cursor.rs b/src/helpers/cursor.rs new file mode 100644 index 0000000..e2be9d4 --- /dev/null +++ b/src/helpers/cursor.rs @@ -0,0 +1,50 @@ +// https://github.com/fastfetch-cli/fastfetch/blob/dev/src/detection/cursor/cursor_apple.m + +use plist::Value; +use std::env; +use std::path::PathBuf; + +fn format_color(dict: &plist::dictionary::Dictionary) -> String { + let r = (dict.get("red").and_then(|v| v.as_real()).unwrap_or(0.0) * 255.0 + 0.5) as u32; + let g = (dict.get("green").and_then(|v| v.as_real()).unwrap_or(0.0) * 255.0 + 0.5) as u32; + let b = (dict.get("blue").and_then(|v| v.as_real()).unwrap_or(0.0) * 255.0 + 0.5) as u32; + let a = (dict.get("alpha").and_then(|v| v.as_real()).unwrap_or(1.0) * 255.0 + 0.5) as u32; + + let color_hex = (r << 24) | (g << 16) | (b << 8) | a; + + match color_hex { + 0x000000FF => "Black".to_string(), + 0xFFFFFFFF => "White".to_string(), + 0xFF2600FF => "Red".to_string(), + 0x0433FFFF => "Blue".to_string(), + 0x00F900FF => "Green".to_string(), + 0xFFFB00FF => "Yellow".to_string(), + _ => format!("#{:08X}", color_hex), + } +} + +pub fn get_cursor_info() -> String { + let mut path = PathBuf::from(env::var("HOME").unwrap_or_default()); + path.push("Library/Preferences/com.apple.universalaccess.plist"); + + let mut fill = "Black".to_string(); + let mut outline = "White".to_string(); + let mut size = "32".to_string(); + + if let Ok(value) = Value::from_file(path) { + if let Some(dict) = value.as_dictionary() { + if let Some(f_dict) = dict.get("cursorFill").and_then(|v| v.as_dictionary()) { + fill = format_color(f_dict); + } + if let Some(o_dict) = dict.get("cursorOutline").and_then(|v| v.as_dictionary()) { + outline = format_color(o_dict); + } + + if let Some(s_val) = dict.get("mouseDriverCursorSize").and_then(|v| v.as_real()) { + size = format!("{:.0}", s_val * 32.0); + } + } + } + + format!("Fill - {}, Outline - {} ({}px)", fill, outline, size) +} diff --git a/src/helpers/desktop_env.rs b/src/helpers/desktop_env.rs new file mode 100644 index 0000000..6543a14 --- /dev/null +++ b/src/helpers/desktop_env.rs @@ -0,0 +1,22 @@ +// https://github.com/fastfetch-cli/fastfetch/blob/dev/src/detection/theme/theme_apple.c + +use sysinfo::System; + +pub fn get_desktop_env_info() -> String { + let os_version = System::os_version().unwrap_or_else(|| "0.0.0".to_string()); + let major_version: u32 = os_version + .split('.') + .next() + .and_then(|s| s.parse().ok()) + .unwrap_or(0); + + if major_version > 15 { + "Liquid Glass".to_string() + } else if major_version < 10 && major_version != 0 { + "Platinum".to_string() + } else if major_version >= 10 { + "Aqua".to_string() + } else { + "Unknown".to_string() + } +} diff --git a/src/helpers/font.rs b/src/helpers/font.rs new file mode 100644 index 0000000..5377a5d --- /dev/null +++ b/src/helpers/font.rs @@ -0,0 +1,25 @@ +// https://github.com/fastfetch-cli/fastfetch/blob/dev/src/detection/font/font_apple.m + +use objc2_app_kit::NSFont; +use objc2_foundation::MainThreadMarker; + +pub fn get_font_info() -> String { + let _mtm = MainThreadMarker::new().expect("Must be on the main thread to query fonts"); + + let sys_font = NSFont::systemFontOfSize(12.0); + let sys_name = sys_font + .familyName() + .expect("System font must have a family name") + .to_string(); + + let user_font = NSFont::userFontOfSize(12.0); + let user_name = match user_font { + Some(font) => font + .familyName() + .map(|name| name.to_string()) + .unwrap_or_else(|| "Helvetica".to_string()), + None => "Helvetica".to_string(), + }; + + format!("{} [System], {} [User]", sys_name, user_name) +} diff --git a/src/helpers/mod.rs b/src/helpers/mod.rs index 08ec7aa..0d8bba4 100644 --- a/src/helpers/mod.rs +++ b/src/helpers/mod.rs @@ -1,4 +1,11 @@ +pub mod cursor; +pub mod desktop_env; pub mod display; +pub mod font; pub mod packages; pub mod shell; +pub mod terminal; +pub mod terminal_font; pub mod uptime; +pub mod wm; +pub mod wm_theme; diff --git a/src/helpers/terminal.rs b/src/helpers/terminal.rs new file mode 100644 index 0000000..1594414 --- /dev/null +++ b/src/helpers/terminal.rs @@ -0,0 +1,38 @@ +use std::env; +use sysinfo::System; + +pub fn get_terminal_info() -> String { + let term_env = env::var("TERM_PROGRAM") + .or_else(|_| env::var("TERM")) + .unwrap_or_default(); + + let term_ver = env::var("TERM_PROGRAM_VERSION").unwrap_or_default(); + + if !term_env.is_empty() && term_env != "xterm-256color" && term_env != "xterm" { + let clean_name = term_env + .replace("com.apple.", "") // Apple Terminal + .replace("com.mitchellh.", "") // Ghostty + .replace(".app", ""); + + return if term_ver.is_empty() { + clean_name + } else { + format!("{} {}", clean_name, term_ver) + }; + } + + let mut sys = System::new(); + sys.refresh_processes(sysinfo::ProcessesToUpdate::All, true); + + let my_pid = sysinfo::get_current_pid().unwrap_or(sysinfo::Pid::from(0)); + + if let Some(process) = sys.process(my_pid) { + if let Some(parent_pid) = process.parent() { + if let Some(parent_proc) = sys.process(parent_pid) { + return parent_proc.name().to_string_lossy().replace(".app", ""); + } + } + } + + "unknown".to_string() +} diff --git a/src/helpers/terminal_font.rs b/src/helpers/terminal_font.rs new file mode 100644 index 0000000..2d94235 --- /dev/null +++ b/src/helpers/terminal_font.rs @@ -0,0 +1,32 @@ +// https://github.com/fastfetch-cli/fastfetch/blob/dev/src/detection/terminalfont/terminalfont.c + +use std::process::Command; + +pub fn get_terminal_font_info() -> String { + let output = Command::new("ghostty").arg("+show-config").output(); + + let mut font_family = String::new(); + let mut font_size = String::new(); + + if let Ok(out) = output { + let stdout = String::from_utf8_lossy(&out.stdout); + + for line in stdout.lines() { + if line.starts_with("font-family =") && font_family.is_empty() { + font_family = line.replace("font-family =", "").trim().to_string(); + } + if line.starts_with("font-size =") && font_size.is_empty() { + font_size = line.replace("font-size =", "").trim().to_string(); + } + } + } + + if font_family.is_empty() { + font_family = "JetBrainsMono Nerd Font".to_string(); + } + if font_size.is_empty() { + font_size = "13".to_string(); + } + + format!("{} Regular ({}pt)", font_family, font_size) +} diff --git a/src/helpers/wm.rs b/src/helpers/wm.rs new file mode 100644 index 0000000..bdc7651 --- /dev/null +++ b/src/helpers/wm.rs @@ -0,0 +1,47 @@ +// https://github.com/fastfetch-cli/fastfetch/blob/dev/src/detection/displayserver/displayserver_apple.c + +use plist::Value; +use std::path::Path; + +pub struct DisplayServerResult { + pub wm_pretty_name: String, +} + +pub fn get_window_manager_info() -> DisplayServerResult { + let mut result = DisplayServerResult { + wm_pretty_name: "Quartz Compositor".to_string(), + }; + + if cfg!(target_os = "macos") { + let plist_path = "/System/Library/CoreServices/WindowManager.app/Contents/version.plist"; + + if Path::new(plist_path).exists() { + if let Ok(value) = Value::from_file(plist_path) { + if let Some(dict) = value.as_dictionary() { + if let Some(raw_version) = dict.get("SourceVersion").and_then(|v| v.as_string()) + { + // Apple format: AAAABBBCCDDDDDD (Major, Minor, Patch, Build) + if raw_version.len() >= 8 && raw_version.chars().all(|c| c.is_numeric()) { + let major = + raw_version[..raw_version.len() - 12].trim_start_matches('0'); + let minor = raw_version[raw_version.len() - 12..raw_version.len() - 9] + .trim_start_matches('0'); + let patch = raw_version[raw_version.len() - 9..raw_version.len() - 7] + .trim_start_matches('0'); + + let m = if minor.is_empty() { "0" } else { minor }; + let p = if patch.is_empty() { "0" } else { patch }; + + result.wm_pretty_name = + format!("Quartz Compositor {}.{}.{}", major, m, p); + } else { + result.wm_pretty_name = format!("Quartz Compositor {}", raw_version); + } + } + } + } + } + } + + result +} diff --git a/src/helpers/wm_theme.rs b/src/helpers/wm_theme.rs new file mode 100644 index 0000000..c0c9ab2 --- /dev/null +++ b/src/helpers/wm_theme.rs @@ -0,0 +1,40 @@ +// https://github.com/fastfetch-cli/fastfetch/blob/dev/src/detection/wmtheme/wmtheme_apple.m + +use plist::Value; +use std::env; +use std::path::PathBuf; + +pub fn get_wm_theme_info() -> String { + let mut path = PathBuf::from(env::var("HOME").unwrap_or_else(|_| "".to_string())); + path.push("Library/Preferences/.GlobalPreferences.plist"); + + let mut accent_name = "Multicolor".to_string(); + let mut appearance = "Light".to_string(); + + if let Ok(value) = Value::from_file(path) { + if let Some(dict) = value.as_dictionary() { + if let Some(accent_val) = dict + .get("AppleAccentColor") + .and_then(|v| v.as_signed_integer()) + { + accent_name = match accent_val { + -1 => "Graphite".to_string(), + 0 => "Red".to_string(), + 1 => "Orange".to_string(), + 2 => "Yellow".to_string(), + 3 => "Green".to_string(), + 4 => "Blue".to_string(), + 5 => "Purple".to_string(), + 6 => "Pink".to_string(), + _ => "Multicolor".to_string(), + }; + } + + if let Some(style) = dict.get("AppleInterfaceStyle").and_then(|v| v.as_string()) { + appearance = style.to_string(); // Usually "Dark" + } + } + } + + format!("{} ({})", accent_name, appearance) +} diff --git a/src/main.rs b/src/main.rs index 48ab5d9..5f9c8f9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,6 @@ // neo64fetch - "jarvis, rewrite this project in rust" // use colored::*; -use display_info::DisplayInfo; use std::env; use sysinfo::System; mod helpers; @@ -19,8 +18,15 @@ struct Stats { packages: String, shell: String, display: String, + desktop_env: String, + window_manager: String, + window_manager_theme: String, + font: String, + cursor: String, + terminal: String, + terminal_font: String, - // Extra fields which are usually appended + // Extra fields architecture: String, } @@ -41,8 +47,16 @@ fn main() { packages: helpers::packages::get_brew_info(), shell: helpers::shell::get_shell_info(), display: helpers::display::get_display_info(), + desktop_env: helpers::desktop_env::get_desktop_env_info(), + window_manager: helpers::wm::get_window_manager_info().wm_pretty_name, + window_manager_theme: helpers::wm_theme::get_wm_theme_info(), + font: helpers::font::get_font_info(), + cursor: helpers::cursor::get_cursor_info(), + terminal: helpers::terminal::get_terminal_info(), + terminal_font: helpers::terminal_font::get_terminal_font_info(), }; + // TODO: Add ascii art support later // Testing each component separately; going to comment out at the end { println!("{}", stats.username); @@ -55,5 +69,12 @@ fn main() { println!("{}", stats.packages); println!("{}", stats.shell); println!("{}", stats.display); + println!("{}", stats.desktop_env); + println!("{}", stats.window_manager); + println!("{}", stats.window_manager_theme); + println!("{}", stats.font); + println!("{}", stats.cursor); + println!("{}", stats.terminal); + println!("{}", stats.terminal_font); } }