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
31use 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#[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 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 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 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 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 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 if let Ok(memory) = &self.memory {
318 info.push_str(&format!("{GREEN}{BOLD}Memory: {RESET}{memory}\n"));
319 }
320 if let Ok(sensors) = &self.temperature
322 && !sensors.is_empty()
323 {
324 let k = "Temperature: ";
325 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 if let Ok(interfaces) = &self.network {
347 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 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}