├── .gitignore ├── src ├── widgets │ ├── mod.rs │ └── readout.rs ├── theme │ ├── mod.rs │ ├── borders.rs │ ├── color.rs │ ├── base.rs │ └── components.rs ├── extra.rs ├── error.rs ├── bars.rs ├── config.rs ├── doctor.rs ├── main.rs ├── buffer.rs ├── cli.rs ├── format.rs ├── ascii.rs └── data │ └── mod.rs ├── assets └── preview.png ├── Cross.toml ├── contrib ├── ascii │ ├── fedoralinux.ascii │ ├── archlinux.ascii │ └── rust.ascii ├── themes │ ├── Beryllium.toml │ ├── Hydrogen.toml │ ├── Helium.toml │ └── Lithium.toml ├── README.md └── scripts │ └── macchina-video.sh ├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ └── bug-report.md └── workflows │ ├── macchina.yml │ └── macchina-release-tag.yml ├── LICENSE ├── macchina.toml ├── Cargo.toml ├── CHANGELOG.md ├── doc ├── macchina.1.scd ├── macchina.1 ├── macchina.7.scd └── macchina.7 └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .idea/ 3 | .vim 4 | -------------------------------------------------------------------------------- /src/widgets/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod readout; 2 | -------------------------------------------------------------------------------- /assets/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gobidev/macchina/main/assets/preview.png -------------------------------------------------------------------------------- /src/theme/mod.rs: -------------------------------------------------------------------------------- 1 | mod base; 2 | pub mod borders; 3 | pub mod color; 4 | pub mod components; 5 | pub use base::*; 6 | -------------------------------------------------------------------------------- /Cross.toml: -------------------------------------------------------------------------------- 1 | [target.aarch64-linux-android] 2 | # Workaround for https://github.com/cross-rs/cross/issues/1128 3 | # and https://github.com/rust-lang/rust/issues/103673 4 | image = "ghcr.io/cross-rs/aarch64-linux-android:edge" 5 | -------------------------------------------------------------------------------- /contrib/ascii/fedoralinux.ascii: -------------------------------------------------------------------------------- 1 | .:-======-:. 2 | :=++++++++++++=-. 3 | -++++++++++++++++++- 4 | .+++++++++++=:::-=+++++. 5 | .+++++++++++. .. :+++++. 6 | =++++++++++- +++= =+++++ 7 | :+++++++++++: .++++:.=+++++: 8 | -++++++++==+- .===+++++++++= 9 | =+++++- -- .+++++++++= 10 | =++++. -==+- .==++++++++++- 11 | =+++- +++++- .++++++++++++ 12 | =+++= -++++. :+++++++++++. 13 | =++++- .. .+++++++++++: 14 | =++++++-:..:=++++++++++= 15 | -++++++++++++++++++++-. 16 | :=+++++++++++++=-:. 17 | -------------------------------------------------------------------------------- /contrib/themes/Beryllium.toml: -------------------------------------------------------------------------------- 1 | # Beryllium 2 | 3 | spacing = 3 4 | hide_ascii = true 5 | key_color = "#7067CF" 6 | separator = "" 7 | 8 | [box] 9 | border = "plain" 10 | visible = true 11 | 12 | [palette] 13 | glyph = "○ " 14 | visible = true 15 | 16 | [bar] 17 | glyph = "○" 18 | hide_delimiters = true 19 | visible = true 20 | 21 | [box.inner_margin] 22 | x = 2 23 | y = 1 24 | 25 | [custom_ascii] 26 | color = "#FF7001" 27 | -------------------------------------------------------------------------------- /contrib/README.md: -------------------------------------------------------------------------------- 1 | # `contrib` directory 2 | 3 | This directory contains cool things our community has contributed, or some 4 | niceties we've added that they might find useful. 5 | 6 | | Directory | Description | 7 | | -----------------: | :------------------------------------------------------------------------------------- | 8 | | [scripts](scripts) | A collection of scripts that supercharge _macchina_. | 9 | | [ascii](ascii) | A collection of ASCII art files that you can display alongside your system statistics. | 10 | | [themes](themes) | A collection of themes that you should definitely steal! | 11 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Thanks for taking interest in our project, we'd love to work with you. 2 | 3 | We've compiled, for your convenienve, a list of the most relevant 4 | questions and answers that you should know about before you contribute 5 | to the project. 6 | 7 | ## Where do I submit my PR? 8 | 9 | [Right here!](https://github.com/Macchina-CLI/macchina/pull/new/main) 10 | 11 | ## Will my pull request get merged? 12 | 13 | It depends. 14 | 15 | Our highest priority is to maintain great performance throughout 16 | feature releases. If your PR includes a kind of feature which 17 | *inherently* hinders performance considerably, it will unfortunately 18 | be rejected. 19 | 20 | Other than that, all contributions are welcome. 21 | 22 | ## Should I follow any specific code styles? 23 | 24 | `cargo fmt` and `cargo clippy` are your friends. 25 | -------------------------------------------------------------------------------- /src/theme/borders.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Deserializer, Serialize, Serializer}; 2 | 3 | #[derive(Debug, Clone)] 4 | pub enum Border { 5 | Thick, 6 | Plain, 7 | Rounded, 8 | Double, 9 | } 10 | 11 | impl<'de> Deserialize<'de> for Border { 12 | fn deserialize(deserializer: D) -> Result 13 | where 14 | D: Deserializer<'de>, 15 | { 16 | let s = String::deserialize(deserializer)?; 17 | match &s.as_str().to_lowercase()[..] { 18 | "thick" => Ok(Self::Thick), 19 | "plain" => Ok(Self::Plain), 20 | "double" => Ok(Self::Double), 21 | _ => Ok(Self::Rounded), 22 | } 23 | } 24 | } 25 | 26 | impl Serialize for Border { 27 | fn serialize(&self, serializer: S) -> Result 28 | where 29 | S: Serializer, 30 | { 31 | serializer.serialize_some(&self) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/extra.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::ffi::OsStr; 3 | use std::fs; 4 | use std::path::{Path, PathBuf}; 5 | 6 | /// Simply returns `$HOME/.config` 7 | pub fn config_dir() -> Option { 8 | match env::var("HOME") { 9 | Ok(home) => Some(PathBuf::from(home).join(".config")), 10 | _ => None, 11 | } 12 | } 13 | 14 | /// Simply returns `/usr/share` 15 | pub fn usr_share_dir() -> Option { 16 | Some(PathBuf::from("/usr/share")) 17 | } 18 | 19 | /// Returns the entries of a given path. 20 | pub fn get_entries(path: &Path) -> Option> { 21 | match fs::read_dir(path) { 22 | Ok(dir) => { 23 | let mut entries: Vec = Vec::new(); 24 | dir.flatten().for_each(|x| entries.push(x.path())); 25 | Some(entries) 26 | } 27 | _ => None, 28 | } 29 | } 30 | 31 | /// Returns the extension of a given path. 32 | pub fn path_extension(path: &Path) -> Option<&str> { 33 | path.extension().and_then(OsStr::to_str) 34 | } 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Create a report to help improve Macchina 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. 16 | 2. 17 | 3. 18 | 19 | **Expected behavior** 20 | A clear and concise description of what you expected to happen. 21 | 22 | **Screenshots** 23 | If applicable, add screenshots to help explain your problem. 24 | 25 | **`macchina --doctor` output** 26 | Add a screenshot or copy and paste the output of `--doctor` if an element you're supposed to be seeing isn't showing up. 27 | 28 | **System Information** 29 | You don't have to provide this information if you're not comfortable doing so, but it'll help us solve the issue a lot faster. 30 | - Distribution: [e.g. Pop!_OS] 31 | - Desktop Environment [e.g. Plasma] 32 | - Operating System [e.g. macOS] 33 | - Terminal [e.g. Alacritty] 34 | - Macchina's version [e.g. 0.5.9] 35 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use colored::Colorize; 2 | use std::io; 3 | use thiserror::Error; 4 | 5 | #[derive(Debug, Error)] 6 | pub enum Error { 7 | #[error("Failed due to IOError {0}")] 8 | IOError(#[from] io::Error), 9 | 10 | #[error("Failed to parse TOML file {0}")] 11 | ParsingError(#[from] toml::de::Error), 12 | } 13 | 14 | pub type Result = std::result::Result; 15 | 16 | pub fn print_errors(err: Error) { 17 | match err { 18 | Error::ParsingError(err) => match err.line_col() { 19 | Some((line, col)) => { 20 | println!( 21 | "{}: At line {} column {}.\n{}: {}", 22 | "Error".bright_red(), 23 | (line + 1).to_string().yellow(), 24 | (col + 1).to_string().yellow(), 25 | "Caused by".bold(), 26 | err 27 | ) 28 | } 29 | None => println!("{}: {:?}", "Error".bright_red(), err), 30 | }, 31 | Error::IOError(err) => { 32 | return println!("{}: {:?}", "Error".bright_red(), err); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Aziz Ben Ali 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /contrib/themes/Hydrogen.toml: -------------------------------------------------------------------------------- 1 | # Hydrogen 2 | 3 | spacing = 2 4 | padding = 0 5 | hide_ascii = true 6 | separator = ">" 7 | key_color = "Cyan" 8 | separator_color = "White" 9 | 10 | [palette] 11 | type = "Full" 12 | visible = false 13 | 14 | [bar] 15 | glyph = "ߋ" 16 | symbol_open = '[' 17 | symbol_close = ']' 18 | hide_delimiters = true 19 | visible = true 20 | 21 | [box] 22 | border = "plain" 23 | visible = true 24 | 25 | [box.inner_margin] 26 | x = 1 27 | y = 0 28 | 29 | [randomize] 30 | key_color = false 31 | separator_color = false 32 | 33 | [keys] 34 | host = "Host" 35 | kernel = "Kernel" 36 | battery = "Battery" 37 | os = "OS" 38 | de = "DE" 39 | wm = "WM" 40 | distro = "Distro" 41 | terminal = "Terminal" 42 | shell = "Shell" 43 | packages = "Packages" 44 | uptime = "Uptime" 45 | memory = "Memory" 46 | machine = "Machine" 47 | local_ip = "Local IP" 48 | backlight = "Brightness" 49 | resolution = "Resolution" 50 | cpu_load = "CPU Load" 51 | cpu = "CPU" 52 | -------------------------------------------------------------------------------- /src/bars.rs: -------------------------------------------------------------------------------- 1 | /** 2 | Returns a `usize` whose value can range from 0 up to 10 based on the given `value`. 3 | This is used to calculate the number of blocks to show 4 | in a bar. 5 | 6 | For example: 7 | - __CPU Usage__ ranges from 0 to 100%, this function can return a `usize` 8 | that tells the bar how many of its blocks should be represented as being used. 9 | 10 | The same goes for __battery percentage__, as it ranges from 0 to 100%. 11 | */ 12 | pub fn num_to_blocks(value: u8) -> usize { 13 | match value { 14 | 0..=10 => 1, 15 | 11..=20 => 2, 16 | 21..=30 => 3, 17 | 31..=40 => 4, 18 | 41..=50 => 5, 19 | 51..=60 => 6, 20 | 61..=70 => 7, 21 | 71..=80 => 8, 22 | 81..=90 => 9, 23 | 91..=100 => 10, 24 | // 0 is reserved for errors 25 | _ => 0, 26 | } 27 | } 28 | 29 | /// Returns a `usize` whose value can range from 0 up to 10 based on the given `value`. 30 | /// This is very similar to `num_to_blocks` but the calculations are done in a different way. 31 | pub fn memory(used: u64, total: u64) -> usize { 32 | let used = used as f64; 33 | let total = total as f64; 34 | 35 | (used / total * 10f64).ceil() as usize 36 | } 37 | -------------------------------------------------------------------------------- /contrib/themes/Helium.toml: -------------------------------------------------------------------------------- 1 | # Helium 2 | 3 | hide_ascii = false 4 | spacing = 2 5 | padding = 0 6 | separator = "->" 7 | key_color = "Blue" 8 | separator_color = "Yellow" 9 | 10 | [bar] 11 | glyph = "o" 12 | symbol_open = "(" 13 | symbol_close = ")" 14 | hide_delimiters = false 15 | visible = false 16 | 17 | [box] 18 | title = " Helium " 19 | border = "rounded" 20 | visible = false 21 | 22 | [box.inner_margin] 23 | x = 2 24 | y = 1 25 | 26 | [custom_ascii] 27 | color = "Yellow" 28 | 29 | [randomize] 30 | key_color = false 31 | separator_color = false 32 | 33 | [keys] 34 | host = "Host" 35 | kernel = "Kernel" 36 | battery = "Battery" 37 | os = "OS" 38 | de = "DE" 39 | wm = "WM" 40 | distro = "Distro" 41 | terminal = "Terminal" 42 | shell = "Shell" 43 | packages = "Packages" 44 | uptime = "Uptime" 45 | memory = "Memory" 46 | machine = "Machine" 47 | local_ip = "IP" 48 | backlight = "Brightness" 49 | resolution = "Resolution" 50 | cpu_load = "CPU Load" 51 | cpu = "CPU" 52 | -------------------------------------------------------------------------------- /macchina.toml: -------------------------------------------------------------------------------- 1 | # Specifies the network interface to use for the LocalIP readout 2 | interface = "wlan0" 3 | 4 | # Lengthen uptime output 5 | long_uptime = true 6 | 7 | # Lengthen shell output 8 | long_shell = false 9 | 10 | # Lengthen kernel output 11 | long_kernel = false 12 | 13 | # Toggle between displaying the current shell or your user's default one. 14 | current_shell = true 15 | 16 | # Toggle between displaying the number of physical or logical cores of your 17 | # processor. 18 | physical_cores = true 19 | 20 | # Themes need to be placed in "$XDG_CONFIG_DIR/macchina/themes" beforehand. 21 | # e.g.: 22 | # if theme path is /home/foo/.config/macchina/themes/Sodium.toml 23 | # theme should be uncommented and set to "Sodium" 24 | # 25 | # theme = "" 26 | 27 | # Displays only the specified readouts. 28 | # Accepted values (case-sensitive): 29 | # - Host 30 | # - Machine 31 | # - Kernel 32 | # - Distribution 33 | # - OperatingSystem 34 | # - DesktopEnvironment 35 | # - WindowManager 36 | # - Resolution 37 | # - Backlight 38 | # - Packages 39 | # - LocalIP 40 | # - Terminal 41 | # - Shell 42 | # - Uptime 43 | # - Processor 44 | # - ProcessorLoad 45 | # - Memory 46 | # - Battery 47 | # Example: 48 | # show = ["Battery", "Memory", ...] 49 | -------------------------------------------------------------------------------- /contrib/themes/Lithium.toml: -------------------------------------------------------------------------------- 1 | # Lithium 2 | 3 | spacing = 1 4 | padding = 0 5 | hide_ascii = true 6 | separator = " " 7 | key_color = "Yellow" 8 | separator_color = "Yellow" 9 | 10 | [palette] 11 | type = "Light" 12 | glyph = " ● " 13 | visible = true 14 | 15 | [bar] 16 | glyph = "●" 17 | symbol_open = '(' 18 | symbol_close = ')' 19 | visible = false 20 | hide_delimiters = true 21 | 22 | [box] 23 | title = " " 24 | border = "plain" 25 | visible = false 26 | 27 | [box.inner_margin] 28 | x = 2 29 | y = 1 30 | 31 | [custom_ascii] 32 | color = "Yellow" 33 | 34 | [randomize] 35 | key_color = false 36 | separator_color = false 37 | pool = "base" 38 | 39 | [keys] 40 | host = "Host" 41 | kernel = "Kernel" 42 | battery = "Battery" 43 | os = "OS" 44 | de = "DE" 45 | wm = "WM" 46 | distro = "Distro" 47 | terminal = "Terminal" 48 | shell = "Shell" 49 | packages = "Packages" 50 | uptime = "Uptime" 51 | memory = "Memory" 52 | machine = "Machine" 53 | local_ip = "IP" 54 | backlight = "Brightness" 55 | resolution = "Resolution" 56 | cpu_load = "CPU Load" 57 | cpu = "CPU" 58 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "macchina" 3 | version = "6.1.8" 4 | authors = ["Aziz Ben Ali ", "Marvin Haschker ", "Uttarayan Mondal "] 5 | edition = "2021" 6 | description = "A system information fetcher with an emphasis on performance." 7 | keywords = ["system", "fetch", "cli"] 8 | repository = "https://github.com/Macchina-CLI/macchina" 9 | license = "MIT" 10 | readme = "README.md" 11 | build = "build.rs" 12 | 13 | [dependencies] 14 | libmacchina = { version = "6.3.5", features = ["version"] } 15 | bytesize = "1.1.0" 16 | shellexpand = "3.0.0" 17 | clap = { version = "4.0.32", features = ["derive"] } # TODO: Update me! 18 | atty= "0.2.14" 19 | colored = "2.0.0" 20 | rand = "0.8.5" 21 | unicode-width = "0.1.10" 22 | lazy_static = "1.4.0" 23 | ansi-to-tui = "2.0.0" 24 | color-to-tui = "0.2.0" 25 | dirs = "4.0.0" 26 | toml = "0.5.10" 27 | serde_json = "1.0.91" 28 | thiserror = "1.0.38" 29 | tui = { version = "0.19.0", default-features = false, features = ["crossterm"] } 30 | serde = { version = "1.0.152", features = ["derive"] } 31 | 32 | [build-dependencies.vergen] 33 | version = "7.4.2" 34 | default-features = false 35 | features = ["build", "cargo", "git", "rustc"] 36 | 37 | [profile.release] 38 | opt-level = 3 39 | debug = false 40 | lto = true 41 | incremental = true 42 | codegen-units = 1 43 | 44 | [features] 45 | openwrt = ["libmacchina/openwrt"] 46 | -------------------------------------------------------------------------------- /src/theme/color.rs: -------------------------------------------------------------------------------- 1 | use rand::seq::SliceRandom; 2 | use serde::{Deserialize, Deserializer, Serialize, Serializer}; 3 | use tui::style::Color; 4 | 5 | #[derive(Debug, Clone)] 6 | pub enum ColorTypes { 7 | Base, 8 | Hexadecimal, 9 | Indexed, 10 | } 11 | 12 | impl<'de> Deserialize<'de> for ColorTypes { 13 | fn deserialize(deserializer: D) -> Result 14 | where 15 | D: Deserializer<'de>, 16 | { 17 | let s = String::deserialize(deserializer)?; 18 | match &s.as_str().to_lowercase()[..] { 19 | "hexadecimal" => Ok(Self::Hexadecimal), 20 | "indexed" => Ok(Self::Indexed), 21 | _ => Ok(Self::Base), 22 | } 23 | } 24 | } 25 | 26 | impl Serialize for ColorTypes { 27 | fn serialize(&self, serializer: S) -> Result 28 | where 29 | S: Serializer, 30 | { 31 | serializer.serialize_some(&self) 32 | } 33 | } 34 | 35 | pub fn make_random_color() -> Color { 36 | use Color::*; 37 | let mut random = rand::thread_rng(); 38 | let colors = [ 39 | Red, 40 | Black, 41 | Green, 42 | Yellow, 43 | Blue, 44 | Magenta, 45 | Cyan, 46 | Gray, 47 | LightRed, 48 | LightGreen, 49 | LightYellow, 50 | LightBlue, 51 | LightMagenta, 52 | LightCyan, 53 | White, 54 | ]; 55 | *colors.choose(&mut random).unwrap() 56 | } 57 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use crate::cli::{Opt, PKG_NAME}; 2 | use crate::error::Result; 3 | use std::path::{Path, PathBuf}; 4 | 5 | pub fn read_config + ?Sized>(path: &S) -> Result { 6 | let path = Path::new(path); 7 | if path.exists() { 8 | let config_buffer = std::fs::read(path)?; 9 | Ok(toml::from_slice(&config_buffer)?) 10 | } else { 11 | Ok(Opt::default()) 12 | } 13 | } 14 | 15 | pub fn get_config() -> Result { 16 | if cfg!(target_os = "macos") { 17 | if let Ok(home) = std::env::var("HOME") { 18 | let path = PathBuf::from(home) 19 | .join(".config") 20 | .join(PKG_NAME) 21 | .join(format!("{PKG_NAME}.toml")); 22 | 23 | return read_config(&path); 24 | } 25 | } else if let Some(mut path) = dirs::config_dir() { 26 | path.push(PKG_NAME); 27 | path.push(format!("{PKG_NAME}.toml")); 28 | 29 | return read_config(&path); 30 | } 31 | 32 | Ok(Opt::default()) 33 | } 34 | 35 | #[cfg(test)] 36 | mod tests { 37 | use super::*; 38 | 39 | #[test] 40 | fn documentation_config() -> Result<()> { 41 | let opt = read_config("macchina.toml")?; 42 | 43 | assert!(opt.long_uptime); 44 | assert!(!opt.long_shell); 45 | assert!(!opt.long_kernel); 46 | assert!(opt.current_shell); 47 | assert!(opt.physical_cores); 48 | assert_eq!(opt.interface, Some(String::from("wlan0"))); 49 | Ok(()) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## `6.1.8` 4 | 5 | - Don't panic when unwrapping ReadoutKey::from_str (Fixes: https://github.com/Macchina-CLI/macchina/issues/270) 6 | 7 | ## `6.1.7` 8 | 9 | - All dependencies have been bumped to their latest version. 10 | 11 | ## `6.1.6` 12 | 13 | libmacchina has been bumped to `v6.3.1` which: 14 | - Fixes a bug that causes framerate to appear as nil on certain macOS systems 15 | (Author: @123marvin123) 16 | 17 | ## `6.1.5` 18 | 19 | - Fixed `--version` not displaying the version (Author: @123marvin123) 20 | 21 | ## `6.1.4` 22 | 23 | ### Fixes 24 | 25 | - We hope this release addresses the build issues that have been occurring lately. 26 | 27 | ### Features 28 | 29 | libmacchina has been bumped to `v6.3.0` which includes a new feature: 30 | - Implement backlight readout for macOS (Author: @123marvin123) 31 | 32 | ## `6.1.3` 33 | 34 | Yanked. 35 | 36 | See [this comment for specifics](https://github.com/Macchina-CLI/macchina/issues/263#issuecomment-1250045395). 37 | 38 | ## `6.1.2` 39 | 40 | - Fixed an issue that caused installations through `cargo` to fail due to a 41 | malformed lockfile. 42 | 43 | ## `6.1.1` 44 | 45 | - Updated dependencies to their latest versions 46 | - Removed `--export-config` flag 47 | - Renamed `CHANGELOG.txt` to `CHANGELOG.md` 48 | 49 | ## `6.1.0` 50 | 51 | Yanked. 52 | 53 | See this [commit message for specifics](https://github.com/Macchina-CLI/macchina/commit/fb31328cf75e3e945a70b80cb1891a062a63de5e). 54 | 55 | ## `6.0.6` 56 | 57 | Bump libmacchina to v6.1.0: 58 | - Fixes a bug that causes the package readout to display "0 (cargo)" if 59 | $CARGO_HOME/bin is empty. 60 | (https://github.com/Macchina-CLI/libmacchina/commit/22a7df0f74e7d14c34cbfc35b40b61d5f2b5d199) 61 | - Fixes a bug that causes the network readout to return an IPv6 address in some cases. 62 | (https://github.com/Macchina-CLI/libmacchina/commit/608a1dde39def981d2750f4221c217151b80437e) 63 | 64 | Contributors: 65 | - @luckman212 66 | 67 | ## `6.0.5` 68 | 69 | - Fix incorrect `target_os` for a particular `cfg!` flag. 70 | 71 | -------------------------------------------------------------------------------- /contrib/scripts/macchina-video.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # Will only work on macchina v0.7.3 or higher 4 | # This script will download and run a video from youtube / any site supported by youtube-dl 5 | # and display the video in macchina. 6 | # The flow is 7 | # youtube-dl -> ffmpeg -> jp2a -> macchina 8 | # First argument is video url. 9 | # Second argument is frame wait time. 10 | 11 | PID=$$ 12 | DIR="/tmp/ffmpeg_$PID" 13 | 14 | if [ -n "$1" ]; then 15 | URL="$1" 16 | else 17 | URL=$(echo "aHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj1kUXc0dzlXZ1hjUQo=" | base64 -d) 18 | fi 19 | 20 | if [ -n "$2" ]; then 21 | FRAME_WAIT_TIME=$2 22 | else 23 | FRAME_WAIT_TIME=5 24 | fi 25 | 26 | required="youtube-dl ffmpeg base64 awk jp2a macchina" 27 | 28 | for r in "$required"; do 29 | if ! [ -n "$(which "$r" 2>/dev/null)" ]; then # need the quotes 30 | printf '\x1b[31m%s not found\x1b[0m\n' "$r" 31 | exit 1 32 | fi 33 | done 34 | 35 | # polling rate is .05 i.e. once every 50ms 36 | WAIT=$(echo - | awk -v seconds="$FRAME_WAIT_TIME" '{print seconds/.05}') 37 | 38 | trap_ctrlc() { 39 | 40 | printf '\x1b[?25h' # shows cursor 41 | if [ -n "$FFMPEG_PID" -a -d "/proc/$FFMPEG_PID" ]; then 42 | kill -0 "$FFMPEG_PID" 43 | wait "$FFMPEG_PID" 44 | fi 45 | 46 | if [ -n "$DIR" -a -d "$DIR" ]; then 47 | rm -rf "$DIR" 2>/dev/null 48 | fi 49 | 50 | exit 51 | } 52 | 53 | mkdir "$DIR" 54 | 55 | # youtube-dl -f best $URL -o - | ffmpeg -i pipe: -r 10 -update 1 "$DIR/out_%d.png" > /dev/null 2>&1 & 56 | youtube-dl -f best "$URL" -o - 2>/dev/null | ffmpeg -i pipe: -r 10 "$DIR/out_%d.png" >/dev/null 2>&1 & 57 | FFMPEG_PID=$! 58 | 59 | trap trap_ctrlc INT 60 | 61 | printf '\x1b[?25l' # hides the cursor 62 | for img in "$(# increasing this too much will break it 63 | seq 1 999999 64 | )"; do 65 | count=0 66 | while ! [ -f "$DIR/out_$img.png" ]; do 67 | sleep .05 68 | count=$((count + 1)) 69 | if [ "$count" -ge "$WAIT" ]; then break; fi 70 | done 71 | printf '\x1b[s' # saves cursor position 72 | target/debug/macchina --custom-ascii <(jp2a --color --width=50 "$DIR/out_$img".png) 73 | # jp2a --color --width=50 $DIR/out_$img.png # just display the video wihout macchina 74 | printf '\x1b[u' 75 | if [ -f "$DIR/out_$img.png" ]; then 76 | rm -f "$DIR/out_$img".png 77 | fi 78 | sleep .02 79 | done 80 | 81 | printf '\x1b[?25h' # shows cursor 82 | 83 | if [ -n "$FFMPEG_PID" -a -d "/proc/$FFMPEG_PID" ]; then 84 | kill -0 "$FFMPEG_PID" 85 | wait "$FFMPEG_PID" 86 | fi 87 | 88 | if [ -n "$DIR" -a -d "$DIR" ]; then 89 | rm -rf "$DIR" 2>/dev/null 90 | fi 91 | 92 | wait "$FFMPEG_PID" 93 | -------------------------------------------------------------------------------- /doc/macchina.1.scd: -------------------------------------------------------------------------------- 1 | MACCHINA(1) 2 | 3 | # NAME 4 | 5 | macchina - A system information frontend, with an (unhealthy) emphasis 6 | on performance. 7 | 8 | # SYNOPSIS 9 | 10 | *macchina* [FLAGS] 11 | 12 | # FLAGS 13 | 14 | *-d, --doctor* 15 | Checks the system for failures. 16 | 17 | *-e, --export-config* 18 | Prints a template configuration file to stdout. 19 | 20 | *-c, --config*=_FILE_ 21 | Specify a custom path to a configuration file. 22 | 23 | This is helpful in cases where macchina fails to parse the configuration 24 | file from the default path, i.e. *$XDG_CONFIG_HOME/macchina/macchina.toml*. 25 | 26 | *-l, --list-themes* 27 | Lists all available themes. 28 | 29 | Themes are *TOML* files which must be placed in 30 | *$XDG_CONFIG_HOME/macchina/themes*. Whatever this flag 31 | returns to the console can be used with the *--theme* flag. 32 | 33 | *-t, --theme*=_THEME_ 34 | Specify the name of the theme to use, i.e. its basename and 35 | without the ".toml" extension. 36 | 37 | You should verify whether macchina was able to find your theme 38 | with *--list-themes*. 39 | 40 | *-i, --interface*=_IF_NAME_ 41 | Specify the network interface for the LocalIP readout, e.g. "wlan0", "eth0". 42 | 43 | *-s, --current-shell* 44 | Toggles between the current shell, i.e. the parent of the terminal emulator, or the default one. 45 | 46 | *-C, --logical-cores* 47 | Toggles between logical and physical cores for the Processor readout. 48 | 49 | *-K, --long-kernel* 50 | Lengthens kernel output. 51 | 52 | *-S, --long-shell* 53 | Lengthens shell output. 54 | 55 | *-U, --long-uptime* 56 | Lengthens uptime output. 57 | 58 | *-h, --help* 59 | Prints help information. 60 | 61 | *-v, --version* 62 | Prints version information. 63 | 64 | *-o, --show* 65 | Display only the specified readouts. 66 | 67 | Please note that the order these are listed in will be the order that they are 68 | displayed in. 69 | 70 | Possible values are (case-sensitive): 71 | - Host 72 | - Machine 73 | - Kernel 74 | - Distribution 75 | - OperatingSystem 76 | - DesktopEnvironment 77 | - WindowManager 78 | - Packages 79 | - Shell 80 | - Terminal 81 | - LocalIP 82 | - Backlight 83 | - Resolution 84 | - Uptime 85 | - Processor 86 | - ProcessorLoad 87 | - Memory 88 | - Battery 89 | 90 | *--ascii-artists* 91 | Lists the original artists of the ASCII art used by macchina. 92 | 93 | # SEE ALSO 94 | 95 | macchina(7) 96 | 97 | # AUTHORS 98 | 99 | Written and maintained by the Macchina-CLI team: 100 | - Aziz Ben Ali 101 | - Uttarayan Mondal 102 | - Marvin Haschker 103 | 104 | # RESOURCES 105 | 106 | This project is hosted at *https://github.com/Macchina-CLI/macchina*. 107 | -------------------------------------------------------------------------------- /src/doctor.rs: -------------------------------------------------------------------------------- 1 | use crate::data::Readout; 2 | use colored::Colorize; 3 | use libmacchina::traits::ReadoutError; 4 | 5 | #[cfg(windows)] 6 | fn activate_virtual_terminal() { 7 | colored::control::set_virtual_terminal(true).expect("Could not activate virtual terminal."); 8 | } 9 | 10 | fn split_failed_items<'a>( 11 | failed_items: &'a [&Readout], 12 | ) -> (Vec<&'a Readout<'a>>, Vec<&'a Readout<'a>>) { 13 | let err_items: Vec<_> = failed_items 14 | .iter() 15 | .filter(|p| !matches!(p.1.as_ref().err(), Some(ReadoutError::Warning(_)))) 16 | .copied() 17 | .collect(); 18 | 19 | let warn_items: Vec<_> = failed_items 20 | .iter() 21 | .filter(|p| matches!(p.1.as_ref().err(), Some(ReadoutError::Warning(_)))) 22 | .copied() 23 | .collect(); 24 | 25 | (err_items, warn_items) 26 | } 27 | 28 | fn print_errors<'a>(err_items: &[&'a Readout<'a>]) { 29 | if err_items.is_empty() { 30 | println!("🎉 You are good to go! No failures detected."); 31 | } 32 | 33 | for failed_item in err_items { 34 | let key = failed_item.0; 35 | let error = failed_item.1.as_ref().err().unwrap().to_string(); 36 | 37 | println!( 38 | "Readout \"{}\" failed with message: {}", 39 | key.to_string().bright_blue(), 40 | error.bright_red() 41 | ); 42 | } 43 | } 44 | 45 | fn print_warnings<'a>(warn_items: &[&'a Readout<'a>], total_failed_items: usize) { 46 | if warn_items.is_empty() { 47 | return; 48 | } 49 | 50 | let warn_len = warn_items.len().to_string().bright_yellow(); 51 | let err_len = total_failed_items.to_string().bright_red(); 52 | println!("\n{warn_len} of the {err_len} unsuccessful read(s) resulted in a warning:"); 53 | 54 | for warn_item in warn_items { 55 | let key = warn_item.0; 56 | let warn = warn_item.1.as_ref().err().unwrap().to_string(); 57 | 58 | println!( 59 | "Readout \"{}\" threw a warning with message: {}", 60 | key.to_string().bright_blue(), 61 | warn.yellow() 62 | ); 63 | } 64 | } 65 | 66 | pub(crate) fn print_doctor(data: &[Readout]) { 67 | let failed_items: Vec<_> = data.iter().filter(|p| p.1.is_err()).collect(); 68 | let (err_items, warn_items) = split_failed_items(&failed_items); 69 | 70 | #[cfg(windows)] 71 | activate_virtual_terminal(); 72 | 73 | println!( 74 | "Let's check your system for {}... Here's a summary:\n", 75 | "errors".bright_red() 76 | ); 77 | 78 | println!( 79 | "We've collected {} {}, including {} {} and {} read(s) which resulted in a {}.", 80 | data.len().to_string().bright_green(), 81 | "readouts".bright_green(), 82 | err_items.len().to_string().bright_red(), 83 | "failed read(s)".bright_red(), 84 | warn_items.len(), 85 | "warning".bright_yellow() 86 | ); 87 | 88 | print_errors(&err_items); 89 | print_warnings(&warn_items, failed_items.len()); 90 | } 91 | -------------------------------------------------------------------------------- /.github/workflows/macchina.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: CI 4 | 5 | jobs: 6 | checks: 7 | name: ${{ matrix.name }} (${{ matrix.target }}) 8 | runs-on: ${{ matrix.os }} 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | target: 13 | - x86_64-unknown-linux-gnu 14 | - x86_64-apple-darwin 15 | - x86_64-pc-windows-msvc 16 | - x86_64-unknown-netbsd 17 | - aarch64-linux-android 18 | - aarch64-unknown-linux-gnu 19 | 20 | include: 21 | - os: ubuntu-latest 22 | name: Linux 23 | target: x86_64-unknown-linux-gnu 24 | cross: false 25 | strip: true 26 | 27 | - os: macos-latest 28 | name: macOS 29 | target: x86_64-apple-darwin 30 | cross: false 31 | strip: true 32 | 33 | - os: windows-latest 34 | name: Windows 35 | target: x86_64-pc-windows-msvc 36 | cross: false 37 | strip: true 38 | 39 | - os: ubuntu-latest 40 | name: NetBSD 41 | target: x86_64-unknown-netbsd 42 | cross: true 43 | strip: true 44 | 45 | - os: ubuntu-latest 46 | name: Android 47 | target: aarch64-linux-android 48 | cross: true 49 | strip: true 50 | 51 | - os: ubuntu-latest 52 | name: Linux 53 | target: aarch64-unknown-linux-gnu 54 | cross: true 55 | strip: true 56 | 57 | steps: 58 | - name: Checkout 59 | uses: actions/checkout@v2 60 | 61 | - name: Bootstrap 62 | uses: actions-rs/toolchain@v1 63 | with: 64 | toolchain: stable 65 | components: rustfmt, clippy 66 | target: ${{ matrix.target }} 67 | 68 | - name: Formatting 69 | uses: actions-rs/cargo@v1 70 | with: 71 | command: fmt 72 | use-cross: ${{ matrix.cross }} 73 | continue-on-error: false 74 | 75 | - name: Lints 76 | uses: actions-rs/cargo@v1 77 | with: 78 | command: clippy 79 | args: --target=${{ matrix.target }} -- --no-deps -D clippy::all 80 | use-cross: ${{ matrix.cross }} 81 | continue-on-error: false 82 | 83 | - name: Build 84 | uses: actions-rs/cargo@v1 85 | with: 86 | command: build 87 | args: --target=${{ matrix.target }} 88 | use-cross: ${{ matrix.cross }} 89 | 90 | - name: Test 91 | uses: actions-rs/cargo@v1 92 | with: 93 | command: test 94 | args: --target=${{ matrix.target }} 95 | if: ${{ !matrix.cross }} 96 | 97 | - name: Doctor 98 | uses: actions-rs/cargo@v1 99 | with: 100 | command: run 101 | args: --target=${{ matrix.target }} -- --doctor 102 | use-cross: ${{ matrix.cross }} 103 | if: ${{ matrix.test }} 104 | -------------------------------------------------------------------------------- /.github/workflows/macchina-release-tag.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: 4 | - 'v*' 5 | 6 | name: Release 7 | 8 | jobs: 9 | changelog: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v2 14 | - name: Create Release 15 | uses: softprops/action-gh-release@v1 16 | with: 17 | body_path: CHANGELOG.md 18 | env: 19 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 20 | publish: 21 | name: ${{ matrix.name }} (${{ matrix.target }}) 22 | runs-on: ${{ matrix.os }} 23 | strategy: 24 | fail-fast: false 25 | matrix: 26 | target: 27 | - x86_64-unknown-linux-gnu 28 | - x86_64-apple-darwin 29 | - x86_64-pc-windows-msvc 30 | - x86_64-unknown-netbsd 31 | - aarch64-linux-android 32 | - aarch64-unknown-linux-gnu 33 | include: 34 | - os: ubuntu-latest 35 | name: Linux 36 | target: x86_64-unknown-linux-gnu 37 | artifact_name: target/x86_64-unknown-linux-gnu/release/macchina 38 | release_name: macchina-linux-x86_64 39 | cross: false 40 | strip: true 41 | 42 | - os: macos-latest 43 | name: Macos 44 | target: x86_64-apple-darwin 45 | artifact_name: target/x86_64-apple-darwin/release/macchina 46 | release_name: macchina-macos-x86_64 47 | cross: false 48 | strip: true 49 | 50 | - os: windows-latest 51 | name: Windows 52 | target: x86_64-pc-windows-msvc 53 | artifact_name: target/x86_64-pc-windows-msvc/release/macchina.exe 54 | release_name: macchina-windows-x86_64.exe 55 | cross: false 56 | strip: true 57 | 58 | - os: ubuntu-latest 59 | name: NetBSD 60 | target: x86_64-unknown-netbsd 61 | artifact_name: target/x86_64-unknown-netbsd/release/macchina 62 | release_name: macchina-netbsd-x86_64 63 | cross: true 64 | strip: true 65 | 66 | - os: ubuntu-latest 67 | name: Android 68 | target: aarch64-linux-android 69 | artifact_name: target/aarch64-linux-android/release/macchina 70 | release_name: macchina-android-aarch64 71 | cross: true 72 | strip: true 73 | 74 | - os: ubuntu-latest 75 | name: Linux aarch64 76 | target: aarch64-unknown-linux-gnu 77 | artifact_name: target/aarch64-unknown-linux-gnu/release/macchina 78 | release_name: macchina-linux-aarch64 79 | cross: true 80 | strip: true 81 | 82 | steps: 83 | - name: Checkout 84 | uses: actions/checkout@v2 85 | 86 | - name: Bootstrap 87 | uses: actions-rs/toolchain@v1 88 | with: 89 | toolchain: stable 90 | target: ${{ matrix.target }} 91 | 92 | - name: Build 93 | uses: actions-rs/cargo@v1 94 | with: 95 | command: build 96 | args: --target=${{ matrix.target }} --release 97 | use-cross: ${{ matrix.cross }} 98 | 99 | - name: Rename binaries 100 | run: mv ${{ matrix.artifact_name }} ${{ matrix.release_name }} 101 | 102 | - name: Upload binaries 103 | uses: softprops/action-gh-release@v1 104 | with: 105 | files: ${{ matrix.release_name }} 106 | env: 107 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 108 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![warn(clippy::all)] 2 | 3 | mod ascii; 4 | mod bars; 5 | mod buffer; 6 | mod cli; 7 | mod config; 8 | mod data; 9 | mod doctor; 10 | mod error; 11 | mod extra; 12 | mod format; 13 | pub mod theme; 14 | pub mod widgets; 15 | 16 | use cli::{Opt, PKG_NAME}; 17 | use error::Result; 18 | use tui::{backend::Backend, buffer::Buffer, layout::Rect}; 19 | 20 | #[macro_use] 21 | extern crate lazy_static; 22 | 23 | fn main() -> Result<()> { 24 | let opt = Opt::get_options(); 25 | 26 | if opt.version { 27 | get_version(); 28 | return Ok(()); 29 | } 30 | 31 | if opt.ascii_artists { 32 | ascii::list_ascii_artists(); 33 | return Ok(()); 34 | } 35 | 36 | if opt.list_themes { 37 | theme::list_themes(&opt); 38 | return Ok(()); 39 | } 40 | 41 | let theme = theme::create_theme(&opt); 42 | let should_display = data::should_display(&opt); 43 | let readout_data = data::get_all_readouts(&opt, &theme, &should_display); 44 | 45 | if opt.doctor { 46 | doctor::print_doctor(&readout_data); 47 | return Ok(()); 48 | } 49 | 50 | const MAX_ASCII_HEIGHT: usize = 50; 51 | const MINIMUM_READOUTS_TO_PREFER_SMALL_ASCII: usize = 8; 52 | let mut backend = buffer::create_backend(); 53 | let mut tmp_buffer = Buffer::empty(Rect::new(0, 0, 500, 50)); 54 | let mut ascii_area = Rect::new(0, 1, 0, tmp_buffer.area.height - 1); 55 | let prefers_small_ascii = 56 | readout_data.len() < MINIMUM_READOUTS_TO_PREFER_SMALL_ASCII || theme.prefers_small_ascii(); 57 | 58 | if theme.is_ascii_visible() { 59 | if let Some(path) = theme.get_custom_ascii().get_path() { 60 | let expanded = shellexpand::tilde(&path.to_string_lossy()).to_string(); 61 | let file_path = std::path::PathBuf::from(expanded); 62 | let ascii_art = if let Some(color) = theme.get_custom_ascii().get_color() { 63 | ascii::get_ascii_from_file_override_color(&file_path, color)? 64 | } else { 65 | ascii::get_ascii_from_file(&file_path)? 66 | }; 67 | 68 | if ascii_art.width() != 0 && ascii_art.height() < MAX_ASCII_HEIGHT { 69 | ascii_area = buffer::draw_ascii(ascii_art, &mut tmp_buffer); 70 | } 71 | } else if prefers_small_ascii { 72 | // prefer smaller ascii in this case 73 | if let Some(ascii) = ascii::select_ascii(ascii::AsciiSize::Small) { 74 | ascii_area = buffer::draw_ascii(ascii, &mut tmp_buffer); 75 | } 76 | } else { 77 | // prefer bigger ascii otherwise 78 | if let Some(ascii) = ascii::select_ascii(ascii::AsciiSize::Big) { 79 | ascii_area = buffer::draw_ascii(ascii, &mut tmp_buffer); 80 | } 81 | } 82 | } 83 | 84 | let tmp_buffer_area = tmp_buffer.area; 85 | 86 | buffer::draw_readout_data( 87 | readout_data, 88 | theme, 89 | &mut tmp_buffer, 90 | Rect::new( 91 | ascii_area.x + ascii_area.width + 2, 92 | ascii_area.y, 93 | tmp_buffer_area.width - ascii_area.width - 4, 94 | ascii_area.height, 95 | ), 96 | ); 97 | 98 | buffer::write_buffer_to_console(&mut backend, &mut tmp_buffer)?; 99 | 100 | backend.flush()?; 101 | print!("\n\n"); 102 | 103 | Ok(()) 104 | } 105 | 106 | fn get_version() { 107 | if let Some(git_sha) = option_env!("VERGEN_GIT_SHA_SHORT") { 108 | println!( 109 | "{} {} ({})", 110 | PKG_NAME, 111 | env!("CARGO_PKG_VERSION"), 112 | git_sha 113 | ); 114 | } else { 115 | println!("{} {}", PKG_NAME, env!("CARGO_PKG_VERSION")); 116 | } 117 | 118 | println!("libmacchina {}", libmacchina::version()); 119 | } 120 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

macchina

3 | 4 | Fast, minimal and customizable system information frontend. 5 | 6 | Linux • macOS • Windows • NetBSD • FreeBSD • OpenWrt • Android 7 | 8 | Preview 9 |
10 | 11 | ## About 12 | 13 | [macchina](https://crates.io/crates/macchina) lets you view system 14 | information, like your kernel version, uptime, memory usage, processor 15 | load and much more. _macchina_ is basic by default and extensible by 16 | design. 17 | 18 | If you're interested in the library _macchina_ uses to fetch system 19 | information, have a look at [libmacchina]; fetching-related issues 20 | should be filed on that repository. 21 | 22 | ## Status 23 | 24 | _macchina_ is currently in **maintenance mode**, meaning bug fixes and 25 | little optimizations are much more prioritized over the addition of 26 | new features. This is due to the fact that we (the authors) do not 27 | have the time to focus on moving the project forward, or expanding on 28 | what is currently offered while keeping up with the demanding nature 29 | of our lives. _macchina_ will at some point (although I can't say 30 | when) leave this stage and implement all the bells and whistles the 31 | community has been requesting. 32 | 33 | We hope you understand our situation and continue to support _macchina_. 34 | 35 | ## Benchmarks 36 | 37 | Check out the [benchmarks wiki page](https://github.com/Macchina-CLI/macchina/wiki/Benchmarks). 38 | 39 | ## Features 40 | 41 | ### Themes 42 | 43 | _macchina_ has a theming system which you can use to customize pretty much any 44 | visual aspect of the program. Themes live **outside** the configuration file, 45 | so you can create a bunch of them and switch between them at any time. 46 | 47 | Why are they separate? 48 | 49 | - **Modularity** — themes are an engine of their own, and their sole purpose is 50 | to provide an interface that allows for the modification of _macchina's_ 51 | visual components. It makes sense to separate them from the main 52 | configuration file. 53 | 54 | - **Portability** — sure, the configuration file is shareable, but what if you 55 | wanted to share the look of _your macchina_ and not its behavior? What if you 56 | wanted to switch between dozens of themes that you very carefully designed? 57 | The way we handle customization answers this need. 58 | 59 | Learn how to [make your own](#customization). 60 | 61 | ### Doctor 62 | 63 | In the event of fetching failures, which can occur for various reasons, the 64 | `--doctor` flag that can tell you why that might be happening. 65 | 66 | ## Configuration 67 | 68 | See the [configuration wiki page](https://github.com/Macchina-CLI/macchina/wiki/Configuration). 69 | 70 | ## Customization 71 | 72 | Have a look at the [customization wiki page](https://github.com/Macchina-CLI/macchina/wiki/Customization). 73 | 74 | ## Installation 75 | 76 | Check out the [installation wiki page](https://github.com/Macchina-CLI/macchina/wiki/Installation). 77 | We also provide [prebuilt binaries](https://github.com/grtcdr/macchina/releases) with every release. 78 | 79 | ## Contributors 80 | 81 | _macchina_ like many other open source projects, would not be where it 82 | is right now without the help of its contributors. Whether you've 83 | helped drive it forward by contributing to the codebase, packaged it 84 | so we didn't have to, or recommended it to someone you know — we truly 85 | appreciate your support! 86 | 87 | The following is a list of awesome people that have truly shaped _macchina_: 88 | - [pin](https://pkgsrc.se/bbmaint.php?maint=pin@NetBSD.org): Provided 89 | massive amounts of help, feedback and testing, and is currently 90 | packaging _macchina_ on NetBSD. 91 | - [123marvin123](https://github.com/123marvin123): Co-author of _(lib)macchina_ and 92 | author of countless high-quality contributions and primarily, support for 93 | macOS and Windows. 94 | - [uttarayan21](https://github.com/uttarayan21): Co-author of _(lib)macchina_ and 95 | author of numerous shipshape contributions and primarily, support for Android and OpenWrt. 96 | 97 | Looking to help? [Read this first.](.github/CONTRIBUTING.md) 98 | 99 | [libmacchina]: https://github.com/Macchina-CLI/libmacchina 100 | -------------------------------------------------------------------------------- /src/buffer.rs: -------------------------------------------------------------------------------- 1 | use crate::data::Readout; 2 | use crate::theme::Theme; 3 | use crate::widgets::readout::ReadoutList; 4 | use atty::Stream; 5 | use std::io; 6 | use std::io::Stdout; 7 | use tui::backend::{Backend, CrosstermBackend}; 8 | use tui::buffer::{Buffer, Cell}; 9 | use tui::layout::{Margin, Rect}; 10 | use tui::text::Text; 11 | use tui::widgets::{Block, Borders, Paragraph, Widget}; 12 | use unicode_width::UnicodeWidthStr; 13 | 14 | pub fn create_backend() -> CrosstermBackend { 15 | CrosstermBackend::new(io::stdout()) 16 | } 17 | 18 | pub fn find_widest_cell(buf: &Buffer, last_y: u16) -> u16 { 19 | let area = &buf.area; 20 | let mut widest: u16 = 0; 21 | let empty_cell = Cell::default(); 22 | 23 | for y in 0..last_y { 24 | for x in (0..area.width).rev() { 25 | let current_cell = buf.get(x, y); 26 | if current_cell.ne(&empty_cell) && x > widest { 27 | widest = x; 28 | break; 29 | } 30 | } 31 | } 32 | 33 | widest + 1 34 | } 35 | 36 | pub fn find_last_buffer_cell_index(buf: &Buffer) -> Option<(u16, u16)> { 37 | let empty_cell = Cell::default(); 38 | 39 | if let Some((idx, _)) = buf 40 | .content 41 | .iter() 42 | .enumerate() 43 | .filter(|p| !(*(p.1)).eq(&empty_cell)) 44 | .last() 45 | { 46 | return Some(buf.pos_of(idx)); 47 | } 48 | 49 | None 50 | } 51 | 52 | pub fn draw_ascii(ascii: Text<'static>, tmp_buffer: &mut Buffer) -> Rect { 53 | let ascii_rect = Rect { 54 | x: 1, 55 | y: 1, 56 | width: ascii.width() as u16, 57 | height: ascii.height() as u16, 58 | }; 59 | 60 | Paragraph::new(ascii).render(ascii_rect, tmp_buffer); 61 | ascii_rect 62 | } 63 | 64 | pub fn draw_readout_data(data: Vec, theme: Theme, buf: &mut Buffer, area: Rect) { 65 | let mut list = ReadoutList::new(data, &theme); 66 | 67 | if theme.get_block().is_visible() { 68 | list = list 69 | .block_inner_margin(Margin { 70 | horizontal: theme.get_block().get_horizontal_margin(), 71 | vertical: theme.get_block().get_vertical_margin(), 72 | }) 73 | .block( 74 | Block::default() 75 | .border_type(theme.get_block().get_border_type()) 76 | .title(theme.get_block().get_title()) 77 | .borders(Borders::ALL), 78 | ); 79 | } 80 | 81 | list.render(area, buf); 82 | } 83 | 84 | pub fn write_buffer_to_console( 85 | backend: &mut CrosstermBackend, 86 | tmp_buffer: &mut Buffer, 87 | ) -> Result<(), io::Error> { 88 | let term_size = backend.size().unwrap_or_default(); 89 | 90 | let (_, last_y) = find_last_buffer_cell_index(tmp_buffer) 91 | .expect("An error occurred while writing to the terminal buffer."); 92 | 93 | let last_x = find_widest_cell(tmp_buffer, last_y); 94 | 95 | print!("{}", "\n".repeat(last_y as usize + 1)); 96 | 97 | let mut cursor_y: u16 = 0; 98 | 99 | if atty::is(Stream::Stdout) { 100 | cursor_y = backend.get_cursor().unwrap_or((0, 0)).1; 101 | } 102 | 103 | // we need a checked subtraction here, because (cursor_y - last_y - 1) might underflow if the 104 | // cursor_y is smaller than (last_y - 1). 105 | let starting_pos = cursor_y.saturating_sub(last_y).saturating_sub(1); 106 | let mut skip_n = 0; 107 | 108 | let iter = tmp_buffer 109 | .content 110 | .iter() 111 | .enumerate() 112 | .filter(|(_previous, cell)| { 113 | let curr_width = cell.symbol.width(); 114 | if curr_width == 0 { 115 | return false; 116 | } 117 | 118 | let old_skip = skip_n; 119 | skip_n = curr_width.saturating_sub(1); 120 | old_skip == 0 121 | }) 122 | .map(|(idx, cell)| { 123 | let (x, y) = tmp_buffer.pos_of(idx); 124 | (x, y, cell) 125 | }) 126 | .filter(|(x, y, _)| *x < last_x && *x < term_size.width && *y <= last_y) 127 | .map(|(x, y, cell)| (x, y + starting_pos, cell)); 128 | 129 | backend.draw(iter)?; 130 | Ok(()) 131 | } 132 | -------------------------------------------------------------------------------- /src/cli.rs: -------------------------------------------------------------------------------- 1 | use crate::config; 2 | use crate::data; 3 | use crate::error; 4 | use clap::Parser; 5 | use serde::{Deserialize, Serialize}; 6 | use std::default::Default; 7 | 8 | pub const PKG_NAME: &str = env!("CARGO_PKG_NAME"); 9 | 10 | #[derive(Parser, Debug, Default, Serialize, Deserialize)] 11 | #[serde(default, deny_unknown_fields)] 12 | pub struct Opt { 13 | #[clap( 14 | short = 'v', 15 | long = "version", 16 | help = "Prints version information", 17 | conflicts_with = "doctor" 18 | )] 19 | #[serde(skip_serializing, skip_deserializing)] 20 | pub version: bool, 21 | 22 | #[clap( 23 | short = 'o', 24 | long = "show", 25 | help = "Displays only the specified readouts", 26 | hide_possible_values = true 27 | )] 28 | pub show: Option>, 29 | 30 | #[clap(short = 'd', long = "doctor", help = "Checks the system for failures")] 31 | #[serde(skip_serializing, skip_deserializing)] 32 | pub doctor: bool, 33 | 34 | #[clap(short = 'U', long = "long-uptime", help = "Lengthens uptime output")] 35 | pub long_uptime: bool, 36 | 37 | #[clap(short = 'S', long = "long-shell", help = "Lengthens shell output")] 38 | pub long_shell: bool, 39 | 40 | #[clap(short = 'K', long = "long-kernel", help = "Lengthens kernel output")] 41 | pub long_kernel: bool, 42 | 43 | #[clap( 44 | short = 'C', 45 | long = "physical-cores", 46 | help = "Toggles between logical and physical cores" 47 | )] 48 | pub physical_cores: bool, 49 | 50 | #[clap( 51 | short = 's', 52 | long = "current-shell", 53 | help = "Toggles between the current shell and the default one" 54 | )] 55 | pub current_shell: bool, 56 | 57 | #[clap(short = 't', long = "theme", help = "Specify the name of the theme")] 58 | pub theme: Option, 59 | 60 | #[clap( 61 | long = "list-themes", 62 | short = 'l', 63 | help = "Lists all available themes: built-in and custom" 64 | )] 65 | #[serde(skip_serializing, skip_deserializing)] 66 | pub list_themes: bool, 67 | 68 | #[clap( 69 | long = "config", 70 | short = 'c', 71 | help = "Specify a custom path for the configuration file" 72 | )] 73 | #[serde(skip_serializing, skip_deserializing)] 74 | pub config: Option, 75 | 76 | #[clap( 77 | long = "ascii-artists", 78 | help = "Lists the original artists of the ASCII art used by macchina" 79 | )] 80 | #[serde(skip_serializing, skip_deserializing)] 81 | pub ascii_artists: bool, 82 | 83 | #[clap( 84 | long = "interface", 85 | short = 'i', 86 | help = "Specify the network interface for the LocalIP readout" 87 | )] 88 | pub interface: Option, 89 | } 90 | 91 | impl Opt { 92 | pub fn parse_args(&mut self, args: Opt) { 93 | if args.version { 94 | self.version = true; 95 | } 96 | 97 | if args.doctor { 98 | self.doctor = true; 99 | } 100 | 101 | if args.current_shell { 102 | self.current_shell = true; 103 | } 104 | 105 | if args.long_shell { 106 | self.long_shell = true; 107 | } 108 | 109 | if args.long_uptime { 110 | self.long_uptime = true; 111 | } 112 | 113 | if args.list_themes { 114 | self.list_themes = true; 115 | } 116 | 117 | if args.long_kernel { 118 | self.long_shell = true; 119 | } 120 | 121 | if args.physical_cores { 122 | self.physical_cores = true; 123 | } 124 | 125 | if args.ascii_artists { 126 | self.ascii_artists = true; 127 | } 128 | 129 | if args.config.is_some() { 130 | self.config = args.config; 131 | } 132 | 133 | if args.theme.is_some() { 134 | self.theme = args.theme; 135 | } 136 | 137 | if args.show.is_some() { 138 | self.show = args.show; 139 | } 140 | 141 | if args.interface.is_some() { 142 | self.interface = args.interface; 143 | } 144 | } 145 | 146 | pub fn get_options() -> Opt { 147 | let args = Opt::parse(); 148 | let config_opt = match args.config { 149 | Some(_) => config::read_config(&args.config.clone().unwrap()), 150 | None => config::get_config(), 151 | }; 152 | 153 | match config_opt { 154 | Ok(mut config) => { 155 | config.parse_args(args); 156 | config 157 | } 158 | Err(e) => { 159 | error::print_errors(e); 160 | args 161 | } 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/format.rs: -------------------------------------------------------------------------------- 1 | use bytesize::ByteSize; 2 | use libmacchina::traits::{BatteryState, PackageManager, ReadoutError}; 3 | 4 | /// This function should return a new `String` constructed from the value \ 5 | /// returned by `traits::GeneralReadout::uptime()` 6 | pub fn uptime(uptime: usize, long: bool) -> String { 7 | let mut fmt = String::new(); 8 | let uptime: f32 = uptime as f32; 9 | // uptime is formatted to "x days, y hours, z minutes" if the system 10 | // has been up for longer than 60 seconds, and "x seconds" if not. 11 | 12 | // "x days", "y hours" or "z minutes" might not show up if their value is 0. 13 | // for example, if the system has been up for less than a day, 14 | // this function will return "y hours, z minutes". 15 | if uptime > 60.0 { 16 | let up_days = (uptime / 60.0 / 60.0 / 24.0).floor(); 17 | let up_hours = (uptime / 60.0 / 60.0 % 24.0).floor(); 18 | let up_minutes = (uptime / 60.0 % 60.0).floor(); 19 | match long { 20 | false => { 21 | if up_days != 0.0 { 22 | fmt.push_str(&up_days.to_string()); 23 | fmt.push_str("d "); 24 | } 25 | if up_hours != 0.0 { 26 | fmt.push_str(&up_hours.to_string()); 27 | fmt.push_str("h "); 28 | } 29 | if up_minutes != 0.0 { 30 | fmt.push_str(&up_minutes.to_string()); 31 | fmt.push('m'); 32 | } 33 | } 34 | true => { 35 | if up_days != 0.0 { 36 | fmt.push_str(&up_days.to_string()); 37 | if (up_days - 1.0).abs() < 0.001 { 38 | fmt.push_str(" day "); 39 | } else { 40 | fmt.push_str(" days "); 41 | } 42 | } 43 | if up_hours != 0.0 { 44 | fmt.push_str(&up_hours.to_string()); 45 | if (up_hours - 1.0).abs() < 0.001 { 46 | fmt.push_str(" hour "); 47 | } else { 48 | fmt.push_str(" hours "); 49 | } 50 | } 51 | if up_minutes != 0.0 { 52 | fmt.push_str(&up_minutes.to_string()); 53 | if (up_minutes - 1.0).abs() < 0.001 { 54 | fmt.push_str(" minute"); 55 | } else { 56 | fmt.push_str(" minutes"); 57 | } 58 | } 59 | } 60 | } 61 | } 62 | // uptime is formatted to seconds only if the 63 | // system has been up for fewer than 60 seconds 64 | else { 65 | let up_seconds = (uptime % 60.0).floor(); 66 | if up_seconds != 0.0 { 67 | fmt = up_seconds.to_string(); 68 | fmt.push('s'); 69 | } 70 | } 71 | 72 | fmt.trim().to_string() 73 | } 74 | 75 | /// This function should return a new `String` constructed from the values \ 76 | /// returned by `traits::GeneralReadout::username()` and `traits::GeneralReadout::hostname()` 77 | pub fn host(username: &str, hostname: &str) -> String { 78 | format!("{username}@{hostname}") 79 | } 80 | 81 | /// This function should return a new `String` constructed from the values \ 82 | /// returned by `traits::BatteryReadout::percentage()` and `traits::BatteryReadout::status()` 83 | pub fn battery(percentage: u8, state: BatteryState) -> String { 84 | // Holds either "Charging" or "Discharging" values 85 | if percentage != 100 { 86 | format!("{}% & {}", percentage, Into::<&'static str>::into(state)) 87 | } else { 88 | String::from("Full") 89 | } 90 | } 91 | 92 | /// This function should return a new `String` constructed from the values \ 93 | /// returned by `traits::MemoryReadout::used()` and `traits::MemoryReadout::total()` 94 | pub fn memory(total: u64, used: u64) -> String { 95 | let total = ByteSize::kb(total); 96 | let used = ByteSize::kb(used); 97 | 98 | format!("{used}/{total}") 99 | } 100 | 101 | /// This function should return a new `String` constructed from the value \ 102 | /// returned by `traits::GeneralReadout::cpu_model_name()` 103 | pub fn cpu_only(model_name: &str) -> String { 104 | model_name.replace("(TM)", "™").replace("(R)", "®") 105 | } 106 | 107 | pub fn cpu(model_name: &str, cpu_cores: usize) -> String { 108 | format!("{} ({})", cpu_only(model_name), cpu_cores) 109 | } 110 | 111 | pub fn cpu_usage(used: usize) -> String { 112 | format!("{used}%") 113 | } 114 | 115 | pub fn packages(packages: Vec<(PackageManager, usize)>) -> Result { 116 | let len = packages.len(); 117 | if len == 0 { 118 | return Err(ReadoutError::Other(String::from( 119 | "No packages found — Do you have a package manager installed?", 120 | ))); 121 | } 122 | 123 | // pre-allocate an estimated size to reduce the number 124 | // of reallocations when manipulating the string 125 | let mut string = String::with_capacity(len * 7); 126 | 127 | for (i, (pm, count)) in packages.iter().enumerate() { 128 | let add_comma = if i + 1 < len { ", " } else { "" }; 129 | string.push_str(&format!("{} ({}){}", count, pm.to_string(), add_comma)); 130 | } 131 | 132 | Ok(string) 133 | } 134 | -------------------------------------------------------------------------------- /doc/macchina.1: -------------------------------------------------------------------------------- 1 | .\" Generated by scdoc 1.11.2 2 | .\" Complete documentation for this program is not available as a GNU info page 3 | .ie \n(.g .ds Aq \(aq 4 | .el .ds Aq ' 5 | .nh 6 | .ad l 7 | .\" Begin generated content: 8 | .TH "MACCHINA" "1" "2023-02-27" 9 | .P 10 | .SH NAME 11 | .P 12 | macchina - A system information frontend, with an (unhealthy) emphasis 13 | on performance.\& 14 | .P 15 | .SH SYNOPSIS 16 | .P 17 | \fBmacchina\fR [FLAGS] 18 | .P 19 | .SH FLAGS 20 | .P 21 | \fB-d, --doctor\fR 22 | .RS 4 23 | Checks the system for failures.\& 24 | .P 25 | .RE 26 | \fB-e, --export-config\fR 27 | .RS 4 28 | Prints a template configuration file to stdout.\& 29 | .P 30 | .RE 31 | \fB-c, --config\fR=\fIFILE\fR 32 | .RS 4 33 | Specify a custom path to a configuration file.\& 34 | .P 35 | This is helpful in cases where macchina fails to parse the configuration 36 | file from the default path, i.\&e.\& \fB$XDG_CONFIG_HOME/macchina/macchina.\&toml\fR.\& 37 | .P 38 | .RE 39 | \fB-l, --list-themes\fR 40 | .RS 4 41 | Lists all available themes.\& 42 | .P 43 | Themes are \fBTOML\fR files which must be placed in 44 | \fB$XDG_CONFIG_HOME/macchina/themes\fR.\& Whatever this flag 45 | returns to the console can be used with the \fB--theme\fR flag.\& 46 | .P 47 | .RE 48 | \fB-t, --theme\fR=\fITHEME\fR 49 | .RS 4 50 | Specify the name of the theme to use, i.\&e.\& its basename and 51 | without the ".\&toml" extension.\& 52 | .P 53 | You should verify whether macchina was able to find your theme 54 | with \fB--list-themes\fR.\& 55 | .P 56 | .RE 57 | \fB-i, --interface\fR=\fIIF_NAME\fR 58 | .RS 4 59 | Specify the network interface for the LocalIP readout, e.\&g.\& "wlan0", "eth0".\& 60 | .P 61 | .RE 62 | \fB-s, --current-shell\fR 63 | .RS 4 64 | Toggles between the current shell, i.\&e.\& the parent of the terminal emulator, or the default one.\& 65 | .P 66 | .RE 67 | \fB-C, --logical-cores\fR 68 | .RS 4 69 | Toggles between logical and physical cores for the Processor readout.\& 70 | .P 71 | .RE 72 | \fB-K, --long-kernel\fR 73 | .RS 4 74 | Lengthens kernel output.\& 75 | .P 76 | .RE 77 | \fB-S, --long-shell\fR 78 | .RS 4 79 | Lengthens shell output.\& 80 | .P 81 | .RE 82 | \fB-U, --long-uptime\fR 83 | .RS 4 84 | Lengthens uptime output.\& 85 | .P 86 | .RE 87 | \fB-h, --help\fR 88 | .RS 4 89 | Prints help information.\& 90 | .P 91 | .RE 92 | \fB-v, --version\fR 93 | .RS 4 94 | Prints version information.\& 95 | .P 96 | .RE 97 | \fB-o, --show\fR 98 | .RS 4 99 | Display only the specified readouts.\& 100 | .P 101 | Please note that the order these are listed in will be the order that they are 102 | displayed in.\& 103 | .P 104 | Possible values are (case-sensitive): 105 | .RS 4 106 | .ie n \{\ 107 | \h'-04'\(bu\h'+03'\c 108 | .\} 109 | .el \{\ 110 | .IP \(bu 4 111 | .\} 112 | Host 113 | .RE 114 | .RS 4 115 | .ie n \{\ 116 | \h'-04'\(bu\h'+03'\c 117 | .\} 118 | .el \{\ 119 | .IP \(bu 4 120 | .\} 121 | Machine 122 | .RE 123 | .RS 4 124 | .ie n \{\ 125 | \h'-04'\(bu\h'+03'\c 126 | .\} 127 | .el \{\ 128 | .IP \(bu 4 129 | .\} 130 | Kernel 131 | .RE 132 | .RS 4 133 | .ie n \{\ 134 | \h'-04'\(bu\h'+03'\c 135 | .\} 136 | .el \{\ 137 | .IP \(bu 4 138 | .\} 139 | Distribution 140 | .RE 141 | .RS 4 142 | .ie n \{\ 143 | \h'-04'\(bu\h'+03'\c 144 | .\} 145 | .el \{\ 146 | .IP \(bu 4 147 | .\} 148 | OperatingSystem 149 | .RE 150 | .RS 4 151 | .ie n \{\ 152 | \h'-04'\(bu\h'+03'\c 153 | .\} 154 | .el \{\ 155 | .IP \(bu 4 156 | .\} 157 | DesktopEnvironment 158 | .RE 159 | .RS 4 160 | .ie n \{\ 161 | \h'-04'\(bu\h'+03'\c 162 | .\} 163 | .el \{\ 164 | .IP \(bu 4 165 | .\} 166 | WindowManager 167 | .RE 168 | .RS 4 169 | .ie n \{\ 170 | \h'-04'\(bu\h'+03'\c 171 | .\} 172 | .el \{\ 173 | .IP \(bu 4 174 | .\} 175 | Packages 176 | .RE 177 | .RS 4 178 | .ie n \{\ 179 | \h'-04'\(bu\h'+03'\c 180 | .\} 181 | .el \{\ 182 | .IP \(bu 4 183 | .\} 184 | Shell 185 | .RE 186 | .RS 4 187 | .ie n \{\ 188 | \h'-04'\(bu\h'+03'\c 189 | .\} 190 | .el \{\ 191 | .IP \(bu 4 192 | .\} 193 | Terminal 194 | .RE 195 | .RS 4 196 | .ie n \{\ 197 | \h'-04'\(bu\h'+03'\c 198 | .\} 199 | .el \{\ 200 | .IP \(bu 4 201 | .\} 202 | LocalIP 203 | .RE 204 | .RS 4 205 | .ie n \{\ 206 | \h'-04'\(bu\h'+03'\c 207 | .\} 208 | .el \{\ 209 | .IP \(bu 4 210 | .\} 211 | Backlight 212 | .RE 213 | .RS 4 214 | .ie n \{\ 215 | \h'-04'\(bu\h'+03'\c 216 | .\} 217 | .el \{\ 218 | .IP \(bu 4 219 | .\} 220 | Resolution 221 | .RE 222 | .RS 4 223 | .ie n \{\ 224 | \h'-04'\(bu\h'+03'\c 225 | .\} 226 | .el \{\ 227 | .IP \(bu 4 228 | .\} 229 | Uptime 230 | .RE 231 | .RS 4 232 | .ie n \{\ 233 | \h'-04'\(bu\h'+03'\c 234 | .\} 235 | .el \{\ 236 | .IP \(bu 4 237 | .\} 238 | Processor 239 | .RE 240 | .RS 4 241 | .ie n \{\ 242 | \h'-04'\(bu\h'+03'\c 243 | .\} 244 | .el \{\ 245 | .IP \(bu 4 246 | .\} 247 | ProcessorLoad 248 | .RE 249 | .RS 4 250 | .ie n \{\ 251 | \h'-04'\(bu\h'+03'\c 252 | .\} 253 | .el \{\ 254 | .IP \(bu 4 255 | .\} 256 | Memory 257 | .RE 258 | .RS 4 259 | .ie n \{\ 260 | \h'-04'\(bu\h'+03'\c 261 | .\} 262 | .el \{\ 263 | .IP \(bu 4 264 | .\} 265 | Battery 266 | 267 | .RE 268 | .P 269 | .RE 270 | \fB--ascii-artists\fR 271 | .RS 4 272 | Lists the original artists of the ASCII art used by macchina.\& 273 | .P 274 | .RE 275 | .SH SEE ALSO 276 | .P 277 | macchina(7) 278 | .P 279 | .SH AUTHORS 280 | .P 281 | Written and maintained by the Macchina-CLI team: 282 | .RS 4 283 | .ie n \{\ 284 | \h'-04'\(bu\h'+03'\c 285 | .\} 286 | .el \{\ 287 | .IP \(bu 4 288 | .\} 289 | Aziz Ben Ali 290 | .RE 291 | .RS 4 292 | .ie n \{\ 293 | \h'-04'\(bu\h'+03'\c 294 | .\} 295 | .el \{\ 296 | .IP \(bu 4 297 | .\} 298 | Uttarayan Mondal 299 | .RE 300 | .RS 4 301 | .ie n \{\ 302 | \h'-04'\(bu\h'+03'\c 303 | .\} 304 | .el \{\ 305 | .IP \(bu 4 306 | .\} 307 | Marvin Haschker 308 | 309 | .RE 310 | .P 311 | .SH RESOURCES 312 | .P 313 | This project is hosted at \fBhttps://github.\&com/Macchina-CLI/macchina\fR.\& 314 | -------------------------------------------------------------------------------- /doc/macchina.7.scd: -------------------------------------------------------------------------------- 1 | MACCHINA(7) 2 | 3 | # NAME 4 | macchina - theming. 5 | 6 | # SYNOPSIS 7 | *$XDG_CONFIG_HOME/macchina/themes*, *~/.config/macchina/themes*. 8 | 9 | # DESCRIPTION 10 | Themes are your interface to customizing all visual aspects of macchina. The 11 | following manpage covers everything you need to know to design your own theme. 12 | 13 | # GENERAL OPTIONS 14 | General options should be set at the top of the theme's configuration file, as 15 | they do not belong to any particular section. 16 | 17 | ## spacing 18 | Defines the amount of spacing to leave between 19 | the separator and the content besides it, e.g.: 20 | 21 | spacing = 1 22 | 23 | ## padding 24 | Defines the amount of padding to leave between 25 | the content and its surroundings, e.g.: 26 | 27 | padding = 0 28 | 29 | ## hide_ascii 30 | Disables the rendering of ASCII, whether it be 31 | built-in or custom, e.g.: 32 | 33 | hide_ascii = false 34 | 35 | ## prefer_small_ascii 36 | For built-in ASCII, always use smaller variants, e.g.: 37 | 38 | prefer_small_ascii = true 39 | 40 | ## separator 41 | Defines the glyph to use for the separator, e.g.: 42 | 43 | separator = "-->" 44 | 45 | ## key_color 46 | Defines the color of the keys. 47 | 48 | Accepts hexadecimal values: 49 | 50 | color = "#00FF00" 51 | 52 | Indexed values: 53 | 54 | color = "046" 55 | 56 | Predefined color names, where casing is insensitive: 57 | 58 | color = "Green" 59 | 60 | ## separator_color 61 | Defines the color of the separator. 62 | 63 | Accepts hexadecimal values: 64 | 65 | color = "#00FF00" 66 | 67 | Indexed values: 68 | 69 | color = "046" 70 | 71 | Predefined color names, where casing is insensitive: 72 | 73 | color = "Green" 74 | 75 | # PALETTE SECTION 76 | This section, noted *[palette]*, offers a visual component that displays and 77 | represents the active colorscheme of your terminal emulator. 78 | 79 | ## type 80 | Defines the color set to use for the palette, with possible values of "Dark", 81 | "Light", "Full" (case-sensitive). 82 | 83 | ## glyph 84 | Defines the glyph to use for the palette, e.g.: 85 | 86 | glyph = "() " 87 | 88 | You should append a space to leave some room between the glyphs. 89 | 90 | ## visible 91 | Defines whether to show or hide the palette, e.g.: 92 | 93 | visible = true 94 | 95 | # BAR SECTION 96 | 97 | This section, noted *[bar]*, replaces data that ranges from 0-100% with bars. 98 | 99 | ## glyph 100 | Defines the glyph to use for all bars, e.g.: 101 | 102 | glyph = "o" 103 | 104 | ## symbol_open 105 | Defines the character to use for opening delimiters. Be sure 106 | to surround the value with single quotes and not double quotes, e.g.: 107 | 108 | symbol_open = '(' 109 | 110 | ## symbol_close 111 | Defines the character to use for closing delimiters. Be sure 112 | to surround the value with single quotes and not double quotes, e.g.: 113 | 114 | symbol_close = ')' 115 | 116 | ## visible 117 | Defines whether to show or hide the bars, e.g.: 118 | 119 | visible = true 120 | 121 | ## hide_delimiters 122 | Defines whether to show or hide the bars delimiters, i.e. 123 | the characters that surround the bars themselves, e.g.: 124 | 125 | hide_delimiters = false 126 | 127 | # BOX SECTION 128 | 129 | The section, noted *[box]*, offers a box component which is rendered to surround 130 | your system information. 131 | 132 | ## title 133 | Defines the title of the box, e.g.: 134 | 135 | title = "Hydrogen" 136 | 137 | ## border 138 | Defines the type of border to use for the box, with possible values of "plain", 139 | "thick", "rounded" or "double". 140 | 141 | ## visible 142 | Defines whether to show or hide the box, e.g.: 143 | 144 | visible = true 145 | 146 | # BOX.INNER_MARGIN SECTION 147 | 148 | ## x 149 | Defines the horizontal margin to leave between 150 | the content and the box, e.g.: 151 | 152 | x = 2 153 | 154 | ## y 155 | Defines the vertical margin to leave between the content and the box, e.g.: 156 | 157 | y = 1 158 | 159 | # CUSTOM_ASCII SECTION 160 | This section, noted *[custom_ascii]*, allows you to specify your own ASCII art. 161 | ANSI escape sequences are supported. 162 | 163 | ## color 164 | Defines the color of the ASCII. 165 | 166 | Accepts hexadecimal values: 167 | 168 | color = "#00FF00" 169 | 170 | Indexed values: 171 | 172 | color = "046" 173 | 174 | Predefined color names (case-insensitive): 175 | 176 | color = "Green" 177 | 178 | ## path 179 | Defines the path to a file on your filesystem 180 | which contains the ASCII art you want to display, e.g.: 181 | 182 | path = "~/ascii/arch_linux" 183 | 184 | # RANDOMIZE SECTION 185 | This section, noted *[randomize]*, is used to randomize color selection. 186 | 187 | ## key_color 188 | Defines whether to randomize the color of the keys, e.g.: 189 | 190 | key_color = true 191 | 192 | ## separator_color 193 | Defines whether to randomize the color of the separator, e.g.: 194 | 195 | separator_color = true 196 | 197 | ## pool 198 | Defines the pool of colors from which to pick a random color, with possible 199 | values of "hexadecimal", "indexed" or "base" (case-insensitive). 200 | 201 | - If "hexadecimal" is specified, you'll get a random color ranging 202 | from #000000 to #FFFFFF 203 | 204 | - If "indexed" is specified, you'll get a random color ranging from 0 to 255 205 | 206 | - If "base" is specified, you'll see a random color from the following set of 207 | colors: "black", "white", "red", "green", "blue", "yellow", "magenta" and 208 | "cyan". 209 | 210 | # KEYS SECTION 211 | This section, noted *[keys]*, allows you to modify the text of each key. 212 | 213 | For example, the "Processor" readout, which by default shows up as "CPU" in 214 | macchina's output, can be renamed to whatever you like by setting the "cpu" 215 | option. 216 | 217 | ## host 218 | Defines the text of the Host readout, e.g.: 219 | 220 | host = "Host" 221 | 222 | ## kernel 223 | Defines the text of the Kernel readout, e.g.: 224 | 225 | kernel = "Kernel" 226 | 227 | ## os 228 | Defines the text of the OperatingSystem readout, e.g.: 229 | 230 | os = "OS" 231 | 232 | ## machine 233 | Defines the text of the Machine readout, e.g.: 234 | 235 | machine= "Machine" 236 | 237 | ## de 238 | Defines the text of the DesktopEnvironment readout, e.g.: 239 | 240 | de = "DE" 241 | 242 | ## wm 243 | Defines the text of the WindowManager readout, e.g.: 244 | 245 | wm = "WM" 246 | 247 | ## distro 248 | Defines the text of the Distribution readout, e.g.: 249 | 250 | distro = "Distro" 251 | 252 | ## terminal 253 | Defines the text of the Terminal readout, e.g.: 254 | 255 | terminal = "Term" 256 | 257 | ## shell 258 | Defines the text of the Shell readout, e.g.: 259 | 260 | shell = "Shell" 261 | 262 | ## packages 263 | Defines the text of the Packages readout, e.g.: 264 | 265 | packages = "Packages" 266 | 267 | ## uptime 268 | Defines the text of the Uptime readout, e.g.: 269 | 270 | uptime = "Uptime" 271 | 272 | ## local_ip 273 | Defines the text of the LocalIP readout, e.g.: 274 | 275 | local_ip = "Local IP" 276 | 277 | ## memory 278 | Defines the text of the Memory readout, e.g.: 279 | 280 | memory = "Memory" 281 | 282 | ## battery 283 | Defines the text of the Battery readout, e.g.: 284 | 285 | battery = "Battery" 286 | 287 | ## backlight 288 | Defines the text of the Backlight readout, e.g.: 289 | 290 | backlight = "Brightness" 291 | 292 | ## resolution 293 | Defines the text of the Resolution readout, e.g.: 294 | 295 | resolution = "Resolution" 296 | 297 | ## cpu 298 | Defines the text of the Processor readout, e.g.: 299 | 300 | cpu = "CPU" 301 | 302 | ## cpu_load 303 | Defines the text of the ProcessorLoad readout, e.g.: 304 | 305 | cpu_load = "CPU %" 306 | 307 | # SEE ALSO 308 | macchina(1) 309 | -------------------------------------------------------------------------------- /contrib/ascii/archlinux.ascii: -------------------------------------------------------------------------------- 1 |               ..              2 |               cl              3 |              :ooc             4 |             ;oooo:            5 |            .looooo:           6 |           ;c;:looooc          7 |          :ooooooooooc         8 |         :ooooooooooool        9 |        coooool;;loooool.      10 |      .looooo'    .oooooo.     11 |     .ooooooc      ;oooocl'    12 |    'ooooooo:      'ooooo:,    13 |   ,oool:,..        ..,:looo;  14 |  :c,.                    .,c: 15 | ..                          .' 16 | -------------------------------------------------------------------------------- /doc/macchina.7: -------------------------------------------------------------------------------- 1 | .\" Generated by scdoc 1.11.2 2 | .\" Complete documentation for this program is not available as a GNU info page 3 | .ie \n(.g .ds Aq \(aq 4 | .el .ds Aq ' 5 | .nh 6 | .ad l 7 | .\" Begin generated content: 8 | .TH "MACCHINA" "7" "2023-02-26" 9 | .P 10 | .SH NAME 11 | macchina - theming.\& 12 | .P 13 | .SH SYNOPSIS 14 | \fB$XDG_CONFIG_HOME/macchina/themes\fR, \fB~/.\&config/macchina/themes\fR.\& 15 | .P 16 | .SH DESCRIPTION 17 | Themes are your interface to customizing all visual aspects of macchina.\& The 18 | following manpage covers everything you need to know to design your own theme.\& 19 | .P 20 | .SH GENERAL OPTIONS 21 | General options should be set at the top of the theme'\&s configuration file, as 22 | they do not belong to any particular section.\& 23 | .P 24 | .SS spacing 25 | Defines the amount of spacing to leave between 26 | the separator and the content besides it, e.\&g.\&: 27 | .RS 4 28 | .P 29 | spacing = 1 30 | .P 31 | .RE 32 | .SS padding 33 | Defines the amount of padding to leave between 34 | the content and its surroundings, e.\&g.\&: 35 | .RS 4 36 | .P 37 | padding = 0 38 | .P 39 | .RE 40 | .SS hide_ascii 41 | Disables the rendering of ASCII, whether it be 42 | built-in or custom, e.\&g.\&: 43 | .RS 4 44 | .P 45 | hide_ascii = false 46 | .P 47 | .RE 48 | .SS prefer_small_ascii 49 | For built-in ASCII, always use smaller variants, e.\&g.\&: 50 | .RS 4 51 | .P 52 | prefer_small_ascii = true 53 | .P 54 | .RE 55 | .SS separator 56 | Defines the glyph to use for the separator, e.\&g.\&: 57 | .RS 4 58 | .P 59 | separator = "-->" 60 | .P 61 | .RE 62 | .SS key_color 63 | Defines the color of the keys.\& 64 | .P 65 | Accepts hexadecimal values: 66 | .P 67 | .RS 4 68 | color = "#00FF00" 69 | .P 70 | .RE 71 | Indexed values: 72 | .P 73 | .RS 4 74 | color = "046" 75 | .P 76 | .RE 77 | Predefined color names, where casing is insensitive: 78 | .P 79 | .RS 4 80 | color = "Green" 81 | .P 82 | .RE 83 | .SS separator_color 84 | Defines the color of the separator.\& 85 | .RS 4 86 | .P 87 | .RE 88 | Accepts hexadecimal values: 89 | .P 90 | .RS 4 91 | color = "#00FF00" 92 | .P 93 | .RE 94 | Indexed values: 95 | .P 96 | .RS 4 97 | color = "046" 98 | .P 99 | .RE 100 | Predefined color names, where casing is insensitive: 101 | .P 102 | .RS 4 103 | color = "Green" 104 | .P 105 | .RE 106 | .SH PALETTE SECTION 107 | This section, noted \fB[palette]\fR, offers a visual component that displays and 108 | represents the active colorscheme of your terminal emulator.\& 109 | .P 110 | .SS type 111 | Defines the color set to use for the palette, with possible values of "Dark", 112 | "Light", "Full" (case-sensitive).\& 113 | .P 114 | .SS glyph 115 | Defines the glyph to use for the palette, e.\&g.\&: 116 | .P 117 | .RS 4 118 | glyph = "() " 119 | .P 120 | .RE 121 | You should append a space to leave some room between the glyphs.\& 122 | .P 123 | .SS visible 124 | Defines whether to show or hide the palette, e.\&g.\&: 125 | .RS 4 126 | .P 127 | visible = true 128 | .P 129 | .RE 130 | .SH BAR SECTION 131 | .P 132 | This section, noted \fB[bar]\fR, replaces data that ranges from 0-100% with bars.\& 133 | .P 134 | .SS glyph 135 | Defines the glyph to use for all bars, e.\&g.\&: 136 | .RS 4 137 | .P 138 | glyph = "o" 139 | .P 140 | .RE 141 | .SS symbol_open 142 | Defines the character to use for opening delimiters.\& Be sure 143 | .RS 4 144 | to surround the value with single quotes and not double quotes, e.\&g.\&: 145 | .P 146 | symbol_open = '\&('\& 147 | .P 148 | .RE 149 | .SS symbol_close 150 | Defines the character to use for closing delimiters.\& Be sure 151 | to surround the value with single quotes and not double quotes, e.\&g.\&: 152 | .P 153 | .RS 4 154 | symbol_close = '\&)'\& 155 | .P 156 | .RE 157 | .SS visible 158 | Defines whether to show or hide the bars, e.\&g.\&: 159 | .P 160 | .RS 4 161 | visible = true 162 | .P 163 | .RE 164 | .SS hide_delimiters 165 | Defines whether to show or hide the bars delimiters, i.\&e.\& 166 | the characters that surround the bars themselves, e.\&g.\&: 167 | .P 168 | .RS 4 169 | hide_delimiters = false 170 | .P 171 | .RE 172 | .SH BOX SECTION 173 | .P 174 | The section, noted \fB[box]\fR, offers a box component which is rendered to surround 175 | your system information.\& 176 | .P 177 | .SS title 178 | Defines the title of the box, e.\&g.\&: 179 | .P 180 | .RS 4 181 | title = "Hydrogen" 182 | .P 183 | .RE 184 | .SS border 185 | Defines the type of border to use for the box, with possible values of "plain", 186 | "thick", "rounded" or "double".\& 187 | .P 188 | .SS visible 189 | Defines whether to show or hide the box, e.\&g.\&: 190 | .P 191 | .RS 4 192 | visible = true 193 | .P 194 | .RE 195 | .SH BOX.INNER_MARGIN SECTION 196 | .P 197 | .SS x 198 | Defines the horizontal margin to leave between 199 | the content and the box, e.\&g.\&: 200 | .RS 4 201 | .P 202 | x = 2 203 | .P 204 | .RE 205 | .SS y 206 | Defines the vertical margin to leave between the content and the box, e.\&g.\&: 207 | .RS 4 208 | .P 209 | y = 1 210 | .P 211 | .RE 212 | .SH CUSTOM_ASCII SECTION 213 | This section, noted \fB[custom_ascii]\fR, allows you to specify your own ASCII art.\& 214 | ANSI escape sequences are supported.\& 215 | .P 216 | .SS color 217 | Defines the color of the ASCII.\& 218 | .RS 4 219 | .P 220 | .RE 221 | Accepts hexadecimal values: 222 | .P 223 | .RS 4 224 | color = "#00FF00" 225 | .P 226 | .RE 227 | Indexed values: 228 | .P 229 | .RS 4 230 | color = "046" 231 | .P 232 | .RE 233 | Predefined color names (case-insensitive): 234 | .P 235 | .RS 4 236 | color = "Green" 237 | .P 238 | .RE 239 | .SS path 240 | Defines the path to a file on your filesystem 241 | which contains the ASCII art you want to display, e.\&g.\&: 242 | .RS 4 243 | .P 244 | path = "~/ascii/arch_linux" 245 | .P 246 | .RE 247 | .SH RANDOMIZE SECTION 248 | This section, noted \fB[randomize]\fR, is used to randomize color selection.\& 249 | .P 250 | .SS key_color 251 | Defines whether to randomize the color of the keys, e.\&g.\&: 252 | .P 253 | .RS 4 254 | key_color = true 255 | .P 256 | .RE 257 | .SS separator_color 258 | Defines whether to randomize the color of the separator, e.\&g.\&: 259 | .P 260 | .RS 4 261 | separator_color = true 262 | .P 263 | .RE 264 | .SS pool 265 | Defines the pool of colors from which to pick a random color, with possible 266 | values of "hexadecimal", "indexed" or "base" (case-insensitive).\& 267 | .RS 4 268 | .P 269 | .RE 270 | .RS 4 271 | .ie n \{\ 272 | \h'-04'\(bu\h'+03'\c 273 | .\} 274 | .el \{\ 275 | .IP \(bu 4 276 | .\} 277 | If "hexadecimal" is specified, you'\&ll get a random color ranging 278 | 279 | .RE 280 | from #000000 to #FFFFFF 281 | .RS 4 282 | .P 283 | .RE 284 | .RS 4 285 | .ie n \{\ 286 | \h'-04'\(bu\h'+03'\c 287 | .\} 288 | .el \{\ 289 | .IP \(bu 4 290 | .\} 291 | If "indexed" is specified, you'\&ll get a random color ranging from 0 to 255 292 | .RS 4 293 | 294 | .RE 295 | .P 296 | .RE 297 | .RS 4 298 | .ie n \{\ 299 | \h'-04'\(bu\h'+03'\c 300 | .\} 301 | .el \{\ 302 | .IP \(bu 4 303 | .\} 304 | If "base" is specified, you'\&ll see a random color from the following set of 305 | 306 | .RE 307 | colors: "black", "white", "red", "green", "blue", "yellow", "magenta" and 308 | "cyan".\& 309 | .P 310 | .SH KEYS SECTION 311 | This section, noted \fB[keys]\fR, allows you to modify the text of each key.\& 312 | .P 313 | For example, the "Processor" readout, which by default shows up as "CPU" in 314 | macchina'\&s output, can be renamed to whatever you like by setting the "cpu" 315 | option.\& 316 | .P 317 | .SS host 318 | Defines the text of the Host readout, e.\&g.\&: 319 | .P 320 | .RS 4 321 | host = "Host" 322 | .P 323 | .RE 324 | .SS kernel 325 | Defines the text of the Kernel readout, e.\&g.\&: 326 | .P 327 | .RS 4 328 | kernel = "Kernel" 329 | .P 330 | .RE 331 | .SS os 332 | Defines the text of the OperatingSystem readout, e.\&g.\&: 333 | .P 334 | .RS 4 335 | os = "OS" 336 | .P 337 | .RE 338 | .SS machine 339 | Defines the text of the Machine readout, e.\&g.\&: 340 | .P 341 | .RS 4 342 | machine= "Machine" 343 | .P 344 | .RE 345 | .SS de 346 | Defines the text of the DesktopEnvironment readout, e.\&g.\&: 347 | .P 348 | .RS 4 349 | de = "DE" 350 | .P 351 | .RE 352 | .SS wm 353 | Defines the text of the WindowManager readout, e.\&g.\&: 354 | .P 355 | .RS 4 356 | wm = "WM" 357 | .P 358 | .RE 359 | .SS distro 360 | Defines the text of the Distribution readout, e.\&g.\&: 361 | .RS 4 362 | .P 363 | distro = "Distro" 364 | .P 365 | .RE 366 | .SS terminal 367 | Defines the text of the Terminal readout, e.\&g.\&: 368 | .P 369 | .RS 4 370 | terminal = "Term" 371 | .P 372 | .RE 373 | .SS shell 374 | Defines the text of the Shell readout, e.\&g.\&: 375 | .P 376 | .RS 4 377 | shell = "Shell" 378 | .P 379 | .RE 380 | .SS packages 381 | Defines the text of the Packages readout, e.\&g.\&: 382 | .P 383 | .RS 4 384 | packages = "Packages" 385 | .P 386 | .RE 387 | .SS uptime 388 | Defines the text of the Uptime readout, e.\&g.\&: 389 | .P 390 | .RS 4 391 | uptime = "Uptime" 392 | .P 393 | .RE 394 | .SS local_ip 395 | Defines the text of the LocalIP readout, e.\&g.\&: 396 | .P 397 | .RS 4 398 | local_ip = "Local IP" 399 | .P 400 | .RE 401 | .SS memory 402 | Defines the text of the Memory readout, e.\&g.\&: 403 | .P 404 | .RS 4 405 | memory = "Memory" 406 | .P 407 | .RE 408 | .SS battery 409 | Defines the text of the Battery readout, e.\&g.\&: 410 | .RS 4 411 | .P 412 | battery = "Battery" 413 | .P 414 | .RE 415 | .SS backlight 416 | Defines the text of the Backlight readout, e.\&g.\&: 417 | .P 418 | .RS 4 419 | backlight = "Brightness" 420 | .P 421 | .RE 422 | .SS resolution 423 | Defines the text of the Resolution readout, e.\&g.\&: 424 | .P 425 | .RS 4 426 | resolution = "Resolution" 427 | .P 428 | .RE 429 | .SS cpu 430 | Defines the text of the Processor readout, e.\&g.\&: 431 | .RS 4 432 | .P 433 | cpu = "CPU" 434 | .P 435 | .RE 436 | .SS cpu_load 437 | Defines the text of the ProcessorLoad readout, e.\&g.\&: 438 | .P 439 | .RS 4 440 | cpu_load = "CPU %" 441 | .P 442 | .RE 443 | .SH SEE ALSO 444 | macchina(1) 445 | -------------------------------------------------------------------------------- /src/widgets/readout.rs: -------------------------------------------------------------------------------- 1 | use crate::data::{Readout, ReadoutKey}; 2 | use crate::theme::components::{Palette, PaletteType}; 3 | use crate::theme::Theme; 4 | use std::collections::HashMap; 5 | use tui::buffer::Buffer; 6 | use tui::layout::{Margin, Rect}; 7 | use tui::style::{Color, Style}; 8 | use tui::text::{Span, Spans, Text}; 9 | use tui::widgets::{Block, Paragraph, Widget}; 10 | 11 | pub struct ReadoutList<'a> { 12 | block: Option>, 13 | style: Style, 14 | items: Vec>, 15 | theme: &'a Theme, 16 | block_inner_margin: Margin, 17 | } 18 | 19 | impl<'a> ReadoutList<'a> { 20 | pub fn new(items: T, theme: &'a Theme) -> ReadoutList<'a> 21 | where 22 | T: Into>>, 23 | { 24 | ReadoutList { 25 | block: None, 26 | style: Style::default(), 27 | items: items.into(), 28 | theme, 29 | block_inner_margin: Margin { 30 | horizontal: 0, 31 | vertical: 0, 32 | }, 33 | } 34 | } 35 | 36 | pub fn block(mut self, block: Block<'a>) -> ReadoutList<'a> { 37 | self.block = Some(block); 38 | self 39 | } 40 | 41 | pub fn add_item(mut self, item: Readout<'a>) -> ReadoutList<'a> { 42 | self.items.push(item); 43 | self 44 | } 45 | 46 | pub fn theme(mut self, theme: &'a Theme) -> ReadoutList<'a> { 47 | self.theme = theme; 48 | self 49 | } 50 | 51 | pub fn style(mut self, style: Style) -> ReadoutList<'a> { 52 | self.style = style; 53 | self 54 | } 55 | 56 | pub fn block_inner_margin(mut self, margin: Margin) -> ReadoutList<'a> { 57 | self.block_inner_margin = margin; 58 | self 59 | } 60 | } 61 | 62 | impl<'a> Widget for ReadoutList<'a> { 63 | fn render(self, area: Rect, buf: &mut Buffer) { 64 | buf.set_style(area, self.style); 65 | let list_area = match &self.block { 66 | Some(b) => { 67 | let inner_area = b.inner(area); 68 | inner_area.inner(&self.block_inner_margin) 69 | } 70 | None => area, 71 | }; 72 | 73 | if list_area.width < 1 || list_area.height < 1 { 74 | return; 75 | } 76 | 77 | if self.items.is_empty() { 78 | return; 79 | } 80 | 81 | let mut height = 0; 82 | let keys = self.keys_to_text(self.theme); 83 | let max_key_width = Self::get_max_key_width(&keys); 84 | let themed_separator = Self::get_themed_separator( 85 | self.theme.get_separator(), 86 | &self.theme.get_separator_color(), 87 | ); 88 | 89 | let mut max_line_width: u16 = 0; 90 | 91 | for item in self.items.iter().filter(|&f| f.1.is_ok()) { 92 | //it's ok to unwrap, because we filtered out everything that is not a valid Option. 93 | let readout_data = item.1.as_ref().unwrap(); 94 | let readout_key = keys.get(&item.0).unwrap(); 95 | 96 | let list_item_area = Rect { 97 | x: list_area.x, 98 | y: list_area.y + height, 99 | width: list_area.width, 100 | height: readout_data.height() as u16, 101 | }; 102 | 103 | let constraints = 104 | self.create_item_constraints(max_key_width, &themed_separator, readout_data); 105 | let layout = Self::create_layout(&list_item_area, &constraints); 106 | 107 | let total_line_width = constraints.iter().sum::() + constraints.len() as u16 - 1; 108 | if total_line_width > max_line_width { 109 | max_line_width = total_line_width; 110 | } 111 | 112 | let mut layout_iter = layout.iter(); 113 | if self.theme.get_padding() > 0 { 114 | layout_iter.next(); 115 | } 116 | 117 | Paragraph::new(readout_key.clone()).render(*layout_iter.next().unwrap(), buf); 118 | 119 | if !self.theme.get_separator().is_empty() { 120 | Paragraph::new(themed_separator.clone()).render(*layout_iter.next().unwrap(), buf); 121 | } 122 | 123 | layout_iter.next(); 124 | Paragraph::new(readout_data.to_owned()).render(*layout_iter.next().unwrap(), buf); 125 | height += readout_data.height() as u16; 126 | } 127 | 128 | self.print_palette(buf, &list_area, &mut height, self.theme.get_palette()); 129 | 130 | Self::render_block( 131 | self.block, 132 | buf, 133 | area.x, 134 | area.y, 135 | height, 136 | max_line_width, 137 | &self.block_inner_margin, 138 | ); 139 | } 140 | } 141 | 142 | impl<'a> ReadoutList<'a> { 143 | fn print_palette( 144 | &self, 145 | buf: &mut Buffer, 146 | list_area: &Rect, 147 | height: &mut u16, 148 | palette: &Palette, 149 | ) { 150 | if !palette.is_visible() { 151 | return; 152 | } 153 | 154 | let light_colors = [ 155 | Color::DarkGray, 156 | Color::LightRed, 157 | Color::LightGreen, 158 | Color::LightYellow, 159 | Color::LightBlue, 160 | Color::LightMagenta, 161 | Color::LightCyan, 162 | Color::Gray, 163 | ]; 164 | 165 | let dark_colors = [ 166 | Color::Black, 167 | Color::Red, 168 | Color::Green, 169 | Color::Yellow, 170 | Color::Blue, 171 | Color::Magenta, 172 | Color::Cyan, 173 | Color::White, 174 | ]; 175 | 176 | let span_vector = |colors: &[Color]| -> Vec<_> { 177 | if let Some(glyph) = palette.get_glyph() { 178 | colors 179 | .iter() 180 | .map(|c| Span::styled(glyph.to_owned(), Style::default().fg(c.to_owned()))) 181 | .collect() 182 | } else { 183 | colors 184 | .iter() 185 | .map(|c| Span::styled(" ", Style::default().bg(c.to_owned()))) 186 | .collect() 187 | } 188 | }; 189 | 190 | let spans = match palette.get_type() { 191 | PaletteType::Light => vec![Spans::from(span_vector(&light_colors))], 192 | PaletteType::Dark => vec![Spans::from(span_vector(&dark_colors))], 193 | PaletteType::Full => vec![ 194 | Spans::from(span_vector(&dark_colors)), 195 | Spans::from(span_vector(&light_colors)), 196 | ], 197 | }; 198 | 199 | let padding = self.theme.get_padding() as u16; 200 | 201 | let area = Rect::new( 202 | list_area.x + padding, 203 | list_area.y + *height + 1, 204 | list_area.width - padding, 205 | spans.len() as u16, 206 | ); 207 | 208 | Paragraph::new(spans).render(area, buf); 209 | 210 | *height += area.height + 1; 211 | } 212 | 213 | fn keys_to_text(&self, theme: &Theme) -> HashMap { 214 | let style = Style::default().fg(theme.get_key_color()); 215 | 216 | self.items 217 | .iter() 218 | .map(|i| (i.0, Text::styled(theme.key(&i.0).to_string(), style))) 219 | .collect() 220 | } 221 | 222 | fn get_max_key_width(keys: &HashMap) -> usize { 223 | keys.iter().map(|i| i.1.width()).max().unwrap() 224 | } 225 | 226 | fn render_block( 227 | block: Option>, 228 | buf: &mut Buffer, 229 | x: u16, 230 | y: u16, 231 | content_height: u16, 232 | content_width: u16, 233 | margin: &Margin, 234 | ) { 235 | if let Some(block) = block { 236 | block.render( 237 | Rect { 238 | x, 239 | y, 240 | width: content_width + 2 + margin.horizontal * 2, 241 | height: content_height + 2 + margin.vertical * 2, 242 | }, 243 | buf, 244 | ); 245 | } 246 | } 247 | 248 | fn get_themed_separator(separator: &'a str, sep_color: &Color) -> Text<'a> { 249 | Text::styled(separator, Style::default().fg(*sep_color)) 250 | } 251 | } 252 | 253 | impl<'a> ReadoutList<'a> { 254 | fn create_item_constraints( 255 | &self, 256 | max_key_width: usize, 257 | themed_separator: &Text, 258 | readout_data: &Text, 259 | ) -> Vec { 260 | let mut values = vec![]; 261 | 262 | if self.theme.get_separator().is_empty() { 263 | values.push(max_key_width as u16); 264 | } else { 265 | values.push(max_key_width as u16 + self.theme.get_spacing() as u16); 266 | values.push(themed_separator.width() as u16); 267 | } 268 | 269 | values.push(self.theme.get_spacing() as u16); 270 | values.push(readout_data.width() as u16); 271 | 272 | if self.theme.get_padding() > 0 { 273 | values.insert(0, self.theme.get_padding() as u16) 274 | } 275 | 276 | values 277 | } 278 | } 279 | 280 | impl<'a> ReadoutList<'a> { 281 | fn create_layout(area: &Rect, constraints: &[u16]) -> Vec { 282 | let mut layout: Vec = Vec::with_capacity(constraints.len()); 283 | layout.push(Rect { 284 | x: area.x, 285 | y: area.y, 286 | width: constraints[0], 287 | height: area.height, 288 | }); 289 | 290 | for (i, &constraint) in constraints.iter().enumerate().skip(1) { 291 | let previous = layout[i - 1]; 292 | layout.push(Rect { 293 | x: previous.x + previous.width, 294 | y: previous.y, 295 | width: constraint, 296 | height: area.height, 297 | }); 298 | } 299 | 300 | layout 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /src/theme/base.rs: -------------------------------------------------------------------------------- 1 | use crate::cli::{Opt, PKG_NAME}; 2 | use crate::data::ReadoutKey; 3 | use crate::error; 4 | use crate::extra; 5 | use crate::theme::components::*; 6 | use crate::Result; 7 | use colored::Colorize; 8 | use dirs; 9 | use serde::{Deserialize, Serialize}; 10 | use std::fmt; 11 | use std::path::{Path, PathBuf}; 12 | use tui::style::Color; 13 | 14 | #[derive(Debug, Clone, Serialize, Deserialize)] 15 | #[serde(default)] 16 | pub struct Theme { 17 | custom_ascii: ASCII, 18 | bar: Bar, 19 | r#box: Block, 20 | separator: String, 21 | randomize: Randomize, 22 | spacing: usize, 23 | padding: usize, 24 | palette: Palette, 25 | hide_ascii: bool, 26 | prefer_small_ascii: bool, 27 | keys: Keys, 28 | #[serde(with = "color_to_tui")] 29 | key_color: Color, 30 | #[serde(with = "color_to_tui")] 31 | separator_color: Color, 32 | #[serde(skip_serializing, skip_deserializing)] 33 | name: String, 34 | #[serde(skip_serializing, skip_deserializing)] 35 | filepath: PathBuf, 36 | #[serde(skip_serializing, skip_deserializing)] 37 | active: bool, 38 | } 39 | 40 | impl Default for Theme { 41 | fn default() -> Self { 42 | Theme { 43 | key_color: Color::Blue, 44 | separator_color: Color::Yellow, 45 | separator: String::from("-"), 46 | palette: Palette::default(), 47 | randomize: Randomize::default(), 48 | custom_ascii: ASCII::default(), 49 | bar: Bar::default(), 50 | r#box: Block::default(), 51 | keys: Keys::default(), 52 | name: String::new(), 53 | filepath: PathBuf::new(), 54 | active: false, 55 | hide_ascii: false, 56 | prefer_small_ascii: false, 57 | spacing: 2, 58 | padding: 2, 59 | } 60 | } 61 | } 62 | 63 | impl Theme { 64 | pub fn new(custom: Theme) -> Self { 65 | Theme { 66 | bar: custom.bar, 67 | key_color: custom.key_color, 68 | separator: custom.separator, 69 | separator_color: custom.separator_color, 70 | spacing: custom.spacing, 71 | padding: custom.padding, 72 | palette: custom.palette, 73 | hide_ascii: custom.hide_ascii, 74 | prefer_small_ascii: custom.prefer_small_ascii, 75 | r#box: custom.r#box, 76 | custom_ascii: custom.custom_ascii, 77 | randomize: custom.randomize, 78 | keys: custom.keys, 79 | name: custom.name, 80 | filepath: custom.filepath, 81 | active: custom.active, 82 | } 83 | } 84 | 85 | pub fn is_ascii_visible(&self) -> bool { 86 | !self.hide_ascii 87 | } 88 | 89 | pub fn is_active(&self) -> bool { 90 | self.active 91 | } 92 | 93 | pub fn get_filepath(&self) -> PathBuf { 94 | self.filepath.to_owned() 95 | } 96 | 97 | pub fn get_name(&self) -> String { 98 | self.name.to_owned() 99 | } 100 | 101 | pub fn get_bar(&self) -> &Bar { 102 | &self.bar 103 | } 104 | 105 | pub fn get_keys(&self) -> &Keys { 106 | &self.keys 107 | } 108 | 109 | pub fn get_randomization(&self) -> &Randomize { 110 | &self.randomize 111 | } 112 | 113 | pub fn get_custom_ascii(&self) -> &ASCII { 114 | &self.custom_ascii 115 | } 116 | 117 | pub fn get_palette(&self) -> &Palette { 118 | &self.palette 119 | } 120 | 121 | pub fn get_block(&self) -> &Block { 122 | &self.r#box 123 | } 124 | 125 | pub fn get_separator(&self) -> &str { 126 | &self.separator 127 | } 128 | 129 | pub fn get_key_color(&self) -> Color { 130 | self.key_color 131 | } 132 | 133 | pub fn get_separator_color(&self) -> Color { 134 | self.separator_color 135 | } 136 | 137 | pub fn prefers_small_ascii(&self) -> bool { 138 | self.prefer_small_ascii 139 | } 140 | 141 | pub fn get_padding(&self) -> usize { 142 | self.padding 143 | } 144 | 145 | pub fn get_spacing(&self) -> usize { 146 | self.spacing 147 | } 148 | 149 | pub fn set_active(&mut self, theme_name: Option<&String>) { 150 | if let Some(name) = theme_name { 151 | if self.name.eq(name) { 152 | self.active = true; 153 | } 154 | } 155 | } 156 | 157 | fn set_name(&mut self) { 158 | if let Some(f) = self.filepath.file_stem() { 159 | if let Some(s) = f.to_str() { 160 | self.name = s.to_string(); 161 | } 162 | } 163 | } 164 | 165 | fn set_filepath(&mut self, p: PathBuf) { 166 | self.filepath = p 167 | } 168 | 169 | fn set_randomization(&mut self) { 170 | if self.randomize.rand_key() { 171 | self.key_color = self.randomize.generate(); 172 | } 173 | 174 | if self.randomize.rand_sep() { 175 | self.separator_color = self.randomize.generate(); 176 | } 177 | } 178 | 179 | pub fn set_separator_color(&mut self, color: Color) { 180 | self.separator_color = color; 181 | } 182 | 183 | pub fn set_padding(&mut self, size: usize) { 184 | self.padding = size 185 | } 186 | 187 | pub fn set_spacing(&mut self, spacing: usize) { 188 | self.spacing = spacing; 189 | } 190 | 191 | pub fn set_key_color(&mut self, color: Color) { 192 | self.key_color = color; 193 | } 194 | 195 | pub fn set_separator(&mut self, separator: impl ToString) { 196 | self.separator = separator.to_string() 197 | } 198 | 199 | pub fn key(&self, readout_key: &ReadoutKey) -> &str { 200 | match *readout_key { 201 | ReadoutKey::Host => self.keys.get_host(), 202 | ReadoutKey::Kernel => self.keys.get_kernel(), 203 | ReadoutKey::OperatingSystem => self.keys.get_os(), 204 | ReadoutKey::Machine => self.keys.get_machine(), 205 | ReadoutKey::Distribution => self.keys.get_distro(), 206 | ReadoutKey::LocalIP => self.keys.get_local_ip(), 207 | ReadoutKey::Resolution => self.keys.get_resolution(), 208 | ReadoutKey::Shell => self.keys.get_shell(), 209 | ReadoutKey::Terminal => self.keys.get_terminal(), 210 | ReadoutKey::WindowManager => self.keys.get_wm(), 211 | ReadoutKey::DesktopEnvironment => self.keys.get_de(), 212 | ReadoutKey::Packages => self.keys.get_packages(), 213 | ReadoutKey::Processor => self.keys.get_cpu(), 214 | ReadoutKey::ProcessorLoad => self.keys.get_cpu_load(), 215 | ReadoutKey::Battery => self.keys.get_battery(), 216 | ReadoutKey::Backlight => self.keys.get_backlight(), 217 | ReadoutKey::Uptime => self.keys.get_uptime(), 218 | ReadoutKey::Memory => self.keys.get_memory(), 219 | } 220 | } 221 | } 222 | 223 | impl fmt::Display for Theme { 224 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 225 | if self.is_active() { 226 | write!( 227 | f, 228 | "- {} {}", 229 | self.name.bright_green().italic(), 230 | "(active)".cyan().bold() 231 | ) 232 | } else { 233 | write!(f, "- {}", self.name.bright_green().italic()) 234 | } 235 | } 236 | } 237 | 238 | impl PartialEq for Theme { 239 | fn eq(&self, other: &Self) -> bool { 240 | self.filepath == other.filepath || self.name == other.name 241 | } 242 | } 243 | 244 | /// Predefined locations where macchina should search. 245 | pub fn locations() -> Vec { 246 | let mut dirs = vec![]; 247 | 248 | if cfg!(target_os = "macos") { 249 | dirs.push(extra::config_dir().unwrap_or_default()); 250 | } else { 251 | dirs.push(dirs::config_dir().unwrap_or_default()); 252 | } 253 | 254 | if cfg!(target_os = "linux") { 255 | dirs.push(extra::usr_share_dir().unwrap_or_default()); 256 | } 257 | 258 | #[cfg(target_os = "netbsd")] 259 | dirs.push(libmacchina::dirs::localbase_dir().unwrap_or_default()); 260 | 261 | dirs.retain(|x| x.exists()); 262 | dirs.iter() 263 | .map(|x| x.join(PKG_NAME).join("themes")) 264 | .collect() 265 | } 266 | 267 | /// Searches for and returns a theme from a given directory. 268 | pub fn get_theme(path: &Path) -> Result { 269 | let buffer = std::fs::read(path)?; 270 | Ok(toml::from_slice(&buffer)?) 271 | } 272 | 273 | /// Searches for and returns the specified theme. 274 | pub fn create_theme(opt: &Opt) -> Theme { 275 | let locations = locations(); 276 | let mut theme = Theme::default(); 277 | if let Some(th) = &opt.theme { 278 | locations.iter().find(|d| { 279 | let path = d.join(&format!("{th}.toml")); 280 | if !path.exists() { 281 | return false; 282 | } 283 | 284 | match get_theme(&path) { 285 | Ok(t) => { 286 | theme = t; 287 | theme.set_randomization(); 288 | true 289 | } 290 | Err(e) => { 291 | error::print_errors(e); 292 | false 293 | } 294 | } 295 | }); 296 | } 297 | 298 | theme 299 | } 300 | 301 | /// Prints out a list of available themes. 302 | pub fn list_themes(opt: &Opt) { 303 | // 1. Iterate over locations 304 | // 2. Print current location 305 | // 2. Iterate over TOML files 306 | // 3. Display themes. 307 | let locations = locations(); 308 | locations.iter().for_each(|dir| { 309 | println!("{}:", dir.to_string_lossy()); 310 | if let Some(mut entries) = extra::get_entries(dir) { 311 | entries.sort(); 312 | entries 313 | .iter() 314 | .filter(|x| extra::path_extension(x).unwrap_or_default() == "toml") 315 | .for_each(|theme| { 316 | if let Some(str) = theme.file_name() { 317 | if let Some(name) = str.to_str() { 318 | match get_theme(theme) { 319 | Ok(mut t) => { 320 | t.set_filepath(dir.join(name)); 321 | t.set_name(); 322 | t.set_active(opt.theme.as_ref()); 323 | println!("{t}"); 324 | } 325 | Err(e) => { 326 | println!( 327 | "- {}: {}", 328 | name.replace(".toml", ""), 329 | e.to_string().yellow() 330 | ); 331 | } 332 | } 333 | } 334 | } 335 | }); 336 | } 337 | }) 338 | } 339 | 340 | #[cfg(test)] 341 | mod tests { 342 | use super::*; 343 | 344 | #[test] 345 | fn test_additional_themes() -> Result<()> { 346 | for theme in std::fs::read_dir("contrib/themes")? { 347 | let theme = theme?.path(); 348 | get_theme(&theme)?; 349 | } 350 | Ok(()) 351 | } 352 | } 353 | -------------------------------------------------------------------------------- /src/theme/components.rs: -------------------------------------------------------------------------------- 1 | use crate::theme::borders::Border; 2 | use crate::theme::color::*; 3 | use rand::Rng; 4 | use serde::{Deserialize, Serialize}; 5 | use std::path::PathBuf; 6 | use tui::style::Color; 7 | use tui::widgets::BorderType; 8 | 9 | #[derive(Debug, Clone, Serialize, Deserialize)] 10 | pub struct Palette { 11 | r#type: Option, 12 | glyph: Option, 13 | visible: Option, 14 | } 15 | 16 | impl Default for Palette { 17 | fn default() -> Self { 18 | Palette { 19 | r#type: Some(PaletteType::Dark), 20 | glyph: Some(String::from(" ")), 21 | visible: None, 22 | } 23 | } 24 | } 25 | 26 | impl Palette { 27 | pub fn get_type(&self) -> PaletteType { 28 | if let Some(t) = &self.r#type { 29 | return t.to_owned(); 30 | } 31 | 32 | PaletteType::Dark 33 | } 34 | 35 | pub fn get_glyph(&self) -> Option<&String> { 36 | self.glyph.as_ref() 37 | } 38 | 39 | pub fn is_visible(&self) -> bool { 40 | if let Some(v) = self.visible { 41 | return v; 42 | } 43 | 44 | false 45 | } 46 | } 47 | 48 | #[derive(Debug, Clone, Serialize, Deserialize)] 49 | pub struct Randomize { 50 | key_color: Option, 51 | separator_color: Option, 52 | pool: Option, 53 | } 54 | 55 | impl Default for Randomize { 56 | fn default() -> Self { 57 | Randomize { 58 | key_color: None, 59 | separator_color: None, 60 | pool: Some(ColorTypes::Base), 61 | } 62 | } 63 | } 64 | 65 | impl Randomize { 66 | pub fn rand_key(&self) -> bool { 67 | if let Some(k) = self.key_color { 68 | return k; 69 | } 70 | 71 | false 72 | } 73 | 74 | pub fn rand_sep(&self) -> bool { 75 | if let Some(s) = self.separator_color { 76 | return s; 77 | } 78 | 79 | false 80 | } 81 | 82 | pub fn generate(&self) -> Color { 83 | if let Some(pool) = &self.pool { 84 | match pool { 85 | ColorTypes::Base => return make_random_color(), 86 | ColorTypes::Indexed => { 87 | let mut rng = rand::thread_rng(); 88 | return Color::Indexed(rng.gen_range(0..=127)); 89 | } 90 | ColorTypes::Hexadecimal => { 91 | let mut rng = rand::thread_rng(); 92 | let rgb = ( 93 | rng.gen_range(0..=255), 94 | rng.gen_range(0..=255), 95 | rng.gen_range(0..=255), 96 | ); 97 | return Color::Rgb(rgb.0, rgb.1, rgb.2); 98 | } 99 | }; 100 | } 101 | 102 | make_random_color() 103 | } 104 | } 105 | 106 | #[derive(Debug, Default, Clone, Serialize, Deserialize)] 107 | pub struct ASCII { 108 | path: Option, 109 | 110 | #[serde(default)] 111 | #[serde(with = "color_to_tui::optional")] 112 | color: Option, 113 | } 114 | 115 | impl ASCII { 116 | pub fn get_color(&self) -> Option { 117 | self.color 118 | } 119 | 120 | pub fn get_path(&self) -> Option<&PathBuf> { 121 | self.path.as_ref() 122 | } 123 | } 124 | 125 | #[derive(Debug, Clone, Serialize, Deserialize)] 126 | pub enum PaletteType { 127 | Light, 128 | Dark, 129 | Full, 130 | } 131 | 132 | #[derive(Debug, Clone, Serialize, Deserialize)] 133 | pub struct InnerMargin { 134 | x: u16, 135 | y: u16, 136 | } 137 | 138 | impl Default for InnerMargin { 139 | fn default() -> Self { 140 | InnerMargin { x: 1, y: 0 } 141 | } 142 | } 143 | 144 | #[derive(Debug, Clone, Serialize, Deserialize)] 145 | pub struct Block { 146 | title: Option, 147 | visible: Option, 148 | inner_margin: Option, 149 | border: Option, 150 | } 151 | 152 | impl Default for Block { 153 | fn default() -> Self { 154 | Block { 155 | title: None, 156 | visible: Some(false), 157 | inner_margin: Some(InnerMargin::default()), 158 | border: Some(Border::Plain), 159 | } 160 | } 161 | } 162 | 163 | impl Block { 164 | pub fn get_title(&self) -> String { 165 | if let Some(t) = &self.title { 166 | return t.to_owned(); 167 | } 168 | 169 | String::new() 170 | } 171 | 172 | pub fn get_border_type(&self) -> BorderType { 173 | if let Some(b) = &self.border { 174 | match b { 175 | Border::Plain => return BorderType::Plain, 176 | Border::Rounded => return BorderType::Rounded, 177 | Border::Double => return BorderType::Double, 178 | Border::Thick => return BorderType::Thick, 179 | }; 180 | } 181 | 182 | BorderType::Plain 183 | } 184 | 185 | pub fn is_visible(&self) -> bool { 186 | if let Some(v) = self.visible { 187 | return v; 188 | } 189 | 190 | false 191 | } 192 | 193 | pub fn get_horizontal_margin(&self) -> u16 { 194 | if let Some(marg) = &self.inner_margin { 195 | return marg.x; 196 | } 197 | 198 | 1 199 | } 200 | 201 | pub fn get_vertical_margin(&self) -> u16 { 202 | if let Some(marg) = &self.inner_margin { 203 | return marg.y; 204 | } 205 | 206 | 0 207 | } 208 | } 209 | 210 | #[derive(Debug, Clone, Serialize, Deserialize)] 211 | pub struct Bar { 212 | glyph: Option, 213 | symbol_open: Option, 214 | symbol_close: Option, 215 | hide_delimiters: Option, 216 | visible: Option, 217 | } 218 | 219 | impl Default for Bar { 220 | fn default() -> Self { 221 | Bar { 222 | glyph: None, 223 | symbol_open: Some('('), 224 | symbol_close: Some(')'), 225 | hide_delimiters: None, 226 | visible: None, 227 | } 228 | } 229 | } 230 | 231 | impl Bar { 232 | pub fn is_visible(&self) -> bool { 233 | if let Some(v) = self.visible { 234 | return v; 235 | } 236 | 237 | false 238 | } 239 | 240 | pub fn get_glyph(&self) -> &str { 241 | if let Some(g) = &self.glyph { 242 | return g; 243 | } 244 | 245 | "●" 246 | } 247 | 248 | pub fn get_symbol_open(&self) -> char { 249 | if let Some(s) = self.symbol_open { 250 | return s; 251 | } 252 | 253 | '(' 254 | } 255 | 256 | pub fn get_symbol_close(&self) -> char { 257 | if let Some(s) = self.symbol_close { 258 | return s; 259 | } 260 | 261 | ')' 262 | } 263 | 264 | pub fn hide_delimiters(&mut self) { 265 | if let Some(h) = self.hide_delimiters { 266 | if h { 267 | self.symbol_open = Some('\0'); 268 | self.symbol_close = Some('\0'); 269 | } 270 | } 271 | } 272 | 273 | pub fn are_delimiters_hidden(&self) -> bool { 274 | if let Some(h) = self.hide_delimiters { 275 | return h; 276 | } 277 | 278 | false 279 | } 280 | } 281 | 282 | #[derive(Debug, Clone, Serialize, Deserialize)] 283 | pub struct Keys { 284 | pub host: Option, 285 | pub kernel: Option, 286 | pub battery: Option, 287 | pub os: Option, 288 | pub de: Option, 289 | pub wm: Option, 290 | pub distro: Option, 291 | pub terminal: Option, 292 | pub shell: Option, 293 | pub packages: Option, 294 | pub uptime: Option, 295 | pub memory: Option, 296 | pub machine: Option, 297 | pub local_ip: Option, 298 | pub backlight: Option, 299 | pub resolution: Option, 300 | pub cpu_load: Option, 301 | pub cpu: Option, 302 | } 303 | 304 | impl Default for Keys { 305 | fn default() -> Self { 306 | Self { 307 | host: Some(String::from("Host")), 308 | kernel: Some(String::from("Kernel")), 309 | battery: Some(String::from("Battery")), 310 | os: Some(String::from("OS")), 311 | de: Some(String::from("DE")), 312 | wm: Some(String::from("WM")), 313 | distro: Some(String::from("Distro")), 314 | terminal: Some(String::from("Terminal")), 315 | shell: Some(String::from("Shell")), 316 | packages: Some(String::from("Packages")), 317 | uptime: Some(String::from("Uptime")), 318 | memory: Some(String::from("Memory")), 319 | machine: Some(String::from("Machine")), 320 | local_ip: Some(String::from("Local IP")), 321 | backlight: Some(String::from("Brightness")), 322 | resolution: Some(String::from("Resolution")), 323 | cpu_load: Some(String::from("CPU Load")), 324 | cpu: Some(String::from("CPU")), 325 | } 326 | } 327 | } 328 | 329 | impl Keys { 330 | pub fn get_host(&self) -> &str { 331 | if let Some(h) = &self.host { 332 | return h; 333 | } 334 | 335 | "Host" 336 | } 337 | 338 | pub fn get_kernel(&self) -> &str { 339 | if let Some(k) = &self.kernel { 340 | return k; 341 | } 342 | 343 | "Kernel" 344 | } 345 | 346 | pub fn get_battery(&self) -> &str { 347 | if let Some(b) = &self.battery { 348 | return b; 349 | } 350 | 351 | "Battery" 352 | } 353 | 354 | pub fn get_os(&self) -> &str { 355 | if let Some(o) = &self.os { 356 | return o; 357 | } 358 | 359 | "OS" 360 | } 361 | 362 | pub fn get_de(&self) -> &str { 363 | if let Some(d) = &self.de { 364 | return d; 365 | } 366 | 367 | "DE" 368 | } 369 | 370 | pub fn get_wm(&self) -> &str { 371 | if let Some(w) = &self.wm { 372 | return w; 373 | } 374 | 375 | "WM" 376 | } 377 | 378 | pub fn get_distro(&self) -> &str { 379 | if let Some(d) = &self.distro { 380 | return d; 381 | } 382 | 383 | "Distro" 384 | } 385 | 386 | pub fn get_terminal(&self) -> &str { 387 | if let Some(t) = &self.terminal { 388 | return t; 389 | } 390 | 391 | "Terminal" 392 | } 393 | 394 | pub fn get_shell(&self) -> &str { 395 | if let Some(s) = &self.shell { 396 | return s; 397 | } 398 | 399 | "Shell" 400 | } 401 | 402 | pub fn get_packages(&self) -> &str { 403 | if let Some(p) = &self.packages { 404 | return p; 405 | } 406 | 407 | "Packages" 408 | } 409 | 410 | pub fn get_uptime(&self) -> &str { 411 | if let Some(u) = &self.uptime { 412 | return u; 413 | } 414 | 415 | "Uptime" 416 | } 417 | 418 | pub fn get_memory(&self) -> &str { 419 | if let Some(m) = &self.memory { 420 | return m; 421 | } 422 | 423 | "Memory" 424 | } 425 | 426 | pub fn get_machine(&self) -> &str { 427 | if let Some(m) = &self.machine { 428 | return m; 429 | } 430 | 431 | "Machine" 432 | } 433 | 434 | pub fn get_local_ip(&self) -> &str { 435 | if let Some(l) = &self.local_ip { 436 | return l; 437 | } 438 | 439 | "Local IP" 440 | } 441 | 442 | pub fn get_backlight(&self) -> &str { 443 | if let Some(b) = &self.backlight { 444 | return b; 445 | } 446 | 447 | "Brightness" 448 | } 449 | 450 | pub fn get_resolution(&self) -> &str { 451 | if let Some(r) = &self.resolution { 452 | return r; 453 | } 454 | 455 | "Resolution" 456 | } 457 | 458 | pub fn get_cpu_load(&self) -> &str { 459 | if let Some(c) = &self.cpu_load { 460 | return c; 461 | } 462 | 463 | "CPU Load" 464 | } 465 | 466 | pub fn get_cpu(&self) -> &str { 467 | if let Some(c) = &self.cpu { 468 | return c; 469 | } 470 | 471 | "CPU" 472 | } 473 | } 474 | -------------------------------------------------------------------------------- /contrib/ascii/rust.ascii: -------------------------------------------------------------------------------- 1 |                                                    2 |                                                    3 |                      .. .,  .                      4 |                   c,'cc;cc::cc.;c                  5 |                c::ccccccccccccccc:cc         .     6 |  .  ,,      :::ccccccccccccccccccccc:c.    ,c:  ;  7 |  c: .cc   ''cccccccccccccccccccccccccc;'. .cc:.;;  8 |   cc:cc   :ccccccccccccccccccccccccccccc,  .ccc    9 |     .c,  ccccccccccccOc.:cccdx.;ccccccccc;.',      10 |        ';ccccccccccc'o' .cc,'l  ccccccccccc'       11 |       'c;.:ccccccccc:...;ccc'..;ccccccc:.;'c,      12 |        'c  .     ::cccccc'.'cccc::.     . ;,       13 |          ..                              '         14 |            .                            .          15 |                                                    16 |                                                    17 |                                                    18 | -------------------------------------------------------------------------------- /src/ascii.rs: -------------------------------------------------------------------------------- 1 | use crate::Result; 2 | use ansi_to_tui::IntoText; 3 | use colored::Colorize; 4 | use io::Read; 5 | use std::fs::File; 6 | use std::io::{self, BufReader}; 7 | use std::path::Path; 8 | use tui::style::{Color, Style}; 9 | use tui::text::{Span, Spans, Text}; 10 | 11 | lazy_static! { 12 | static ref BLUE: Style = Style::default().fg(Color::Blue); 13 | static ref RED: Style = Style::default().fg(Color::Red); 14 | static ref GREEN: Style = Style::default().fg(Color::Green); 15 | static ref YELLOW: Style = Style::default().fg(Color::Yellow); 16 | static ref MAGENTA: Style = Style::default().fg(Color::Magenta); 17 | static ref WHITE: Style = Style::default().fg(Color::White); 18 | static ref BLACK: Style = Style::default().fg(Color::Black); 19 | } 20 | 21 | pub enum AsciiSize { 22 | Big, 23 | Small, 24 | } 25 | 26 | pub fn list_ascii_artists() { 27 | println!( 28 | "- FreeBSD ASCII art (small variant) was taken from {}' {}", 29 | "Dylan Araps".bold(), 30 | "pfetch".bright_purple() 31 | ); 32 | 33 | println!( 34 | "- macOS ASCII art (big variant) was taken from {}' {}", 35 | "Dylan Araps".bold(), 36 | "Neofetch".bright_purple() 37 | ); 38 | 39 | println!( 40 | "- macOS ASCII art (small variant) was originally made by {}", 41 | "Joan Stark".bold(), 42 | ); 43 | 44 | println!( 45 | "- Linux ASCII art (big variant) was originally made by {}", 46 | "Joan Stark".bold(), 47 | ); 48 | 49 | println!( 50 | "- Linux ASCII art (small variant) was taken from {}", 51 | "Christopher Johnson's ASCII art collection".bold(), 52 | ); 53 | } 54 | 55 | pub fn select_ascii(ascii_size: AsciiSize) -> Option> { 56 | let ascii_art = get_ascii_art(ascii_size); 57 | 58 | if ascii_art.is_empty() { 59 | return None; 60 | } 61 | 62 | Some(ascii_art[0].to_owned()) 63 | } 64 | 65 | pub fn get_ascii_from_file(file_path: &Path) -> Result> { 66 | let file = File::open(file_path)?; 67 | let mut buffer = Vec::new(); 68 | let mut reader = BufReader::new(file); 69 | reader.read_to_end(&mut buffer)?; 70 | Ok(buffer.into_text().unwrap_or_default()) 71 | } 72 | 73 | pub fn get_ascii_from_file_override_color(file_path: &Path, color: Color) -> Result> { 74 | let file = File::open(file_path)?; 75 | let mut reader = BufReader::new(file); 76 | let mut buffer: Vec = Vec::new(); 77 | reader.read_to_end(&mut buffer)?; 78 | let mut text = buffer.into_text().unwrap_or_default(); 79 | text.patch_style(Style::default().fg(color)); 80 | Ok(text) 81 | } 82 | 83 | // The following is a slightly modified 84 | // version of neofetch's Apple ASCII art. 85 | #[cfg(target_os = "macos")] 86 | pub(crate) fn get_ascii_art(size: AsciiSize) -> Vec> { 87 | match size { 88 | AsciiSize::Big => { 89 | let art: Vec = vec![ 90 | Span::styled(" ,MMMM.", *GREEN), 91 | Span::styled(" .MMMMMM", *GREEN), 92 | Span::styled(" MMMMM,", *GREEN), 93 | Span::styled(" .;MMMMM:' MMMMMMMMMM;.", *YELLOW), 94 | Span::styled(" MMMMMMMMMMMMNWMMMMMMMMMMM:", *YELLOW), 95 | Span::styled(" .MMMMMMMMMMMMMMMMMMMMMMMMWM.", *YELLOW), 96 | Span::styled(" MMMMMMMMMMMMMMMMMMMMMMMMM.", *RED), 97 | Span::styled(";MMMMMMMMMMMMMMMMMMMMMMMM:", *RED), 98 | Span::styled(":MMMMMMMMMMMMMMMMMMMMMMMM:", *RED), 99 | Span::styled(".MMMMMMMMMMMMMMMMMMMMMMMMM.", *MAGENTA), 100 | Span::styled(" MMMMMMMMMMMMMMMMMMMMMMMMMMM.", *MAGENTA), 101 | Span::styled(" .MMMMMMMMMMMMMMMMMMMMMMMMMM.", *MAGENTA), 102 | Span::styled(" MMMMMMMMMMMMMMMMMMMMMMMM", *BLUE), 103 | Span::styled(" ;MMMMMMMMMMMMMMMMMMMM.", *BLUE), 104 | Span::styled(" .MMMM,. .MMMM,.", *BLUE), 105 | ]; 106 | 107 | vec![Text::from( 108 | art.iter() 109 | .map(|f| Spans::from(f.to_owned())) 110 | .collect::>(), 111 | )] 112 | } 113 | AsciiSize::Small => { 114 | // The following Apple ASCII art was made by Joan Stark (jgs) 115 | let art: Vec = vec![ 116 | Span::styled(" .:'", *GREEN), 117 | Span::styled(" __ :'__", *GREEN), 118 | Span::styled(" .'` `-' ``.", *YELLOW), 119 | Span::styled(": .-'", *YELLOW), 120 | Span::styled(": :", *RED), 121 | Span::styled(" : `-;", *RED), 122 | Span::styled(" `.__.-.__.'", *MAGENTA), 123 | ]; 124 | 125 | vec![Text::from( 126 | art.iter() 127 | .map(|f| Spans::from(f.to_owned())) 128 | .collect::>(), 129 | )] 130 | } 131 | } 132 | } 133 | 134 | #[cfg(target_os = "android")] 135 | pub(crate) fn get_ascii_art(size: AsciiSize) -> Vec> { 136 | match size { 137 | AsciiSize::Big => { 138 | let art: Vec = vec![ 139 | Span::styled(" . .", *GREEN), 140 | Span::styled(" \\ /", *GREEN), 141 | Span::styled(" .oooooooo.", *GREEN), 142 | Span::styled(" .oooooooooo. ", *GREEN), 143 | Span::styled(" ooo oo ooo", *GREEN), 144 | Span::styled(" oooooooooooo", *GREEN), 145 | Span::styled(" ____________", *GREEN), 146 | Span::styled("oo oooooooooooo oo", *GREEN), 147 | Span::styled("oo oooooooooooo oo", *GREEN), 148 | Span::styled("oo oooooooooooo oo", *GREEN), 149 | Span::styled(" oooooooooooo", *GREEN), 150 | Span::styled(" ooo ooo", *GREEN), 151 | Span::styled(" ooo ooo", *GREEN), 152 | ]; 153 | 154 | vec![Text::from( 155 | art.iter() 156 | .map(|f| Spans::from(f.to_owned())) 157 | .collect::>(), 158 | )] 159 | } 160 | AsciiSize::Small => { 161 | let art: Vec = vec![ 162 | Span::styled(" . . ", *GREEN), 163 | Span::styled(" \\ / ", *GREEN), 164 | Span::styled(" .oooooooo. ", *GREEN), 165 | Span::styled(" .oooooooooo. ", *GREEN), 166 | Span::styled(" ooo oo ooo ", *GREEN), 167 | Span::styled(" oooooooooooo ", *GREEN), 168 | ]; 169 | 170 | vec![Text::from( 171 | art.iter() 172 | .map(|f| Spans::from(f.to_owned())) 173 | .collect::>(), 174 | )] 175 | } 176 | } 177 | } 178 | 179 | #[cfg(target_os = "windows")] 180 | pub(crate) fn get_ascii_art(size: AsciiSize) -> Vec> { 181 | match size { 182 | AsciiSize::Big => { 183 | let art: Vec = vec![ 184 | Span::styled(r#"WWWWWWWWWWWWWW WWWWWWWWWWWWWW"#, *BLUE), 185 | Span::styled(r#"WWWWWWWWWWWWWW WWWWWWWWWWWWWW"#, *BLUE), 186 | Span::styled(r#"WWWWWWWWWWWWWW WWWWWWWWWWWWWW"#, *BLUE), 187 | Span::styled(r#"WWWWWWWWWWWWWW WWWWWWWWWWWWWW"#, *BLUE), 188 | Span::styled(r#"WWWWWWWWWWWWWW WWWWWWWWWWWWWW"#, *BLUE), 189 | Span::styled(r#"WWWWWWWWWWWWWW WWWWWWWWWWWWWW"#, *BLUE), 190 | Span::styled(r#"WWWWWWWWWWWWWW WWWWWWWWWWWWWW"#, *BLUE), 191 | Span::raw(r#" "#), 192 | Span::styled(r#"WWWWWWWWWWWWWW WWWWWWWWWWWWWW"#, *BLUE), 193 | Span::styled(r#"WWWWWWWWWWWWWW WWWWWWWWWWWWWW"#, *BLUE), 194 | Span::styled(r#"WWWWWWWWWWWWWW WWWWWWWWWWWWWW"#, *BLUE), 195 | Span::styled(r#"WWWWWWWWWWWWWW WWWWWWWWWWWWWW"#, *BLUE), 196 | Span::styled(r#"WWWWWWWWWWWWWW WWWWWWWWWWWWWW"#, *BLUE), 197 | Span::styled(r#"WWWWWWWWWWWWWW WWWWWWWWWWWWWW"#, *BLUE), 198 | Span::styled(r#"WWWWWWWWWWWWWW WWWWWWWWWWWWWW"#, *BLUE), 199 | Span::styled(r#"WWWWWWWWWWWWWW WWWWWWWWWWWWWW"#, *BLUE), 200 | ]; 201 | 202 | vec![Text::from( 203 | art.iter() 204 | .map(|f| Spans::from(f.to_owned())) 205 | .collect::>(), 206 | )] 207 | } 208 | AsciiSize::Small => { 209 | let art: Vec = vec![ 210 | Span::styled("wwww wwww", *BLUE), 211 | Span::styled("wwww wwww", *BLUE), 212 | Span::styled("wwww wwww", *BLUE), 213 | Span::raw(r#" "#), 214 | Span::styled("wwww wwww", *BLUE), 215 | Span::styled("wwww wwww", *BLUE), 216 | Span::styled("wwww wwww", *BLUE), 217 | ]; 218 | 219 | vec![Text::from( 220 | art.iter() 221 | .map(|f| Spans::from(f.to_owned())) 222 | .collect::>(), 223 | )] 224 | } 225 | } 226 | } 227 | 228 | // The following penguin ASCII art was made by Joan Stark (jgs) 229 | #[cfg(target_os = "linux")] 230 | pub(crate) fn get_ascii_art(size: AsciiSize) -> Vec> { 231 | match size { 232 | AsciiSize::Big => { 233 | let art: Vec = vec![ 234 | Spans::from(vec![Span::styled(" a8888b.", *WHITE)]), 235 | Spans::from(vec![Span::styled(" d888888b.", *WHITE)]), 236 | Spans::from(vec![Span::styled(" 8P\"YP\"Y88", *WHITE)]), 237 | Spans::from(vec![Span::styled(" 8|o||o|88", *WHITE)]), 238 | Spans::from(vec![ 239 | Span::styled(" 8", *WHITE), 240 | Span::styled("' .", *YELLOW), 241 | Span::styled("88", *WHITE), 242 | ]), 243 | Spans::from(vec![ 244 | Span::styled(" 8", *WHITE), 245 | Span::styled("`._.'", *YELLOW), 246 | Span::styled(" Y8.", *WHITE), 247 | ]), 248 | Spans::from(vec![Span::styled(" d/ `8b.", *WHITE)]), 249 | Spans::from(vec![Span::styled(" dP Y8b.", *WHITE)]), 250 | Spans::from(vec![Span::styled(" d8: ::88b.", *WHITE)]), 251 | Spans::from(vec![Span::styled(" d8\" 'Y88b", *WHITE)]), 252 | Spans::from(vec![Span::styled(" :8P :888", *WHITE)]), 253 | Spans::from(vec![Span::styled(" 8a. _a88P", *WHITE)]), 254 | Spans::from(vec![ 255 | Span::styled("._/\"Y", *YELLOW), 256 | Span::styled("aa .", *WHITE), 257 | Span::styled("|", *YELLOW), 258 | Span::styled(" 88P", *WHITE), 259 | Span::styled("|", *YELLOW), 260 | ]), 261 | Spans::from(vec![ 262 | Span::styled("\\ Y", *YELLOW), 263 | Span::styled("P\" `", *WHITE), 264 | Span::styled("| `.", *YELLOW), 265 | ]), 266 | Spans::from(vec![ 267 | Span::styled("/ \\", *YELLOW), 268 | Span::styled(".___.d", *WHITE), 269 | Span::styled("| .'", *YELLOW), 270 | ]), 271 | Spans::from(vec![Span::styled("`--..__) `._.'", *YELLOW)]), 272 | ]; 273 | 274 | vec![Text::from( 275 | art.iter().map(|f| f.to_owned()).collect::>(), 276 | )] 277 | } 278 | AsciiSize::Small => { 279 | // The following penguin ASCII art was found and 280 | // taken from: https://asciiart.website/index.php 281 | // Artist attribution missing. 282 | // Thank you to whoever made it :^) 283 | let art: Vec = vec![ 284 | Spans::from(vec![Span::styled(" .--.", *WHITE)]), 285 | Spans::from(vec![ 286 | Span::styled(" |o", *WHITE), 287 | Span::styled("_", *YELLOW), 288 | Span::styled("o |", *WHITE), 289 | ]), 290 | Spans::from(vec![ 291 | Span::styled(" |", *WHITE), 292 | Span::styled("\\_/", *YELLOW), 293 | Span::styled(" |", *WHITE), 294 | ]), 295 | Spans::from(vec![Span::styled(" // \\ \\", *WHITE)]), 296 | Spans::from(vec![Span::styled(" (| | )", *WHITE)]), 297 | Spans::from(vec![Span::styled("/'\\_ _/`\\", *WHITE)]), 298 | Spans::from(vec![Span::styled("\\___)=(___/", *WHITE)]), 299 | ]; 300 | 301 | vec![Text::from(art)] 302 | } 303 | } 304 | } 305 | 306 | #[cfg(target_os = "freebsd")] 307 | pub(crate) fn get_ascii_art(size: AsciiSize) -> Vec> { 308 | // The following ASCII art was made by Dylan Araps 309 | // and taken from https://github.com/dylanaraps/pfetch 310 | 311 | let art: Vec = vec![ 312 | Spans::from(vec![Span::styled("/\\,-'''''-,/\\", *RED)]), 313 | Spans::from(vec![Span::styled("\\_) (_//", *RED)]), 314 | Spans::from(vec![Span::styled(" | |", *RED)]), 315 | Spans::from(vec![Span::styled(" | |", *RED)]), 316 | Spans::from(vec![Span::styled(" ; ;", *RED)]), 317 | Spans::from(vec![Span::styled(" '-_____-'", *RED)]), 318 | ]; 319 | 320 | vec![Text::from(art)] 321 | } 322 | 323 | #[cfg(target_os = "netbsd")] 324 | pub(crate) fn get_ascii_art(size: AsciiSize) -> Vec> { 325 | match size { 326 | AsciiSize::Big => { 327 | let art: Vec = vec![ 328 | Spans::from(vec![ 329 | Span::styled("\\\\", *WHITE), 330 | Span::styled("`-______,----__", *YELLOW), 331 | ]), 332 | Spans::from(vec![ 333 | Span::styled(" \\\\", *WHITE), 334 | Span::styled(" __,---`.", *YELLOW), 335 | ]), 336 | Spans::from(vec![ 337 | Span::styled(" \\\\", *WHITE), 338 | Span::styled(" `.____", *YELLOW), 339 | ]), 340 | Spans::from(vec![ 341 | Span::styled(" \\\\", *WHITE), 342 | Span::styled("-______,----`.", *YELLOW), 343 | ]), 344 | Spans::from(vec![Span::styled(" \\\\", *WHITE)]), 345 | Spans::from(vec![Span::styled(" \\\\", *WHITE)]), 346 | Spans::from(vec![Span::styled(" \\\\", *WHITE)]), 347 | Spans::from(vec![Span::styled(" \\\\", *WHITE)]), 348 | Spans::from(vec![Span::styled(" \\\\", *WHITE)]), 349 | Spans::from(vec![Span::styled(" \\\\", *WHITE)]), 350 | Spans::from(vec![Span::styled(" \\\\", *WHITE)]), 351 | ]; 352 | 353 | vec![Text::from( 354 | art.iter().map(|f| f.to_owned()).collect::>(), 355 | )] 356 | } 357 | AsciiSize::Small => { 358 | let art: Vec = vec![ 359 | Spans::from(vec![ 360 | Span::styled("()", *BLACK), 361 | Span::styled("ncncncncncnc", *YELLOW), 362 | ]), 363 | Spans::from(vec![ 364 | Span::styled(" \\\\", *BLACK), 365 | Span::styled("ncncncnc", *YELLOW), 366 | ]), 367 | Spans::from(vec![ 368 | Span::styled(" \\\\", *BLACK), 369 | Span::styled("ncncncncncn", *YELLOW), 370 | ]), 371 | Spans::from(vec![Span::styled(" \\\\", *BLACK)]), 372 | Spans::from(vec![Span::styled(" \\\\", *BLACK)]), 373 | Spans::from(vec![Span::styled(" \\\\", *BLACK)]), 374 | ]; 375 | 376 | vec![Text::from(art)] 377 | } 378 | } 379 | } 380 | -------------------------------------------------------------------------------- /src/data/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::cli::Opt; 2 | use crate::theme::Theme; 3 | use clap::{Parser, ValueEnum}; 4 | use libmacchina::traits::GeneralReadout as _; 5 | use libmacchina::traits::ShellFormat; 6 | use libmacchina::traits::{ReadoutError, ShellKind}; 7 | use libmacchina::{BatteryReadout, GeneralReadout, KernelReadout, MemoryReadout, PackageReadout}; 8 | use serde::{Deserialize, Serialize}; 9 | use std::borrow::Cow; 10 | use std::fmt::Display; 11 | use tui::style::{Color, Style}; 12 | use tui::text::{Span, Spans, Text}; 13 | 14 | /// This enum contains all the possible keys, e.g. _Host_, _Machine_, _Kernel_, etc. 15 | #[allow(clippy::upper_case_acronyms)] 16 | #[derive(Parser, ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] 17 | pub enum ReadoutKey { 18 | Host, 19 | Machine, 20 | Kernel, 21 | Distribution, 22 | OperatingSystem, 23 | DesktopEnvironment, 24 | WindowManager, 25 | Packages, 26 | Shell, 27 | Terminal, 28 | LocalIP, 29 | Backlight, 30 | Resolution, 31 | Uptime, 32 | Processor, 33 | ProcessorLoad, 34 | Memory, 35 | Battery, 36 | } 37 | 38 | impl Display for ReadoutKey { 39 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 40 | match *self { 41 | Self::Host => write!(f, "Host"), 42 | Self::Machine => write!(f, "Machine"), 43 | Self::Kernel => write!(f, "Kernel"), 44 | Self::Distribution => write!(f, "Distribution"), 45 | Self::OperatingSystem => write!(f, "OperatingSystem"), 46 | Self::DesktopEnvironment => write!(f, "DesktopEnvironment"), 47 | Self::WindowManager => write!(f, "WindowManager"), 48 | Self::Packages => write!(f, "Packages"), 49 | Self::Shell => write!(f, "Shell"), 50 | Self::Terminal => write!(f, "Terminal"), 51 | Self::LocalIP => write!(f, "LocalIP"), 52 | Self::Backlight => write!(f, "Backlight"), 53 | Self::Resolution => write!(f, "Resolution"), 54 | Self::Uptime => write!(f, "Uptime"), 55 | Self::Processor => write!(f, "Processor"), 56 | Self::ProcessorLoad => write!(f, "ProcessorLoad"), 57 | Self::Memory => write!(f, "Memory"), 58 | Self::Battery => write!(f, "Battery"), 59 | } 60 | } 61 | } 62 | 63 | #[derive(Debug, Clone)] 64 | pub struct Readout<'a>(pub ReadoutKey, pub Result, ReadoutError>); 65 | 66 | impl<'a> Readout<'a> { 67 | pub fn new_err(readout_key: ReadoutKey, err: ReadoutError) -> Readout<'a> { 68 | Readout(readout_key, Err(err)) 69 | } 70 | 71 | pub fn new(readout_key: ReadoutKey, text: T) -> Readout<'a> 72 | where 73 | T: Into>, 74 | { 75 | Readout(readout_key, Ok(text.into())) 76 | } 77 | } 78 | 79 | fn colored_glyphs(glyph: &str, blocks: usize) -> String { 80 | glyph 81 | .repeat(blocks) 82 | .chars() 83 | .collect::>() 84 | .chunks(1) 85 | .map(|c| c.iter().collect::()) 86 | .collect::>() 87 | .join(" ") 88 | } 89 | 90 | fn create_bar<'a>(theme: &Theme, blocks: usize) -> Spans<'a> { 91 | if theme.get_bar().are_delimiters_hidden() { 92 | let mut span_vector = vec![Span::raw(""), Span::raw("")]; 93 | 94 | let glyph = theme.get_bar().get_glyph(); 95 | let glyphs = colored_glyphs(glyph, blocks); 96 | 97 | if blocks == 10 { 98 | span_vector[0].content = Cow::from(glyphs); 99 | } else { 100 | span_vector[0].content = Cow::from(format!("{glyphs} ")); 101 | } 102 | 103 | span_vector[0].style = Style::default().fg(theme.get_key_color()); 104 | span_vector[1].content = Cow::from(colored_glyphs(glyph, 10 - blocks)); 105 | 106 | if theme.get_key_color() == Color::White { 107 | span_vector[1].content = Cow::from(span_vector[1].content.replace(glyph, " ")); 108 | } 109 | return Spans::from(span_vector); 110 | } 111 | 112 | let mut span_vector = vec![ 113 | Span::raw(format!("{} ", theme.get_bar().get_symbol_open())), 114 | Span::raw(""), 115 | Span::raw(""), 116 | Span::raw(format!(" {}", theme.get_bar().get_symbol_close())), 117 | ]; 118 | 119 | let glyph = theme.get_bar().get_glyph(); 120 | let glyphs = colored_glyphs(glyph, blocks); 121 | 122 | if blocks == 10 { 123 | span_vector[1].content = Cow::from(glyphs); 124 | } else { 125 | span_vector[1].content = Cow::from(format!("{glyphs} ")); 126 | } 127 | span_vector[1].style = Style::default().fg(theme.get_key_color()); 128 | 129 | span_vector[2].content = Cow::from(colored_glyphs(glyph, 10 - blocks)); 130 | if theme.get_key_color() == Color::White { 131 | span_vector[2].content = Cow::from(span_vector[2].content.replace(glyph, " ")); 132 | } 133 | Spans::from(span_vector) 134 | } 135 | 136 | pub fn should_display(opt: &Opt) -> Vec { 137 | if let Some(shown) = opt.show.to_owned() { 138 | return shown; 139 | } 140 | 141 | let keys: Vec = ReadoutKey::value_variants() 142 | .iter() 143 | .map(|f| ReadoutKey::from_str(&f.to_string(), true).unwrap_or(*f)) 144 | .collect(); 145 | 146 | keys 147 | } 148 | 149 | pub fn get_all_readouts<'a>( 150 | opt: &Opt, 151 | theme: &Theme, 152 | should_display: &[ReadoutKey], 153 | ) -> Vec> { 154 | let mut readout_values = Vec::with_capacity(ReadoutKey::value_variants().len()); 155 | let general_readout: GeneralReadout = GeneralReadout::new(); 156 | 157 | for readout_key in should_display { 158 | match readout_key { 159 | ReadoutKey::Host => handle_readout_host(&mut readout_values, &general_readout), 160 | ReadoutKey::Machine => handle_readout_machine(&mut readout_values, &general_readout), 161 | ReadoutKey::Kernel => handle_readout_kernel(&mut readout_values, opt), 162 | ReadoutKey::OperatingSystem => { 163 | handle_readout_operating_system(&mut readout_values, &general_readout) 164 | } 165 | ReadoutKey::Distribution => { 166 | handle_readout_distribution(&mut readout_values, &general_readout) 167 | } 168 | ReadoutKey::Packages => handle_readout_packages(&mut readout_values), 169 | ReadoutKey::LocalIP => handle_readout_local_ip(&mut readout_values, opt), 170 | ReadoutKey::Terminal => handle_readout_terminal(&mut readout_values, &general_readout), 171 | ReadoutKey::Shell => handle_readout_shell(&mut readout_values, &general_readout, opt), 172 | ReadoutKey::Uptime => handle_readout_uptime(&mut readout_values, &general_readout, opt), 173 | ReadoutKey::Resolution => { 174 | handle_readout_resolution(&mut readout_values, &general_readout) 175 | } 176 | ReadoutKey::Backlight => { 177 | handle_readout_backlight(&mut readout_values, &general_readout, theme) 178 | } 179 | ReadoutKey::Processor => { 180 | handle_readout_processor(&mut readout_values, &general_readout, opt) 181 | } 182 | ReadoutKey::ProcessorLoad => { 183 | handle_readout_processor_load(&mut readout_values, &general_readout, theme) 184 | } 185 | ReadoutKey::Memory => handle_readout_memory(&mut readout_values, theme), 186 | ReadoutKey::Battery => handle_readout_battery(&mut readout_values, theme), 187 | ReadoutKey::DesktopEnvironment => { 188 | handle_readout_desktop_environment(&mut readout_values, &general_readout) 189 | } 190 | ReadoutKey::WindowManager => { 191 | handle_readout_window_manager(&mut readout_values, &general_readout) 192 | } 193 | }; 194 | } 195 | 196 | readout_values 197 | } 198 | 199 | // READOUT HANDLERS 200 | fn handle_readout_host(readout_values: &mut Vec, general_readout: &GeneralReadout) { 201 | use crate::format::host as format_host; 202 | 203 | match (general_readout.username(), general_readout.hostname()) { 204 | (Ok(u), Ok(h)) => readout_values.push(Readout::new(ReadoutKey::Host, format_host(&u, &h))), 205 | (Err(e), _) | (_, Err(e)) => readout_values.push(Readout::new_err(ReadoutKey::Host, e)), 206 | } 207 | } 208 | 209 | fn handle_readout_machine(readout_values: &mut Vec, general_readout: &GeneralReadout) { 210 | match general_readout.machine() { 211 | Ok(s) => readout_values.push(Readout::new(ReadoutKey::Machine, s)), 212 | Err(e) => readout_values.push(Readout::new_err(ReadoutKey::Machine, e)), 213 | } 214 | } 215 | 216 | fn handle_readout_kernel(readout_values: &mut Vec, opt: &Opt) { 217 | use libmacchina::traits::KernelReadout as _; 218 | 219 | let kernel_readout = KernelReadout::new(); 220 | 221 | if opt.long_kernel { 222 | match kernel_readout.pretty_kernel() { 223 | Ok(s) => readout_values.push(Readout::new(ReadoutKey::Kernel, s)), 224 | Err(e) => readout_values.push(Readout::new_err(ReadoutKey::Kernel, e)), 225 | } 226 | } else { 227 | match kernel_readout.os_release() { 228 | Ok(s) => readout_values.push(Readout::new(ReadoutKey::Kernel, s)), 229 | Err(e) => readout_values.push(Readout::new_err(ReadoutKey::Kernel, e)), 230 | } 231 | } 232 | } 233 | 234 | fn handle_readout_operating_system( 235 | readout_values: &mut Vec, 236 | general_readout: &GeneralReadout, 237 | ) { 238 | match general_readout.os_name() { 239 | Ok(s) => readout_values.push(Readout::new(ReadoutKey::OperatingSystem, s)), 240 | Err(e) => readout_values.push(Readout::new_err(ReadoutKey::OperatingSystem, e)), 241 | } 242 | } 243 | fn handle_readout_distribution( 244 | readout_values: &mut Vec, 245 | general_readout: &GeneralReadout, 246 | ) { 247 | match general_readout.distribution() { 248 | Ok(s) => readout_values.push(Readout::new(ReadoutKey::Distribution, s)), 249 | Err(e) => readout_values.push(Readout::new_err(ReadoutKey::Distribution, e)), 250 | } 251 | } 252 | 253 | fn handle_readout_packages(readout_values: &mut Vec) { 254 | use crate::format::packages as format_pkgs; 255 | use libmacchina::traits::PackageReadout as _; 256 | 257 | let package_readout = PackageReadout::new(); 258 | 259 | let packages = package_readout.count_pkgs(); 260 | match format_pkgs(packages) { 261 | Ok(s) => readout_values.push(Readout::new(ReadoutKey::Packages, s)), 262 | Err(e) => readout_values.push(Readout::new_err(ReadoutKey::Packages, e)), 263 | } 264 | } 265 | 266 | fn handle_readout_local_ip(readout_values: &mut Vec, opt: &Opt) { 267 | use libmacchina::traits::NetworkReadout as _; 268 | use libmacchina::NetworkReadout; 269 | 270 | let network_readout = NetworkReadout::new(); 271 | match network_readout.logical_address(opt.interface.as_deref()) { 272 | Ok(s) => readout_values.push(Readout::new(ReadoutKey::LocalIP, s)), 273 | Err(e) => readout_values.push(Readout::new_err(ReadoutKey::LocalIP, e)), 274 | } 275 | } 276 | fn handle_readout_terminal(readout_values: &mut Vec, general_readout: &GeneralReadout) { 277 | match general_readout.terminal() { 278 | Ok(s) => readout_values.push(Readout::new(ReadoutKey::Terminal, s)), 279 | Err(e) => readout_values.push(Readout::new_err(ReadoutKey::Terminal, e)), 280 | } 281 | } 282 | 283 | fn handle_readout_shell( 284 | readout_values: &mut Vec, 285 | general_readout: &GeneralReadout, 286 | opt: &Opt, 287 | ) { 288 | let (ls, cs) = ( 289 | if opt.long_shell { 290 | ShellFormat::Absolute 291 | } else { 292 | ShellFormat::Relative 293 | }, 294 | if opt.current_shell { 295 | ShellKind::Current 296 | } else { 297 | ShellKind::Default 298 | }, 299 | ); 300 | 301 | match general_readout.shell(ls, cs) { 302 | Ok(s) => readout_values.push(Readout::new(ReadoutKey::Shell, s)), 303 | Err(e) => readout_values.push(Readout::new_err(ReadoutKey::Shell, e)), 304 | }; 305 | } 306 | fn handle_readout_uptime( 307 | readout_values: &mut Vec, 308 | general_readout: &GeneralReadout, 309 | opt: &Opt, 310 | ) { 311 | use crate::format::uptime as format_uptime; 312 | match general_readout.uptime() { 313 | Ok(s) => readout_values.push(Readout::new( 314 | ReadoutKey::Uptime, 315 | format_uptime(s, opt.long_uptime), 316 | )), 317 | Err(e) => readout_values.push(Readout::new_err(ReadoutKey::Uptime, e)), 318 | } 319 | } 320 | 321 | fn handle_readout_resolution(readout_values: &mut Vec, general_readout: &GeneralReadout) { 322 | match general_readout.resolution() { 323 | Ok(r) => readout_values.push(Readout::new(ReadoutKey::Resolution, r)), 324 | Err(e) => readout_values.push(Readout::new_err(ReadoutKey::Resolution, e)), 325 | } 326 | } 327 | 328 | fn handle_readout_backlight( 329 | readout_values: &mut Vec, 330 | general_readout: &GeneralReadout, 331 | theme: &Theme, 332 | ) { 333 | match (general_readout.backlight(), theme.get_bar().is_visible()) { 334 | (Ok(b), false) => { 335 | readout_values.push(Readout::new(ReadoutKey::Backlight, format!("{}%", b))) 336 | } 337 | (Ok(b), true) => readout_values.push(Readout::new( 338 | ReadoutKey::Backlight, 339 | create_bar(theme, crate::bars::num_to_blocks(b as u8)), 340 | )), 341 | (Err(e), _) => readout_values.push(Readout::new_err(ReadoutKey::Backlight, e)), 342 | } 343 | } 344 | 345 | fn handle_readout_processor( 346 | readout_values: &mut Vec, 347 | general_readout: &GeneralReadout, 348 | opt: &Opt, 349 | ) { 350 | use crate::format::cpu as format_cpu; 351 | use crate::format::cpu_only as format_cpu_only; 352 | 353 | let cores = { 354 | if opt.physical_cores { 355 | general_readout.cpu_physical_cores() 356 | } else { 357 | general_readout.cpu_cores() 358 | } 359 | }; 360 | 361 | match (general_readout.cpu_model_name(), cores) { 362 | (Ok(m), Ok(c)) => { 363 | readout_values.push(Readout::new(ReadoutKey::Processor, format_cpu(&m, c))) 364 | } 365 | (Ok(m), _) => readout_values.push(Readout::new(ReadoutKey::Processor, format_cpu_only(&m))), 366 | (Err(e), _) => readout_values.push(Readout::new_err(ReadoutKey::Processor, e)), 367 | } 368 | } 369 | 370 | fn handle_readout_processor_load( 371 | readout_values: &mut Vec, 372 | general_readout: &GeneralReadout, 373 | theme: &Theme, 374 | ) { 375 | use crate::format::cpu_usage as format_cpu_usage; 376 | match (general_readout.cpu_usage(), theme.get_bar().is_visible()) { 377 | (Ok(u), true) => { 378 | if u > 100 { 379 | readout_values.push(Readout::new( 380 | ReadoutKey::ProcessorLoad, 381 | create_bar(theme, crate::bars::num_to_blocks(100_u8)), 382 | )) 383 | } 384 | readout_values.push(Readout::new( 385 | ReadoutKey::ProcessorLoad, 386 | create_bar(theme, crate::bars::num_to_blocks(u as u8)), 387 | )) 388 | } 389 | (Ok(u), _) => { 390 | readout_values.push(Readout::new(ReadoutKey::ProcessorLoad, format_cpu_usage(u))) 391 | } 392 | (Err(e), _) => readout_values.push(Readout::new_err(ReadoutKey::ProcessorLoad, e)), 393 | } 394 | } 395 | 396 | fn handle_readout_memory(readout_values: &mut Vec, theme: &Theme) { 397 | use crate::format::memory as format_mem; 398 | use libmacchina::traits::MemoryReadout as _; 399 | 400 | let memory_readout = MemoryReadout::new(); 401 | let total = memory_readout.total(); 402 | let used = memory_readout.used(); 403 | 404 | match (total, used) { 405 | (Ok(total), Ok(used)) => { 406 | if theme.get_bar().is_visible() { 407 | let bar = create_bar(theme, crate::bars::memory(used, total)); 408 | readout_values.push(Readout::new(ReadoutKey::Memory, bar)) 409 | } else { 410 | readout_values.push(Readout::new(ReadoutKey::Memory, format_mem(total, used))) 411 | } 412 | } 413 | (Err(e), _) | (_, Err(e)) => readout_values.push(Readout::new_err(ReadoutKey::Memory, e)), 414 | } 415 | } 416 | 417 | fn handle_readout_battery(readout_values: &mut Vec, theme: &Theme) { 418 | use crate::format::battery as format_bat; 419 | use libmacchina::traits::BatteryReadout as _; 420 | 421 | let battery_readout = BatteryReadout::new(); 422 | let key = ReadoutKey::Battery; 423 | 424 | let percentage = battery_readout.percentage(); 425 | let state = battery_readout.status(); 426 | 427 | match (percentage, state) { 428 | (Ok(p), Ok(s)) => { 429 | if theme.get_bar().is_visible() { 430 | let bar = create_bar(theme, crate::bars::num_to_blocks(p)); 431 | readout_values.push(Readout::new(key, bar)); 432 | } else { 433 | readout_values.push(Readout::new(key, format_bat(p, s))); 434 | } 435 | } 436 | (Err(e), _) | (_, Err(e)) => readout_values.push(Readout::new_err(key, e)), 437 | } 438 | } 439 | 440 | fn handle_readout_desktop_environment( 441 | readout_values: &mut Vec, 442 | general_readout: &GeneralReadout, 443 | ) { 444 | let desktop_environment = general_readout.desktop_environment(); 445 | let window_manager = general_readout.window_manager(); 446 | 447 | match &desktop_environment { 448 | Ok(d) => match &window_manager { 449 | // check if the user is using a window manager only. 450 | Ok(w) if w.eq_ignore_ascii_case(d) => { 451 | readout_values.push(Readout::new_err( 452 | ReadoutKey::DesktopEnvironment, 453 | ReadoutError::Warning(String::from( 454 | "You appear to be only running a window manager.", 455 | )), 456 | )); 457 | } 458 | _ => { 459 | readout_values.push(Readout::new(ReadoutKey::DesktopEnvironment, d.to_owned())); 460 | } 461 | }, 462 | Err(e) => readout_values.push(Readout::new_err( 463 | ReadoutKey::DesktopEnvironment, 464 | e.to_owned(), 465 | )), 466 | } 467 | } 468 | 469 | fn handle_readout_window_manager( 470 | readout_values: &mut Vec, 471 | general_readout: &GeneralReadout, 472 | ) { 473 | let window_manager = general_readout.window_manager(); 474 | let session = general_readout.session(); 475 | 476 | match window_manager { 477 | Ok(w) => match session { 478 | Ok(s) => { 479 | readout_values.push(Readout::new( 480 | ReadoutKey::WindowManager, 481 | format!("{} ({})", w, s), 482 | )); 483 | } 484 | _ => readout_values.push(Readout::new(ReadoutKey::WindowManager, w)), 485 | }, 486 | Err(e) => readout_values.push(Readout::new_err(ReadoutKey::WindowManager, e)), 487 | } 488 | } 489 | --------------------------------------------------------------------------------