Merge pull request #1 from NamedNeon/main
Terminal detection support, fix Darwin identification, and minor refactoring.
This commit is contained in:
commit
7bf4a27e8a
5
Cargo.lock
generated
5
Cargo.lock
generated
@ -256,9 +256,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.139"
|
version = "0.2.147"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
|
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "link-cplusplus"
|
name = "link-cplusplus"
|
||||||
@ -339,6 +339,7 @@ dependencies = [
|
|||||||
"chrono",
|
"chrono",
|
||||||
"colored",
|
"colored",
|
||||||
"compound_duration",
|
"compound_duration",
|
||||||
|
"lazy_static",
|
||||||
"quoted-string",
|
"quoted-string",
|
||||||
"sysinfo",
|
"sysinfo",
|
||||||
"whoami",
|
"whoami",
|
||||||
|
@ -10,6 +10,7 @@ byte-unit = "4.0.18"
|
|||||||
chrono = "0.4.23"
|
chrono = "0.4.23"
|
||||||
colored = "2.0.0"
|
colored = "2.0.0"
|
||||||
compound_duration = "1.2.0"
|
compound_duration = "1.2.0"
|
||||||
|
lazy_static = "1.4.0"
|
||||||
quoted-string = "0.6.1"
|
quoted-string = "0.6.1"
|
||||||
sysinfo = "0.28.1"
|
sysinfo = "0.28.1"
|
||||||
whoami = "1.3.0"
|
whoami = "1.3.0"
|
||||||
@ -22,4 +23,4 @@ codegen-units = 1
|
|||||||
panic = "abort"
|
panic = "abort"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
field-titles = []
|
field-titles = []
|
||||||
|
@ -18,7 +18,8 @@ OxideFetch can display all of the following information:
|
|||||||
- Operating system name, symbol, and matching color
|
- Operating system name, symbol, and matching color
|
||||||
- Kernel version
|
- Kernel version
|
||||||
- Uptime
|
- Uptime
|
||||||
- Shell
|
- Shell
|
||||||
|
- Terminal
|
||||||
- CPU
|
- CPU
|
||||||
- GPU
|
- GPU
|
||||||
- Memory
|
- Memory
|
||||||
@ -63,8 +64,7 @@ No weird quirks to report at this time.
|
|||||||
- Add host system name detection such as "Windows Subsystem for Linux", "IdeaPad 3", "Dell Optiplex", etc.
|
- Add host system name detection such as "Windows Subsystem for Linux", "IdeaPad 3", "Dell Optiplex", etc.
|
||||||
- Add package count/package manager detection
|
- Add package count/package manager detection
|
||||||
- Crosstest on more distributions to verify `sys.name()` outputs
|
- Crosstest on more distributions to verify `sys.name()` outputs
|
||||||
#### Distant future:
|
#### Distant future:
|
||||||
- Add terminal emulator detection by walking up the process tree
|
|
||||||
- More extensible user configuration for entry formatting
|
- More extensible user configuration for entry formatting
|
||||||
|
|
||||||
### Changelog
|
### Changelog
|
||||||
|
56
src/main.rs
56
src/main.rs
@ -22,6 +22,8 @@ SOFTWARE.
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
mod terminal;
|
||||||
|
|
||||||
use byte_unit::*;
|
use byte_unit::*;
|
||||||
use chrono::*;
|
use chrono::*;
|
||||||
use colored::*;
|
use colored::*;
|
||||||
@ -29,10 +31,11 @@ use compound_duration;
|
|||||||
use std::env;
|
use std::env;
|
||||||
use sysinfo::*;
|
use sysinfo::*;
|
||||||
use whoami;
|
use whoami;
|
||||||
|
use crate::terminal::get_terminal;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// Generate system info struct
|
// Generate system info struct
|
||||||
let sys_info = InformationStruct::new();
|
let sys_info = Information::new();
|
||||||
|
|
||||||
// Format the date and time
|
// Format the date and time
|
||||||
let datetime_formatted = format!(
|
let datetime_formatted = format!(
|
||||||
@ -41,12 +44,10 @@ fn main() {
|
|||||||
Utc::now().format("%H:%M %Y-%m-%d")
|
Utc::now().format("%H:%M %Y-%m-%d")
|
||||||
);
|
);
|
||||||
|
|
||||||
println!();
|
|
||||||
|
|
||||||
// TODO: Add support to change what items print, as well as their colors.
|
// TODO: Add support to change what items print, as well as their colors.
|
||||||
// This should be done via some sort of user accessible, persistent config,
|
// This should be done via some sort of user accessible, persistent config,
|
||||||
// and preferably can be modified via env vars.
|
// and preferably can be modified via env vars.
|
||||||
println!("{}", String::from(">>> OxideFetch <<<").red());
|
println!("\n{}", String::from(">>> OxideFetch <<<").red());
|
||||||
color_print("Date:\t", '', &Some(datetime_formatted), "bright yellow");
|
color_print("Date:\t", '', &Some(datetime_formatted), "bright yellow");
|
||||||
color_print(
|
color_print(
|
||||||
"Host:\t",
|
"Host:\t",
|
||||||
@ -59,12 +60,16 @@ fn main() {
|
|||||||
color_print("Kernel:\t", '', &sys_info.kernel_ver, "bright blue");
|
color_print("Kernel:\t", '', &sys_info.kernel_ver, "bright blue");
|
||||||
color_print("Uptime:\t", '', &Some(sys_info.uptime), "bright gray");
|
color_print("Uptime:\t", '', &Some(sys_info.uptime), "bright gray");
|
||||||
color_print("Shell:\t", '', &sys_info.shell, "bright magenta");
|
color_print("Shell:\t", '', &sys_info.shell, "bright magenta");
|
||||||
|
color_print("Terminal:\t", '', &sys_info.terminal, "bright green");
|
||||||
color_print("CPU:\t", '', &Some(sys_info.cpu), "green");
|
color_print("CPU:\t", '', &Some(sys_info.cpu), "green");
|
||||||
if let Some(gpus) = sys_info.gpu {
|
|
||||||
|
if sys_info.gpu.is_some() {
|
||||||
|
let gpus = sys_info.gpu.unwrap();
|
||||||
for gpu in gpus {
|
for gpu in gpus {
|
||||||
color_print("GPU:\t", '', &Some(gpu), "bright green")
|
color_print("GPU:\t", '', &Some(gpu), "bright green")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//color_print("GPU:\t", '', &sys_info.gpu, "bright green");
|
//color_print("GPU:\t", '', &sys_info.gpu, "bright green");
|
||||||
color_print("Memory:\t", '', &Some(sys_info.memory), "bright blue");
|
color_print("Memory:\t", '', &Some(sys_info.memory), "bright blue");
|
||||||
}
|
}
|
||||||
@ -82,7 +87,7 @@ fn color_print(field_title: &str, icon: char, field: &Option<String>, color: &st
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct InformationStruct {
|
struct Information {
|
||||||
// Only fields whose setters can fail, are given Option<String> types.
|
// Only fields whose setters can fail, are given Option<String> types.
|
||||||
// Unsure if I should coerce these fields into an Option<String> *here*, or
|
// Unsure if I should coerce these fields into an Option<String> *here*, or
|
||||||
// within the args of color_print, since that function only accepts args of
|
// within the args of color_print, since that function only accepts args of
|
||||||
@ -94,7 +99,7 @@ struct InformationStruct {
|
|||||||
kernel_ver: Option<String>,
|
kernel_ver: Option<String>,
|
||||||
uptime: String,
|
uptime: String,
|
||||||
shell: Option<String>,
|
shell: Option<String>,
|
||||||
_terminal: Option<String>,
|
terminal: Option<String>,
|
||||||
cpu: String,
|
cpu: String,
|
||||||
gpu: Option<Vec<String>>,
|
gpu: Option<Vec<String>>,
|
||||||
memory: String,
|
memory: String,
|
||||||
@ -102,21 +107,31 @@ struct InformationStruct {
|
|||||||
color: String,
|
color: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InformationStruct {
|
impl Information {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
let mut sys = System::new_all();
|
let mut sys = System::new_all();
|
||||||
sys.refresh_all();
|
sys.refresh_all();
|
||||||
|
|
||||||
|
let mut os_name = sys.name();
|
||||||
|
|
||||||
|
// Get full OS name for Darwin-based systems (i.e. macOS, iOS).
|
||||||
|
if os_name == Some("Darwin".to_string()) {
|
||||||
|
let long_os = sys.long_os_version();
|
||||||
|
if long_os.is_some() {
|
||||||
|
// Isolate system type from version information.
|
||||||
|
let long_os_split =
|
||||||
|
long_os.unwrap().split_whitespace().collect::<Vec<&str>>()[0].to_string();
|
||||||
|
|
||||||
|
os_name = Some(long_os_split);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
username: whoami::username(),
|
username: whoami::username(),
|
||||||
|
|
||||||
hostname: whoami::hostname(),
|
hostname: whoami::hostname(),
|
||||||
|
os_name: os_name.clone(),
|
||||||
os_name: sys.name(),
|
|
||||||
|
|
||||||
os_ver: sys.os_version(),
|
os_ver: sys.os_version(),
|
||||||
|
|
||||||
kernel_ver: sys.kernel_version(),
|
kernel_ver: sys.kernel_version(),
|
||||||
|
|
||||||
uptime: compound_duration::format_dhms(sys.uptime()),
|
uptime: compound_duration::format_dhms(sys.uptime()),
|
||||||
|
|
||||||
// Tracks the SHELL env var and trims the last item from the resultant fs path.
|
// Tracks the SHELL env var and trims the last item from the resultant fs path.
|
||||||
@ -129,8 +144,7 @@ impl InformationStruct {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_terminal: None, // TODO: Add terminal detection.
|
terminal: get_terminal(),
|
||||||
|
|
||||||
cpu: String::from(sys.cpus()[0].brand()),
|
cpu: String::from(sys.cpus()[0].brand()),
|
||||||
|
|
||||||
gpu: {
|
gpu: {
|
||||||
@ -198,8 +212,7 @@ impl InformationStruct {
|
|||||||
Byte::from(sys.total_memory()).get_appropriate_unit(true)
|
Byte::from(sys.total_memory()).get_appropriate_unit(true)
|
||||||
),
|
),
|
||||||
|
|
||||||
icon: match sys
|
icon: match os_name
|
||||||
.name()
|
|
||||||
.unwrap_or(String::from("Unknown System"))
|
.unwrap_or(String::from("Unknown System"))
|
||||||
.as_ref()
|
.as_ref()
|
||||||
// Getting the icon for the distro.
|
// Getting the icon for the distro.
|
||||||
@ -226,7 +239,7 @@ impl InformationStruct {
|
|||||||
"Windows" => '',
|
"Windows" => '',
|
||||||
"Android" => '',
|
"Android" => '',
|
||||||
"iOS" => '',
|
"iOS" => '',
|
||||||
"macOS" => '',
|
"MacOS" => '',
|
||||||
"Unknown System" => '?',
|
"Unknown System" => '?',
|
||||||
_ => {
|
_ => {
|
||||||
if sys
|
if sys
|
||||||
@ -270,13 +283,14 @@ impl InformationStruct {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
|
||||||
use crate::InformationStruct;
|
use crate::Information;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
use crate::terminal::get_terminal;
|
||||||
|
|
||||||
// Self explanatory.
|
// Self explanatory.
|
||||||
#[test]
|
#[test]
|
||||||
pub fn log_gathered_data() {
|
pub fn log_gathered_data() {
|
||||||
let sys_info = InformationStruct::new();
|
let sys_info = Information::new();
|
||||||
let data_string = format!("{:#?}", sys_info);
|
let data_string = format!("{:#?}", sys_info);
|
||||||
let result = fs::write("./test_output.txt", data_string);
|
let result = fs::write("./test_output.txt", data_string);
|
||||||
|
|
||||||
|
162
src/terminal.rs
Normal file
162
src/terminal.rs
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
use std::env;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use sysinfo::{Pid, PidExt, ProcessExt, System, SystemExt};
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref PRETTY_NAMES: HashMap<&'static str, &'static str> = {
|
||||||
|
let mut m = HashMap::new();
|
||||||
|
m.insert("cmd", "Command Prompt");
|
||||||
|
m.insert("powershell", "PowerShell");
|
||||||
|
m.insert("konsole", "Konsole");
|
||||||
|
m.insert("gnome-terminal", "GNOME Terminal");
|
||||||
|
m.insert("xterm", "XTerm");
|
||||||
|
m.insert("xfce4-terminal", "XFCE Terminal");
|
||||||
|
m.insert("kitty", "KiTTY");
|
||||||
|
m.insert("alacritty", "Alacritty");
|
||||||
|
m
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allows detection of shells that host themselves (i.e. Command Prompt).
|
||||||
|
const SELF_HOSTED_SHELLS: [&str; 2] = [
|
||||||
|
"cmd.exe",
|
||||||
|
"powershell.exe"
|
||||||
|
];
|
||||||
|
|
||||||
|
macro_rules! match_env_to_terminal {
|
||||||
|
($env: expr, $name: expr) => {
|
||||||
|
match env::var($env) {
|
||||||
|
Ok(_) => return Some($name.to_string()),
|
||||||
|
Err(_) => (),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_terminal() -> Option<String> {
|
||||||
|
// Match Apple terminals.
|
||||||
|
match env::var("TERM_PROGRAM") {
|
||||||
|
Ok(f) => {
|
||||||
|
return match f.as_str() {
|
||||||
|
"iTerm.app" => Some("iTerm2".to_string()),
|
||||||
|
"Terminal.app" => Some("Apple Terminal".to_string()),
|
||||||
|
"Hyper.app" => Some("Hyper".to_string()),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Err(_) => (),
|
||||||
|
};
|
||||||
|
|
||||||
|
match_env_to_terminal!("ConEmuPID", "ConEmu");
|
||||||
|
match_env_to_terminal!("WT_SESSION", "Windows Terminal");
|
||||||
|
match_env_to_terminal!("SSH_CONNECTION", "SSH");
|
||||||
|
match_env_to_terminal!("tw52", "TosWin2");
|
||||||
|
match_env_to_terminal!("tw100", "TosWin2");
|
||||||
|
|
||||||
|
let mut pid = Pid::from_u32(std::process::id());
|
||||||
|
let shell = match env::var("SHELL") {
|
||||||
|
Ok(f) => f,
|
||||||
|
Err(_) => "".to_string(),
|
||||||
|
};
|
||||||
|
let shell_name = shell.split("/").last().unwrap();
|
||||||
|
|
||||||
|
let mut name: Option<String> = None;
|
||||||
|
|
||||||
|
let mut self_hosted = false;
|
||||||
|
|
||||||
|
// Get to the shell PID.
|
||||||
|
'find_shell: loop {
|
||||||
|
let ppid = pid_to_ppid(pid);
|
||||||
|
|
||||||
|
if ppid.is_none() { // We ran out of parents.
|
||||||
|
return None;
|
||||||
|
} else if ppid.unwrap().as_u32() == 1 { // We have reached the daemon.
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
pid = ppid.unwrap(); // It should be safe to unwrap the PPID now.
|
||||||
|
|
||||||
|
let pid_name = pid_to_name(pid);
|
||||||
|
|
||||||
|
if pid_name.is_none() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let name_unwrapped = pid_name.unwrap();
|
||||||
|
|
||||||
|
// Detect self-hosted shells.
|
||||||
|
for shell in SELF_HOSTED_SHELLS {
|
||||||
|
if name_unwrapped == shell {
|
||||||
|
self_hosted = true;
|
||||||
|
name = Some(name_unwrapped);
|
||||||
|
break 'find_shell;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if name_unwrapped == shell.as_str() || name_unwrapped == shell_name {
|
||||||
|
name = Some(name_unwrapped);
|
||||||
|
break; // We found the shell.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self_hosted {
|
||||||
|
// Try to get parent once more.
|
||||||
|
match pid_to_ppid(pid) {
|
||||||
|
None => return None,
|
||||||
|
Some(f) => {
|
||||||
|
// Try to get name.
|
||||||
|
name = pid_to_name(f);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return match name {
|
||||||
|
Some(f) => {
|
||||||
|
// Remove the file extension.
|
||||||
|
let mut res = f.split(".").nth(0).unwrap().to_string();
|
||||||
|
|
||||||
|
// Try to get a "prettier name".
|
||||||
|
if PRETTY_NAMES.contains_key(res.as_str()) {
|
||||||
|
res = PRETTY_NAMES.get(res.as_str()).unwrap().to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(res)
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pid_to_name(pid: Pid) -> Option<String> {
|
||||||
|
let mut system = System::new();
|
||||||
|
system.refresh_processes();
|
||||||
|
|
||||||
|
for process in system.processes() {
|
||||||
|
if *process.0 == pid {
|
||||||
|
return Some(process.1.name().to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pid_to_ppid(pid: Pid) -> Option<Pid> {
|
||||||
|
let mut system = System::new();
|
||||||
|
system.refresh_processes();
|
||||||
|
|
||||||
|
for process in system.processes() {
|
||||||
|
// Check if the process in question is ours.
|
||||||
|
if *process.0 != pid {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it has a parent.
|
||||||
|
if process.1.parent().is_none() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the parent PID.
|
||||||
|
return Some(process.1.parent().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user