neofetch/
lib.rs

1pub mod battery;
2pub mod color;
3pub mod cpu;
4pub mod de;
5pub mod disk;
6pub mod display;
7pub mod error;
8pub mod gpu;
9pub mod host;
10pub mod hostname;
11pub mod icon;
12pub mod ip;
13pub mod kernel;
14pub mod locale;
15pub mod mappings;
16pub mod memory;
17pub mod network;
18pub mod os;
19pub mod packages;
20pub mod platform;
21pub mod share;
22pub mod shell;
23pub mod system;
24pub mod temperature;
25pub mod terminal;
26pub mod uptime;
27pub mod user;
28pub mod utils;
29pub mod wm;
30
31// Re-export commonly used types
32use cpu::Cpu;
33use disk::Disk;
34use display::{Display, get_display};
35pub use error::{NeofetchError, Result};
36use gpu::Gpu;
37use hostname::get_hostname;
38use os::OS;
39use packages::Packages;
40use uptime::Time;
41use which_shell::ShellVersion;
42
43use crate::battery::get_battery;
44use crate::color::{
45    BLACK_BG, BLUE_BG, BOLD, BRIGHT_BLACK_BG, BRIGHT_BLUE_BG, BRIGHT_CYAN_BG, BRIGHT_GREEN_BG,
46    BRIGHT_MAGENTA_BG, BRIGHT_RED_BG, BRIGHT_WHITE_BG, BRIGHT_YELLOW_BG, CYAN_BG, GREEN, GREEN_BG,
47    MAGENTA_BG, RED, RED_BG, RESET, WHITE_BG, YELLOW_BG, cursor_down, cursor_forward, cursor_up,
48};
49use crate::cpu::get_cpu;
50use crate::de::get_de;
51use crate::disk::get_disk;
52use crate::host::get_host;
53use crate::host::{get_baseband, get_rom};
54use crate::kernel::get_kernel;
55use crate::locale::get_locale;
56use crate::memory::get_memory;
57use crate::packages::get_packages;
58use crate::shell::which_shell;
59use crate::terminal::get_terminal;
60use crate::uptime::get_uptime;
61use crate::user::get_user;
62use crate::wm::{get_wm, get_wm_theme};
63use crate::{gpu::get_gpu, os::get_os};
64use crate::{network::get_network_info, temperature::get_temperature_sensors};
65
66pub fn join(left: String, right: String) -> String {
67    let mut s = String::new();
68    let left_h = left.lines().count();
69    let right_h = right.lines().count();
70    let max_h = left_h.max(right_h);
71    let left_max_w = left.lines().map(ansi_width::ansi_width).max().unwrap_or(0);
72
73    let gap = 3;
74
75    for i in left.lines() {
76        s.push_str(i);
77        let n = left_max_w + gap - ansi_width::ansi_width(i);
78        s.push_str(&" ".repeat(n));
79        s.push('\n');
80    }
81
82    for _ in left_h..max_h {
83        s.push_str(&" ".repeat(left_max_w + gap));
84        s.push('\n');
85    }
86
87    s.push_str(&cursor_up(max_h));
88    for i in right.lines() {
89        let line = format!("{}{i}", cursor_forward(left_max_w + gap));
90        s.push_str(&line);
91        s.push('\n');
92    }
93    s.push_str(&cursor_down(max_h - right_h));
94    s
95}
96
97/// System information container
98#[derive(Debug, Clone)]
99pub struct Neofetch {
100    pub os: Result<OS>,
101    pub user: Result<String>,
102    pub host: Result<String>,
103    pub hostname: Result<String>,
104    pub rom: Result<String>,
105    pub baseband: Result<String>,
106    pub kernel: Result<String>,
107    pub uptime: Result<Time>,
108    pub packages: Result<Packages>,
109    pub shell: Result<ShellVersion>,
110    pub display: Result<Vec<Display>>,
111    pub de: Option<String>,
112    pub wm: Result<String>,
113    pub wm_theme: Result<String>,
114    pub terminal: Result<String>,
115    pub disk: Result<Vec<Disk>>,
116    pub cpu: Result<Cpu>,
117    pub gpu: Option<Vec<Gpu>>,
118    pub memory: Result<String>,
119    pub battery: Result<u32>,
120    pub locale: Result<String>,
121    pub ip: Option<String>,
122    pub temperature: Result<Vec<temperature::TempSensor>>,
123    pub network: Result<Vec<network::NetworkInfo>>,
124}
125
126impl Neofetch {
127    /// Collect all system information
128    pub async fn new() -> Neofetch {
129        let (
130            shell,
131            os,
132            user,
133            host,
134            rom,
135            baseband,
136            kernel,
137            uptime,
138            packages,
139            display,
140            wm,
141            wm_theme,
142            terminal,
143            disk,
144            cpu,
145            gpu,
146            memory,
147            battery,
148            hostname,
149            locale,
150            temperature,
151            network,
152        ) = tokio::join!(
153            which_shell(),
154            get_os(),
155            get_user(),
156            get_host(),
157            get_rom(),
158            get_baseband(),
159            get_kernel(),
160            get_uptime(),
161            get_packages(),
162            get_display(),
163            get_wm(),
164            get_wm_theme(),
165            get_terminal(),
166            get_disk(),
167            get_cpu(),
168            get_gpu(),
169            get_memory(),
170            get_battery(),
171            get_hostname(),
172            get_locale(),
173            get_temperature_sensors(),
174            get_network_info(),
175        );
176
177        // Get desktop environment based on OS
178        let de = os.as_ref().ok().and_then(|o| get_de(o.clone()));
179        let ip = ip::get_ip();
180
181        Neofetch {
182            os,
183            user,
184            host,
185            rom,
186            baseband,
187            kernel,
188            uptime,
189            packages,
190            shell,
191            display,
192            de,
193            wm,
194            wm_theme,
195            terminal,
196            disk,
197            cpu,
198            gpu,
199            memory,
200            battery,
201            hostname,
202            locale,
203            ip,
204            temperature,
205            network,
206        }
207    }
208}
209
210impl std::fmt::Display for Neofetch {
211    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
212        let mut info = String::new();
213        let mut icon = String::new();
214        let user = self.user.as_ref().ok().cloned().unwrap_or_default();
215        let hostname = self.hostname.as_ref().ok().cloned().unwrap_or_default();
216
217        info.push_str(&format!(
218            "{RESET}{RED}{BOLD}{user}{RESET}@{RED}{BOLD}{hostname}{RESET}\n"
219        ));
220        info.push_str("-------\n");
221
222        // Handle OS (Result type)
223        if let Ok(os) = &self.os {
224            icon = os.distro.icon();
225            info.push_str(&format!("{GREEN}{BOLD}OS: {RESET}{}\n", os));
226        }
227
228        if let Ok(host) = &self.host {
229            info.push_str(&format!("{GREEN}{BOLD}Host: {RESET}{host}\n"));
230        }
231
232        if let Ok(rom) = &self.rom {
233            info.push_str(&format!("{GREEN}{BOLD}Rom: {RESET}{rom}\n"));
234        }
235
236        if let Ok(baseband) = &self.baseband {
237            info.push_str(&format!("{GREEN}{BOLD}Baseband: {RESET}{baseband}\n"));
238        }
239
240        // Handle kernel (Result type)
241        if let Ok(kernel) = &self.kernel {
242            info.push_str(&format!("{GREEN}{BOLD}Kernel: {RESET}{kernel}\n"));
243        }
244
245        if let Ok(uptime) = &self.uptime
246            && uptime.0 > 0
247        {
248            info.push_str(&format!("{GREEN}{BOLD}Uptime: {RESET}{uptime}\n"));
249        }
250
251        if let Ok(packages) = &self.packages {
252            let s = packages.to_string();
253            if !s.trim().is_empty() {
254                info.push_str(&format!("{GREEN}{BOLD}Packages: {RESET}{s}\n"));
255            }
256        }
257
258        if let Ok(shell) = &self.shell {
259            info.push_str(&format!("{GREEN}{BOLD}Shell: {RESET}{shell}\n"));
260        }
261
262        if let Ok(displays) = &self.display {
263            for display in displays {
264                let key = if let Some(i) = &display.friendly_name {
265                    format!("{GREEN}{BOLD}Display({i})")
266                } else {
267                    display
268                        .name
269                        .clone()
270                        .map_or(format!("{GREEN}{BOLD}Display"), |s| {
271                            format!("{GREEN}{BOLD}Display({s})")
272                        })
273                };
274                info.push_str(&format!("{key}: {RESET}{}\n", display));
275            }
276        }
277
278        if let Some(de) = &self.de {
279            info.push_str(&format!("{GREEN}{BOLD}DE: {RESET}{de}\n"));
280        }
281
282        if let Ok(wm) = &self.wm {
283            info.push_str(&format!("{GREEN}{BOLD}WM: {RESET}{wm}\n"));
284
285            if let Ok(theme) = &self.wm_theme {
286                info.push_str(&format!("{GREEN}{BOLD}WM Theme: {RESET}{theme}\n"));
287            }
288        }
289
290        if let Ok(terminal) = &self.terminal {
291            info.push_str(&format!("{GREEN}{BOLD}Terminal: {RESET}{terminal}\n"));
292        }
293
294        if let Ok(disks) = &self.disk {
295            for disk in disks {
296                if disk.total > 0 {
297                    info.push_str(&format!(
298                        "{GREEN}{BOLD}Disk({}): {RESET}{}\n",
299                        disk.name, disk
300                    ));
301                }
302            }
303        }
304
305        // Handle CPU (Result type)
306        if let Ok(cpu) = &self.cpu {
307            info.push_str(&format!("{GREEN}{BOLD}CPU: {RESET}{cpu}\n"));
308        }
309
310        if let Some(gpu) = &self.gpu {
311            for g in gpu {
312                info.push_str(&format!("{GREEN}{BOLD}GPU: {RESET}{g}\n"));
313            }
314        }
315
316        // Handle memory (Result type)
317        if let Ok(memory) = &self.memory {
318            info.push_str(&format!("{GREEN}{BOLD}Memory: {RESET}{memory}\n"));
319        }
320        // Handle temperature sensors (Result type)
321        if let Ok(sensors) = &self.temperature
322            && !sensors.is_empty()
323        {
324            let k = "Temperature: ";
325            // Show only the first few sensors to avoid clutter
326            for (i, sensor) in sensors.iter().take(3).enumerate() {
327                if i == 0 {
328                    info.push_str(&format!("{GREEN}{BOLD}{k}{RESET}{sensor}\n"));
329                } else {
330                    info.push_str(&format!(
331                        "{GREEN}{BOLD}{}{RESET}{sensor}\n",
332                        " ".repeat(k.len()),
333                    ));
334                }
335            }
336        }
337        if let Ok(battery) = &self.battery {
338            info.push_str(&format!("{GREEN}{BOLD}Battery: {RESET}{battery}\n"));
339        }
340
341        if let Some(ip) = &self.ip {
342            info.push_str(&format!("{GREEN}{BOLD}Local IP: {RESET}{ip}\n"));
343        }
344
345        // Handle network interfaces (Result type)
346        if let Ok(interfaces) = &self.network {
347            // Show only active interfaces with IP addresses
348            let active_interfaces: Vec<_> = interfaces
349                .iter()
350                .filter(|iface| iface.is_up && iface.ipv4_address.is_some())
351                .collect();
352
353            if !active_interfaces.is_empty() {
354                let k = "Network: ";
355                for (i, iface) in active_interfaces.iter().take(3).enumerate() {
356                    let ip = iface.ipv4_address.as_ref().unwrap();
357                    if i == 0 {
358                        info.push_str(&format!(
359                            "{GREEN}{BOLD}{k}{RESET}{} ({ip})\n",
360                            iface.interface_name,
361                        ));
362                    } else {
363                        info.push_str(&format!(
364                            "{GREEN}{BOLD}{}{RESET}{} ({ip})\n",
365                            " ".repeat(k.len()),
366                            iface.interface_name,
367                        ));
368                    }
369                }
370            }
371        }
372
373        if let Ok(locale) = &self.locale {
374            info.push_str(&format!("{GREEN}{BOLD}Locale: {RESET}{locale}\n"));
375        }
376
377        // Color bars
378        let color_str: String = [
379            BLACK_BG, RED_BG, GREEN_BG, YELLOW_BG, BLUE_BG, MAGENTA_BG, CYAN_BG, WHITE_BG,
380        ]
381        .map(|c| format!("{c}   "))
382        .into_iter()
383        .collect();
384        info.push('\n');
385        info.push_str(&(color_str + RESET + "\n"));
386
387        let color_str: String = [
388            BRIGHT_BLACK_BG,
389            BRIGHT_RED_BG,
390            BRIGHT_GREEN_BG,
391            BRIGHT_YELLOW_BG,
392            BRIGHT_BLUE_BG,
393            BRIGHT_MAGENTA_BG,
394            BRIGHT_CYAN_BG,
395            BRIGHT_WHITE_BG,
396        ]
397        .map(|c| format!("{c}   "))
398        .into_iter()
399        .collect();
400        info.push_str(&(color_str + RESET + "\n"));
401
402        write!(f, "{}", join(icon, info))
403    }
404}
405
406pub async fn neofetch() -> String {
407    Neofetch::new().await.to_string()
408}