First upload
This commit is contained in:
55
.gitignore
vendored
Normal file
55
.gitignore
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
# Prerequisites
|
||||
*.d
|
||||
|
||||
# Object files
|
||||
*.o
|
||||
*.ko
|
||||
*.obj
|
||||
*.elf
|
||||
|
||||
# Linker output
|
||||
*.ilk
|
||||
*.map
|
||||
*.exp
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
|
||||
# Libraries
|
||||
*.lib
|
||||
*.a
|
||||
*.la
|
||||
*.lo
|
||||
|
||||
# Shared objects (inc. Windows DLLs)
|
||||
*.dll
|
||||
*.so
|
||||
*.so.*
|
||||
*.dylib
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
*.i*86
|
||||
*.x86_64
|
||||
*.hex
|
||||
|
||||
# Debug files
|
||||
*.dSYM/
|
||||
*.su
|
||||
*.idb
|
||||
*.pdb
|
||||
|
||||
# Kernel Module Compile Results
|
||||
*.mod*
|
||||
*.cmd
|
||||
.tmp_versions/
|
||||
modules.order
|
||||
Module.symvers
|
||||
Mkfile.old
|
||||
dkms.conf
|
||||
|
||||
# debug information files
|
||||
*.dwo
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Marco Cetica <email@marcocetica.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
11
Makefile
Normal file
11
Makefile
Normal file
@@ -0,0 +1,11 @@
|
||||
TARGET = zfetch
|
||||
CC = clang
|
||||
CFLAGS = -Wall -Wextra -Werror -pedantic-errors -Wwrite-strings -std=c99 -O3
|
||||
|
||||
all: clean $(TARGET)
|
||||
|
||||
$(TARGET): zfetch.c
|
||||
$(CC) $(CFLAGS) $^ -o $@
|
||||
|
||||
clean:
|
||||
rm -f *.o *.a $(TARGET)
|
||||
2
README.md
Normal file
2
README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# zFetch
|
||||
Minimalistic system fetcher for Linux platform written in C99.
|
||||
985
zfetch.c
Normal file
985
zfetch.c
Normal file
@@ -0,0 +1,985 @@
|
||||
/*
|
||||
* zFetch - minimalistic system fetcher for Linux platform
|
||||
*
|
||||
* Copyright (c) 2026 Marco Cetica <email@marcocetica.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#define _DEFAULT_SOURCE
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <inttypes.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/utsname.h>
|
||||
#include <sys/statvfs.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <net/if.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <stdbool.h>
|
||||
#include <getopt.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
|
||||
#define Z_PATH_MAX 4096
|
||||
#define IFACE_ENTRY_LEN 64
|
||||
#define MAX_LOCAL_IPS 8
|
||||
#define LINE_VALUE_LEN 512
|
||||
|
||||
// Colors
|
||||
#define RESET "\033[0m"
|
||||
#define BOLD "\033[1m"
|
||||
#define C1 "\033[31m" // Red
|
||||
#define C2 "\033[32m" // Green
|
||||
#define C3 "\033[33m" // Yellow
|
||||
#define C4 "\033[34m" // Blue
|
||||
#define C5 "\033[35m" // Magenta
|
||||
#define C6 "\033[36m" // Cyan
|
||||
#define C7 "\033[37m" // White
|
||||
// Normal bars
|
||||
#define BAR1 "\033[40m " RESET
|
||||
#define BAR2 "\033[41m " RESET
|
||||
#define BAR3 "\033[42m " RESET
|
||||
#define BAR4 "\033[43m " RESET
|
||||
#define BAR5 "\033[44m " RESET
|
||||
#define BAR6 "\033[45m " RESET
|
||||
#define BAR7 "\033[46m " RESET
|
||||
#define BAR8 "\033[47m " RESET
|
||||
#define BAR9 "\033[100m " RESET
|
||||
// Bright bars
|
||||
#define BAR10 "\033[101m " RESET
|
||||
#define BAR11 "\033[102m " RESET
|
||||
#define BAR12 "\033[103m " RESET
|
||||
#define BAR13 "\033[104m " RESET
|
||||
#define BAR14 "\033[105m " RESET
|
||||
#define BAR15 "\033[106m " RESET
|
||||
#define BAR16 "\033[107m " RESET
|
||||
// Bitmasks to enable/disable options
|
||||
#define OPT_HOST 1U << 0
|
||||
#define OPT_OS 1U << 1
|
||||
#define OPT_UPTIME 1U << 2
|
||||
#define OPT_CPU 1U << 3
|
||||
#define OPT_MEMORY 1U << 4
|
||||
#define OPT_DISK 1U << 5
|
||||
#define OPT_IP 1U << 6
|
||||
#define OPT_LOGO 1U << 7
|
||||
#define OPT_BARS 1U << 8
|
||||
|
||||
#define ARR_LEN(X) (sizeof(X) / sizeof(X[0]))
|
||||
#define STRCMP(X, Y) (strcmp(X, Y) == 0)
|
||||
|
||||
// Operating system logos
|
||||
static const char *logo_arch[] = {
|
||||
C6 " /\\",
|
||||
C6 " / \\",
|
||||
C6 " /\\ \\",
|
||||
C4 " / \\",
|
||||
C4 " / ,, \\",
|
||||
C4 " / | | -\\",
|
||||
C4 " /_-'' ''-_\\",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char *logo_artix[] = {
|
||||
C6 " /\\",
|
||||
C6 " / \\",
|
||||
C6 " /`'.,\\",
|
||||
C6 " / ',",
|
||||
C6 " / ,`\\",
|
||||
C6 " / ,.'`. \\",
|
||||
C6 "/.,'` `'.\\",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char *logo_debian[] = {
|
||||
C1 " _____",
|
||||
C1 " / __ \\",
|
||||
C1 "| / |",
|
||||
C1 "| \\___-",
|
||||
C1 "-_",
|
||||
C1 " --_",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char *logo_fedora[] = {
|
||||
C4 " ,'''''.",
|
||||
C4 " | ,. |",
|
||||
C4 " | | '_'",
|
||||
C4 " ,....| |.. ",
|
||||
C4 ".' ,_;| ..' ",
|
||||
C4 "| | | | ",
|
||||
C4 "| ',_,' | ",
|
||||
C4 " '. ,' ",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char *logo_gentoo[] = {
|
||||
C5 " _-----_",
|
||||
C5 "( \\",
|
||||
C5 "\\ 0 \\",
|
||||
C7 " \\ )",
|
||||
C7 " / _/",
|
||||
C7 "( _-",
|
||||
C7 "\\____-",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char *logo_linux[] = {
|
||||
C3 " ___",
|
||||
C3 " (" C7 ".. " C3 "|",
|
||||
C3 " (" C5 "<> " C3 "|",
|
||||
C3 " / " C7 "__ " C3 "\\",
|
||||
C3 " ( " C7 "/ \\ " C3 "/|",
|
||||
C3 "_/\\ " C7 "__)/" C3 "_)",
|
||||
C3 "\\/ " C3 "-____\\/",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char *logo_nixos[] = {
|
||||
C4 " \\\\ \\\\ //",
|
||||
C4 " ==\\\\__\\\\/ //",
|
||||
C4 " // \\\\//",
|
||||
C4 "==// //==",
|
||||
C4 " //\\\\___//",
|
||||
C4 "// /\\\\ \\\\==",
|
||||
C4 " // \\\\ \\\\",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char *logo_slackware[] = {
|
||||
C4 " ________",
|
||||
C4 " / ______|",
|
||||
C4 " | |______",
|
||||
C4 " \\______ \\",
|
||||
C4 " ______| |",
|
||||
C4 "| |________/",
|
||||
C4 "|____________",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char *logo_alpine[] = {
|
||||
C4 " /\\ /\\",
|
||||
C4 " /" C7 "/ " C4 "\\ \\",
|
||||
C4 " /" C7 "/ " C4 "\\ \\",
|
||||
C4 "/" C7 "// " C4 "\\ \\",
|
||||
C7 "// " C4 "\\ \\",
|
||||
C4 " \\",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char *logo_ubuntu[] = {
|
||||
C3 " _",
|
||||
C3 " ---(_)",
|
||||
C3 " _/ --- \\",
|
||||
C3 "(_) | |",
|
||||
C3 " \\ --- _/",
|
||||
C3 " ---(_)",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char *logo_suse[] = {
|
||||
C2 " _______",
|
||||
C2 "__| __ \\",
|
||||
C2 " / .\\ \\",
|
||||
C2 " \\__/ |",
|
||||
C2 " _______|",
|
||||
C2 " \\_______",
|
||||
C2 "__________/",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char *logo_redhat[] = {
|
||||
C1 " .M.:MMM",
|
||||
C1 " MMMMMMMMMM.",
|
||||
C1 " ,MMMMMMMMMMM",
|
||||
C1 " .MM MMMMMMMMMMM",
|
||||
C1 "MMMM MMMMMMMMM",
|
||||
C1 "MMMMMM MM",
|
||||
C1 " MMMMMMMMM ,MMMM",
|
||||
C1 " MMMMMMMMMMMMMMMM:",
|
||||
C1 " `MMMMMMMMMMMM" ,
|
||||
NULL
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
const char **logo;
|
||||
} logo_entry_t;
|
||||
|
||||
static const logo_entry_t available_logos[] = {
|
||||
{ C4 "alpine" RESET, logo_alpine },
|
||||
{ C6 "arch" RESET, logo_arch },
|
||||
{ C1 "debian" RESET, logo_debian },
|
||||
{ C4 "fedora" RESET, logo_fedora },
|
||||
{ C5 "gentoo" RESET, logo_gentoo },
|
||||
{ C6 "artix" RESET, logo_artix },
|
||||
{ C3 "linux" RESET, logo_linux },
|
||||
{ C4 "nixos" RESET, logo_nixos },
|
||||
{ C1 "redhat" RESET, logo_redhat },
|
||||
{ C4 "slackware" RESET, logo_slackware },
|
||||
{ C2 "suse" RESET, logo_suse },
|
||||
{ C3 "ubuntu" RESET, logo_ubuntu }
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
const char *label;
|
||||
char value[LINE_VALUE_LEN];
|
||||
} line_t;
|
||||
|
||||
typedef struct {
|
||||
char id[64];
|
||||
char pretty_name[128];
|
||||
} os_t;
|
||||
|
||||
/*
|
||||
* Helpers
|
||||
*/
|
||||
|
||||
// Helper function to trim leading and trailing whitespace
|
||||
static void trim_whitespace(char *str) {
|
||||
char *start = str;
|
||||
char *end = NULL;
|
||||
size_t len = 0;
|
||||
|
||||
while (*start && isspace((unsigned char)*start)) {
|
||||
start++;
|
||||
}
|
||||
|
||||
len = strlen(start);
|
||||
if (start != str) {
|
||||
memmove(str, start, len + 1);
|
||||
}
|
||||
|
||||
if (*str == '\0') { return; }
|
||||
|
||||
end = str + strlen(str) - 1;
|
||||
|
||||
while (end >= str && isspace((unsigned char)*end)) {
|
||||
*end-- = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to remove leading and trailing quotes
|
||||
static void strip_quotes(char *str) {
|
||||
const size_t len = strlen(str);
|
||||
|
||||
if (len >= 2 && ((str[0] == '"' && str[len - 1] == '"') ||
|
||||
(str[0] == '\'' && str[len - 1] == '\''))) {
|
||||
memmove(str, str + 1, len - 2);
|
||||
|
||||
str[len - 2] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
static uint16_t default_opts(void) {
|
||||
return OPT_HOST | OPT_OS | OPT_UPTIME | OPT_CPU |
|
||||
OPT_MEMORY | OPT_DISK | OPT_IP | OPT_LOGO | OPT_BARS;
|
||||
}
|
||||
|
||||
static void set_option(uint16_t *options, const char *opt, int value) {
|
||||
uint16_t flag = 0;
|
||||
|
||||
if (STRCMP(opt, "HOST")) { flag = OPT_HOST; }
|
||||
else if (STRCMP(opt, "OS")) { flag = OPT_OS; }
|
||||
else if (STRCMP(opt, "UPTIME")) { flag = OPT_UPTIME; }
|
||||
else if (STRCMP(opt, "CPU")) { flag = OPT_CPU; }
|
||||
else if (STRCMP(opt, "MEMORY")) { flag = OPT_MEMORY; }
|
||||
else if (STRCMP(opt, "DISK")) { flag = OPT_DISK; }
|
||||
else if (STRCMP(opt, "IP")) { flag = OPT_IP; }
|
||||
else if (STRCMP(opt, "LOGO")) { flag = OPT_LOGO; }
|
||||
else if (STRCMP(opt, "BARS")) { flag = OPT_BARS; }
|
||||
|
||||
if (flag == 0) { return; }
|
||||
|
||||
if (value) {
|
||||
*options |= flag;
|
||||
} else {
|
||||
*options &= ~flag;
|
||||
}
|
||||
}
|
||||
|
||||
static void parse_config_file(uint16_t *options, const char *path) {
|
||||
FILE *fp;
|
||||
char line[256];
|
||||
|
||||
fp = fopen(path, "r");
|
||||
if (fp == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (fgets(line, sizeof(line), fp) != NULL) {
|
||||
char *eq, *comment, *end;
|
||||
|
||||
// Search comment lines. If found, truncate them
|
||||
comment = strchr(line, '#');
|
||||
if (comment != NULL) {
|
||||
*comment = '\0';
|
||||
}
|
||||
|
||||
// Skip empty lines or lines with spaces
|
||||
// This also skips truncated lines from the previous step
|
||||
trim_whitespace(line);
|
||||
if (*line == '\0') { continue; }
|
||||
|
||||
// Look for an equal sign. If not found, skip it
|
||||
// Otherwise, truncate at the equal sign
|
||||
eq = strchr(line, '=');
|
||||
if (eq == NULL) {
|
||||
continue;
|
||||
} else {
|
||||
*eq = '\0';
|
||||
}
|
||||
|
||||
// The left side becomes the key while the right side becomes the value
|
||||
// For example:
|
||||
// "CPU = 1" => "CPU_\0_1"
|
||||
// key = trim("CPU_"), value = trim("_1")
|
||||
trim_whitespace(line); // key
|
||||
trim_whitespace(eq + 1); // value
|
||||
|
||||
// Convert value to integer
|
||||
errno = 0;
|
||||
const long parsed = strtol(eq + 1, &end, 10);
|
||||
|
||||
if (errno != 0 || end == eq + 1 || (*end != '\0' && !isspace((unsigned char)*end))) {
|
||||
continue; // Invalid values are skipped
|
||||
}
|
||||
|
||||
if ((parsed == 0 || parsed == 1) && *line != '\0') {
|
||||
set_option(options, line, (int)parsed);
|
||||
}
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
static uint16_t load_config(const char *config_path) {
|
||||
char path[Z_PATH_MAX];
|
||||
const char *home = getenv("HOME");
|
||||
uint16_t options = default_opts();
|
||||
|
||||
if (config_path != NULL) {
|
||||
parse_config_file(&options, config_path);
|
||||
return options;
|
||||
}
|
||||
|
||||
if (home == NULL || *home == '\0') {
|
||||
return options;
|
||||
}
|
||||
|
||||
snprintf(path, sizeof(path), "%s/.zfetch.conf", home);
|
||||
parse_config_file(&options, path);
|
||||
|
||||
// ~/.config takes precedence over $HOME
|
||||
snprintf(path, sizeof(path), "%s/.config/zfetch/config", home);
|
||||
parse_config_file(&options, path);
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
static void read_os_release(os_t *os_rel) {
|
||||
FILE *fp = fopen("/etc/os-release", "r");
|
||||
char line[256];
|
||||
|
||||
snprintf(os_rel->id, sizeof(os_rel->id), "%s", "linux");
|
||||
snprintf(os_rel->pretty_name, sizeof(os_rel->pretty_name), "%s", "linux");
|
||||
|
||||
if (fp == NULL) { return; }
|
||||
|
||||
// The parsing process is the same of the config file parser
|
||||
while (fgets(line, sizeof(line), fp) != NULL) {
|
||||
char *eq = strchr(line, '=');
|
||||
|
||||
if (eq == NULL) { continue; }
|
||||
|
||||
*eq = '\0';
|
||||
|
||||
trim_whitespace(line); // key
|
||||
trim_whitespace(eq + 1); // value
|
||||
strip_quotes(eq + 1);
|
||||
|
||||
if (STRCMP(line, "ID")) {
|
||||
snprintf(os_rel->id, sizeof(os_rel->id), "%s", eq + 1);
|
||||
} else if (STRCMP(line, "PRETTY_NAME")) {
|
||||
snprintf(os_rel->pretty_name, sizeof(os_rel->pretty_name), "%s", eq + 1);
|
||||
}
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
static const char **get_logo(const char *id) {
|
||||
if (id == NULL) { return logo_linux; }
|
||||
else if (STRCMP(id, "arch")) { return logo_arch; }
|
||||
else if (STRCMP(id, "artix")) { return logo_artix; }
|
||||
else if (STRCMP(id, "debian")) { return logo_debian; }
|
||||
else if (STRCMP(id, "fedora")) { return logo_fedora; }
|
||||
else if (STRCMP(id, "gentoo")) { return logo_gentoo; }
|
||||
else if (STRCMP(id, "nixos")) { return logo_nixos; }
|
||||
else if (STRCMP(id, "slackware")) { return logo_slackware; }
|
||||
else if (STRCMP(id, "alpine")) { return logo_alpine; }
|
||||
else if (STRCMP(id, "ubuntu")) { return logo_ubuntu; }
|
||||
else if (STRCMP(id, "opensuse") || STRCMP(id, "opensuse-tumbleweed") ||
|
||||
STRCMP(id, "opensuse-leap") || STRCMP(id, "sles") ||
|
||||
STRCMP(id, "suse")) { return logo_suse; }
|
||||
else if (STRCMP(id, "rhel") || STRCMP(id, "redhat") ||
|
||||
STRCMP(id, "centos") || STRCMP(id, "rocky") ||
|
||||
STRCMP(id, "almalinux")) { return logo_redhat; }
|
||||
|
||||
return logo_linux;
|
||||
}
|
||||
|
||||
static const char *get_percentage_color(double percent) {
|
||||
if (percent >= 85.0) { return C1; } // Red
|
||||
else if (percent >= 60.0) { return C3; } // Yellow/orange
|
||||
|
||||
return C2; // Green
|
||||
}
|
||||
|
||||
static const char *get_logo_accent(const char **logo) {
|
||||
if (logo == logo_arch || logo == logo_artix) {
|
||||
return C6; // Cyan
|
||||
} else if (logo == logo_debian || logo == logo_redhat) {
|
||||
return C1; // Red
|
||||
} else if (logo == logo_fedora || logo == logo_nixos ||
|
||||
logo == logo_slackware || logo == logo_alpine) {
|
||||
return C4; // Blue
|
||||
} else if (logo == logo_gentoo) {
|
||||
return C5; // Magenta
|
||||
} else if (logo == logo_linux || logo == logo_ubuntu) {
|
||||
return C3; // Yellow
|
||||
} else if (logo == logo_suse) {
|
||||
return C2; // Green
|
||||
}
|
||||
|
||||
return C3; // Yellow for generic Tux logo
|
||||
}
|
||||
|
||||
// Measure the printable width
|
||||
static size_t get_printable_length(const char *str) {
|
||||
size_t len = 0;
|
||||
|
||||
// Count while ignoring color escape sequences
|
||||
while (*str != '\0') {
|
||||
if (*str == '\033' && *(str + 1) == '[') {
|
||||
str += 2;
|
||||
while (*str != '\0' && *str != 'm') { str++; }
|
||||
if (*str == 'm') { str++; }
|
||||
continue;
|
||||
}
|
||||
|
||||
len++;
|
||||
str++;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
// Find the widest printable row in order to align the info column nicely
|
||||
static size_t get_logo_max_width(const char **logo) {
|
||||
size_t max = 0;
|
||||
size_t idx = 0;
|
||||
|
||||
while (logo[idx] != NULL) {
|
||||
size_t width = get_printable_length(logo[idx]);
|
||||
if (width > max) {
|
||||
max = width;
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
|
||||
return max;
|
||||
}
|
||||
|
||||
static void fmt_size(uint64_t used_kib, uint64_t total_kib, char *buf, size_t buf_size) {
|
||||
const char *unit = "MB";
|
||||
double divisor = 1024.0;
|
||||
double used;
|
||||
double total;
|
||||
double percent = 0.0;
|
||||
|
||||
if (total_kib >= 1024ULL * 1024ULL) {
|
||||
unit = "GB";
|
||||
divisor = 1024.0 * 1024.0;
|
||||
}
|
||||
|
||||
used = (double)used_kib / divisor;
|
||||
total = (double)total_kib / divisor;
|
||||
|
||||
if (total_kib != 0) {
|
||||
percent = ((double)used_kib / (double)total_kib) * 100.0;
|
||||
}
|
||||
|
||||
snprintf(buf, buf_size, "%.1f %s / %.1f %s (%s%.0f%%%s)",
|
||||
used, unit, total, unit, get_percentage_color(percent), percent, RESET);
|
||||
}
|
||||
|
||||
// Retrieve the first line of a file
|
||||
static bool get_head(const char *path, const char *prefix, char *buf, size_t buf_size) {
|
||||
FILE *fp = fopen(path, "r");
|
||||
char line[512];
|
||||
|
||||
if (fp == NULL) { return false; }
|
||||
|
||||
while (fgets(line, sizeof(line), fp) != NULL) {
|
||||
if (strncmp(line, prefix, strlen(prefix)) == 0) {
|
||||
snprintf(buf, buf_size, "%s", line + strlen(prefix));
|
||||
trim_whitespace(buf);
|
||||
fclose(fp);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline void str_to_lower(char *str) {
|
||||
if (str == NULL) { return; }
|
||||
|
||||
while (*str != '\0') {
|
||||
*str = (char)tolower((unsigned char)*str);
|
||||
str++;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Features
|
||||
*/
|
||||
static void get_os(char *buf, size_t buf_size) {
|
||||
os_t os_release;
|
||||
|
||||
read_os_release(&os_release);
|
||||
snprintf(buf, buf_size, "%s", os_release.pretty_name);
|
||||
}
|
||||
|
||||
static void get_hostname(char *buf, size_t buf_size) {
|
||||
char hostname[256];
|
||||
|
||||
if (gethostname(hostname, sizeof(hostname)) == 0) {
|
||||
hostname[sizeof(hostname) - 1] = '\0';
|
||||
snprintf(buf, buf_size, "%s", hostname);
|
||||
} else {
|
||||
snprintf(buf, buf_size, "unknown");
|
||||
}
|
||||
}
|
||||
|
||||
static void get_uptime(char *buf, size_t buf_size) {
|
||||
FILE *fp = fopen("/proc/uptime", "r");
|
||||
uint64_t total = 0;
|
||||
uint64_t days, hours, minutes;
|
||||
|
||||
if (fp == NULL) {
|
||||
snprintf(buf, buf_size, "unknown");
|
||||
return;
|
||||
}
|
||||
|
||||
// The /proc/uptime has two fields formatted as follows:
|
||||
// <uptime_in_sec>.<fraction> <idle_in_sec>.<fraction>
|
||||
// We only read the seconds and ignore the fractional part
|
||||
if (fscanf(fp, "%" SCNu64, &total) != 1) {
|
||||
fclose(fp);
|
||||
snprintf(buf, buf_size, "unknown");
|
||||
return;
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
|
||||
days = total / 86400ULL;
|
||||
hours = (total % 86400ULL) / 3600ULL;
|
||||
minutes = (total % 3600ULL) / 60ULL;
|
||||
|
||||
if (days > 0) {
|
||||
snprintf(buf, buf_size, "%" PRIu64 " day%s, %" PRIu64 " hour%s, %" PRIu64 " min",
|
||||
days, days == 1 ? "" : "s",
|
||||
hours, hours == 1 ? "" : "s",
|
||||
minutes);
|
||||
} else if (hours > 0) {
|
||||
snprintf(buf, buf_size, "%" PRIu64 " hour%s, %" PRIu64 " min",
|
||||
hours, hours == 1 ? "" : "s",
|
||||
minutes);
|
||||
} else {
|
||||
snprintf(buf, buf_size, "%" PRIu64 " min", minutes);
|
||||
}
|
||||
}
|
||||
|
||||
static void get_cpu(char *buf, size_t buf_size) {
|
||||
if (!get_head("/proc/cpuinfo", "model name\t: ", buf, buf_size) &&
|
||||
!get_head("/proc/cpuinfo", "Hardware\t: ", buf, buf_size)) {
|
||||
struct utsname uts;
|
||||
|
||||
if (uname(&uts) == 0) {
|
||||
snprintf(buf, buf_size, "%s", uts.machine);
|
||||
} else {
|
||||
snprintf(buf, buf_size, "unknown");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void get_ram(char *buf, size_t buf_size) {
|
||||
FILE *fp = fopen("/proc/meminfo", "r");
|
||||
char key[64];
|
||||
unsigned long long value;
|
||||
unsigned long long total = 0, available = 0;
|
||||
bool mem_available = false;
|
||||
|
||||
if (fp == NULL) {
|
||||
snprintf(buf, buf_size, "unknown");
|
||||
return;
|
||||
}
|
||||
|
||||
while (fscanf(fp, "%63s", key) == 1) {
|
||||
if (STRCMP(key, "MemTotal:") || STRCMP(key, "MemAvailable:")) {
|
||||
if (fscanf(fp, " %llu", &value) != 1) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (STRCMP(key, "MemTotal:")) {
|
||||
total = value;
|
||||
} else if (STRCMP(key, "MemAvailable:")) {
|
||||
available = value;
|
||||
mem_available = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Consume the rest of the line
|
||||
int ch;
|
||||
while ((ch = fgetc(fp)) != '\n' && ch != EOF) { }
|
||||
|
||||
if (total != 0 && mem_available) { break; }
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
|
||||
if (total == 0 || !mem_available) {
|
||||
snprintf(buf, buf_size, "unknown");
|
||||
return;
|
||||
}
|
||||
|
||||
fmt_size(total - available, total, buf, buf_size);
|
||||
}
|
||||
|
||||
static void get_disk(char *buf, size_t buf_size) {
|
||||
struct statvfs fs;
|
||||
uint64_t total_kib, avail_kib, used_kib;
|
||||
|
||||
if (statvfs("/", &fs) != 0) {
|
||||
snprintf(buf, buf_size, "unknown");
|
||||
return;
|
||||
}
|
||||
|
||||
total_kib = ((uint64_t)fs.f_blocks * fs.f_frsize) / 1024ULL;
|
||||
avail_kib = ((uint64_t)fs.f_bfree * fs.f_frsize) / 1024ULL;
|
||||
used_kib = total_kib - avail_kib;
|
||||
|
||||
fmt_size(used_kib, total_kib, buf, buf_size);
|
||||
}
|
||||
|
||||
static size_t get_ips(char entries[][IFACE_ENTRY_LEN], size_t max_entries) {
|
||||
size_t count = 0;
|
||||
struct ifaddrs *ifa_list = NULL;
|
||||
|
||||
if (getifaddrs(&ifa_list) != 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (struct ifaddrs *ifa = ifa_list; ifa != NULL && count < max_entries; ifa = ifa->ifa_next) {
|
||||
char ipv4[INET_ADDRSTRLEN];
|
||||
struct sockaddr_in *sin;
|
||||
|
||||
// Skip loopback, disabled interfaces and non-IPV4 interfaces
|
||||
if (!ifa->ifa_addr) { continue; }
|
||||
if (ifa->ifa_addr->sa_family != AF_INET) { continue; }
|
||||
if (!(ifa->ifa_flags & IFF_UP)) { continue; }
|
||||
if (!(ifa->ifa_flags & IFF_RUNNING)) { continue; }
|
||||
if (ifa->ifa_flags & IFF_LOOPBACK) { continue; }
|
||||
|
||||
sin = (struct sockaddr_in *)ifa->ifa_addr;
|
||||
if (!inet_ntop(AF_INET, &sin->sin_addr, ipv4, sizeof(ipv4))) { continue; }
|
||||
|
||||
snprintf(entries[count], IFACE_ENTRY_LEN, "%s (%s)", ipv4, ifa->ifa_name);
|
||||
count++;
|
||||
}
|
||||
|
||||
freeifaddrs(ifa_list);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static void get_available_logos(void) {
|
||||
const size_t logo_count = ARR_LEN(available_logos);
|
||||
|
||||
if (logo_count == 0) {
|
||||
puts("Available logos: none.");
|
||||
return;
|
||||
}
|
||||
|
||||
printf("Available logos: %s", available_logos[0].name);
|
||||
for (size_t idx = 1; idx < logo_count; idx++) {
|
||||
printf(", %s", available_logos[idx].name);
|
||||
}
|
||||
puts(".");
|
||||
}
|
||||
|
||||
static void get_options(uint16_t options, const char *config_path) {
|
||||
// Create a temporary mapping type
|
||||
struct {
|
||||
const char *name;
|
||||
uint16_t flag;
|
||||
} flags[] = {
|
||||
{ "HOST", OPT_HOST },
|
||||
{ "OS", OPT_OS },
|
||||
{ "UPTIME", OPT_UPTIME },
|
||||
{ "CPU", OPT_CPU },
|
||||
{ "MEMORY", OPT_MEMORY },
|
||||
{ "DISK", OPT_DISK },
|
||||
{ "IP", OPT_IP },
|
||||
{ "LOGO", OPT_LOGO },
|
||||
{ "BARS", OPT_BARS }
|
||||
};
|
||||
|
||||
if (config_path) {
|
||||
printf(C3 "Using custom config file: '%s'\n" RESET, config_path);
|
||||
}
|
||||
|
||||
for (size_t idx = 0; idx < ARR_LEN(flags); idx++) {
|
||||
const bool enabled = (options & flags[idx].flag) != 0;
|
||||
|
||||
printf("%-11s %s%s%s\n", flags[idx].name,
|
||||
enabled ? C2 : C1,
|
||||
enabled ? "enabled" : "disabled",
|
||||
RESET);
|
||||
}
|
||||
}
|
||||
|
||||
static void system_fetcher(const char **logo, const line_t *lines, size_t line_count, bool show_logo, bool show_bars) {
|
||||
const size_t gap = 3;
|
||||
const char *accent = get_logo_accent(logo);
|
||||
size_t logo_lines = 0, logo_width = 0, total_lines;
|
||||
|
||||
if (show_logo) {
|
||||
while (logo[logo_lines] != NULL) {
|
||||
logo_lines++;
|
||||
}
|
||||
|
||||
logo_width = get_logo_max_width(logo);
|
||||
}
|
||||
|
||||
total_lines = line_count;
|
||||
if (show_logo && logo_lines > total_lines) {
|
||||
total_lines = logo_lines;
|
||||
}
|
||||
|
||||
if (show_bars && line_count + 2 > total_lines) {
|
||||
total_lines = line_count + 2;
|
||||
}
|
||||
|
||||
for (size_t idx = 0; idx < total_lines; idx++) {
|
||||
if (show_logo && idx < logo_lines) {
|
||||
printf("%s", logo[idx]);
|
||||
}
|
||||
|
||||
if (idx < line_count) {
|
||||
if (show_logo && idx < logo_lines) {
|
||||
const size_t logo_len = get_printable_length(logo[idx]);
|
||||
const size_t padding = (logo_width > logo_len ? logo_width - logo_len : 0) + gap;
|
||||
printf("%*s", (int)padding, "");
|
||||
} else if (show_logo) {
|
||||
printf("%*s", (int)(logo_width + gap), "");
|
||||
}
|
||||
printf("%s" BOLD "%-10s" RESET " %s", accent, lines[idx].label, lines[idx].value);
|
||||
} else if (show_bars && idx == line_count) {
|
||||
if (show_logo) {
|
||||
if (idx < logo_lines) {
|
||||
const size_t logo_len = get_printable_length(logo[idx]);
|
||||
const size_t padding = (logo_width > logo_len ? logo_width - logo_len : 0) + gap;
|
||||
printf("%*s", (int)padding, "");
|
||||
} else {
|
||||
printf("%*s", (int)(logo_width + gap), "");
|
||||
}
|
||||
}
|
||||
|
||||
printf("%s%s%s%s%s%s%s%s", BAR1, BAR2, BAR3, BAR4,
|
||||
BAR5, BAR6, BAR7, BAR8);
|
||||
} else if (show_bars && idx == line_count + 1) {
|
||||
if (show_logo) {
|
||||
if (idx < logo_lines) {
|
||||
const size_t logo_len = get_printable_length(logo[idx]);
|
||||
const size_t padding = (logo_width > logo_len ? logo_width - logo_len : 0) + gap;
|
||||
printf("%*s", (int)padding, "");
|
||||
} else {
|
||||
printf("%*s", (int)(logo_width + gap), "");
|
||||
}
|
||||
}
|
||||
|
||||
printf("%s%s%s%s%s%s%s%s", BAR9, BAR10, BAR11, BAR12,
|
||||
BAR13, BAR14, BAR15, BAR16);
|
||||
}
|
||||
|
||||
putchar('\n');
|
||||
}
|
||||
}
|
||||
|
||||
void print_usage(const char *name) {
|
||||
printf("%s - minimalistic system fetcher for Linux platform.\n"
|
||||
"Available options:\n"
|
||||
"-l, --logo | Manually specify the logo\n"
|
||||
"-c, --config | Specify a configuration file\n"
|
||||
"-a, --list-logos | List available logos\n"
|
||||
"-s, --list-opts | Show which options are enable and which are not\n"
|
||||
"-h, --help | Show this helper\n\n"
|
||||
"Project homepage: https://github.com/ceticamarco/zfetch\n"
|
||||
" https://git.marcocetica.com/marco/zfetch\n", name);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const struct option long_opts[] = {
|
||||
{ "logo", required_argument, NULL, 'l' },
|
||||
{ "config", required_argument, NULL, 'c' },
|
||||
{ "list-logos", no_argument, NULL, 'a' },
|
||||
{ "list-opts", no_argument, NULL, 's' },
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ NULL, 0, NULL, 0 }
|
||||
};
|
||||
uint16_t options = 0;
|
||||
os_t os_release;
|
||||
line_t lines[64];
|
||||
size_t line_count = 0;
|
||||
bool list_logos = false, list_opts = false;
|
||||
int opt;
|
||||
const char *short_opts = "c:l:ash";
|
||||
const char **logo;
|
||||
const char *config_path = NULL, *logo_name = NULL;
|
||||
char logo_name_norm[64];
|
||||
|
||||
opterr = 0; optind = 1;
|
||||
while ((opt = getopt_long(argc, argv, short_opts, long_opts, NULL)) != -1) {
|
||||
switch (opt) {
|
||||
case 'l':
|
||||
logo_name = optarg;
|
||||
break;
|
||||
case 'c':
|
||||
config_path = optarg;
|
||||
break;
|
||||
case 'a':
|
||||
list_logos = true;
|
||||
break;
|
||||
case 's':
|
||||
list_opts = true;
|
||||
break;
|
||||
case 'h':
|
||||
print_usage(*(argv + 0));
|
||||
return 0;
|
||||
default:
|
||||
print_usage(*(argv + 0));
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (optind < argc) {
|
||||
print_usage(*(argv + 0));
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (list_logos) {
|
||||
get_available_logos();
|
||||
return 0;
|
||||
}
|
||||
|
||||
options = load_config(config_path);
|
||||
|
||||
if (list_opts) {
|
||||
get_options(options, config_path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
read_os_release(&os_release);
|
||||
|
||||
if (logo_name != NULL) {
|
||||
snprintf(logo_name_norm, sizeof(logo_name_norm), "%s", logo_name);
|
||||
str_to_lower(logo_name_norm);
|
||||
logo = get_logo(logo_name_norm);
|
||||
} else {
|
||||
logo = get_logo(os_release.id);
|
||||
}
|
||||
|
||||
if (options & OPT_HOST) {
|
||||
lines[line_count].label = "Host:";
|
||||
get_hostname(lines[line_count].value, sizeof(lines[line_count].value));
|
||||
line_count++;
|
||||
}
|
||||
|
||||
if (options & OPT_OS) {
|
||||
lines[line_count].label = "OS:";
|
||||
get_os(lines[line_count].value, sizeof(lines[line_count].value));
|
||||
line_count++;
|
||||
}
|
||||
|
||||
if (options & OPT_UPTIME) {
|
||||
lines[line_count].label = "Uptime:";
|
||||
get_uptime(lines[line_count].value, sizeof(lines[line_count].value));
|
||||
line_count++;
|
||||
}
|
||||
|
||||
if (options & OPT_CPU) {
|
||||
lines[line_count].label = "CPU:";
|
||||
get_cpu(lines[line_count].value, sizeof(lines[line_count].value));
|
||||
line_count++;
|
||||
}
|
||||
|
||||
if (options & OPT_MEMORY) {
|
||||
lines[line_count].label = "Memory:";
|
||||
get_ram(lines[line_count].value, sizeof(lines[line_count].value));
|
||||
line_count++;
|
||||
}
|
||||
|
||||
if (options & OPT_DISK) {
|
||||
lines[line_count].label = "Disk:";
|
||||
get_disk(lines[line_count].value, sizeof(lines[line_count].value));
|
||||
line_count++;
|
||||
}
|
||||
|
||||
if (options & OPT_IP) {
|
||||
char interfaces[MAX_LOCAL_IPS][IFACE_ENTRY_LEN];
|
||||
const size_t ifaces_count = get_ips(interfaces, MAX_LOCAL_IPS);
|
||||
|
||||
if (ifaces_count == 0) {
|
||||
lines[line_count].label = "Local IPs:";
|
||||
snprintf(lines[line_count].value, sizeof(lines[line_count].value), "unknown");
|
||||
line_count++;
|
||||
} else {
|
||||
for (size_t idx = 0; idx < ifaces_count && line_count < ARR_LEN(lines); idx++) {
|
||||
lines[line_count].label = idx == 0 ? "Local IPs:" : "";
|
||||
snprintf(lines[line_count].value, sizeof(lines[line_count].value), "%s%s",
|
||||
interfaces[idx], idx < ifaces_count - 1 ? "," : "");
|
||||
line_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
system_fetcher(logo, lines, line_count, (options & OPT_LOGO) != 0, (options & OPT_BARS) != 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user