From 661cc0a02fb3d38440ad8c1e295fdbde9c1299d8 Mon Sep 17 00:00:00 2001 From: stkhan Date: Wed, 21 Apr 2021 19:36:08 +0000 Subject: Added paleofetch --- paleofetch/.gitignore | 1 + paleofetch/CHANGELOG.md | 20 + paleofetch/LICENSE | 21 + paleofetch/Makefile | 17 + paleofetch/README.md | 102 ++++ paleofetch/config.h | 43 ++ paleofetch/config_scripts/battery_config.sh | 5 + paleofetch/example.png | Bin 0 -> 109097 bytes paleofetch/logos/arch.h | 22 + paleofetch/logos/arch2.h | 21 + paleofetch/logos/arch3.h | 22 + paleofetch/paleofetch.c | 756 ++++++++++++++++++++++++++++ paleofetch/paleofetch.h | 27 + 13 files changed, 1057 insertions(+) create mode 100644 paleofetch/.gitignore create mode 100644 paleofetch/CHANGELOG.md create mode 100644 paleofetch/LICENSE create mode 100644 paleofetch/Makefile create mode 100644 paleofetch/README.md create mode 100644 paleofetch/config.h create mode 100755 paleofetch/config_scripts/battery_config.sh create mode 100644 paleofetch/example.png create mode 100644 paleofetch/logos/arch.h create mode 100644 paleofetch/logos/arch2.h create mode 100644 paleofetch/logos/arch3.h create mode 100644 paleofetch/paleofetch.c create mode 100644 paleofetch/paleofetch.h (limited to 'paleofetch') diff --git a/paleofetch/.gitignore b/paleofetch/.gitignore new file mode 100644 index 0000000..e972a4b --- /dev/null +++ b/paleofetch/.gitignore @@ -0,0 +1 @@ +paleofetch diff --git a/paleofetch/CHANGELOG.md b/paleofetch/CHANGELOG.md new file mode 100644 index 0000000..4f66c8c --- /dev/null +++ b/paleofetch/CHANGELOG.md @@ -0,0 +1,20 @@ +v0.1.2 - 2020-04-23 +------------------- +### Added +* Increased configurability with seperate `config.h` header and `logos` directory +* More accurate OS information +* No longer prints blank values +* Add detection for cpu frequency + +### Fixed +* Made the date format on this document reasonable +* No longer crashes if Xorg isn't running +* Fixed get_uptime when uptime is less than 1 minute + +v0.1.1 - 2020-04-22 +------------------- +* Added support for in-source configuration + +v0.1.0 - 2020-04-22 +----------------- +* Minimum Viable Product- reliable output for all the default fields diff --git a/paleofetch/LICENSE b/paleofetch/LICENSE new file mode 100644 index 0000000..f2b71a0 --- /dev/null +++ b/paleofetch/LICENSE @@ -0,0 +1,21 @@ +MIT License (MIT) + +Copyright (c) 2020. Sam Barr + +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. diff --git a/paleofetch/Makefile b/paleofetch/Makefile new file mode 100644 index 0000000..b272a9e --- /dev/null +++ b/paleofetch/Makefile @@ -0,0 +1,17 @@ +CFLAGS=-O2 -Wall -Wextra -lX11 -lpci +PREFIX=/usr +CACHE=$(shell if [ "$$XDG_CACHE_HOME" ]; then echo "$$XDG_CACHE_HOME"; else echo "$$HOME"/.cache; fi) + +all: paleofetch + +clean: + rm -f paleofetch $(CACHE)/paleofetch + +paleofetch: paleofetch.c paleofetch.h config.h + $(eval battery_path := $(shell ./config_scripts/battery_config.sh)) + $(CC) paleofetch.c -o paleofetch $(CFLAGS) -D $(battery_path) + strip paleofetch + +install: paleofetch + mkdir -p $(PREFIX)/bin + install ./paleofetch $(PREFIX)/bin/paleofetch diff --git a/paleofetch/README.md b/paleofetch/README.md new file mode 100644 index 0000000..c966979 --- /dev/null +++ b/paleofetch/README.md @@ -0,0 +1,102 @@ +paleofetch +========== + +A rewrite of [neofetch](https://github.com/dylanaraps/neofetch) in C. +Currently only supports Linux and Xorg. + + +Why use paleofetch over neofetch? +----------------------------------------- +One major reason is the performance improvement. For example: neofetch finishes running after about 222 milliseconds where as paleofetch can finish running in a blazing fast 3 milliseconds. + +Note: this testing occured on only 1 computer, it's not a good representation on the performance benefit you may gain. + + +Example output: + +![example output](example.png) + +Dependencies +------------ + +Paleofetch requires `libX11` and `libpci`. If you're running Xorg you should already have +the former. On Arch Linux, you should have `libpci` already installed if you have `pciutils` +installed. On other linux distrobutions, you may need to install libpci seperatley +if its not already present. + +Compiling +--------- + + make install + +Usage +----- + +After compiling, simply run the executable: + + paleofetch + +By default, `paleofetch` will cache certain information (in `$XDG_CACHE_HOME/paleofetch`) +to speed up subsequent calls. To ignore the contents of the cache (and repopulate it), run + + paleofetch --recache + +The cache file can safely be removed at any time, paleofetch will repopulate it +if it is absent. + +Configuration +------------- + +Paleofetch is configured by editing `config.h` and recompiling. +You can change your logo by including the appropriate header file in the logos directory. +The color with which paleo fetch draws the logo can be chosen by defining the `COLOR` macro, +look up ANSI escape codes for information on customizing this. + +The last configuration is the `CONFIG` macro, which controls what information paleofetch +prints. Each entry in this macro should look like + + { "NAME: ", getter_function, false }, \ + +Take note of the trailing comma and backslash. The first piece, `"NAME: "`, sets +what paleofetch prints before printing the information; this usually tells you what +bit of information is being shown. Note that the name entry should be unique for entries +which are to be cached. The second piece, `getter_function`, sets +which function paleofetch will call display. Current available getter functions are + +* `get_title`: prints `host@user` like in a bash prompt. Host and user will be printed in color. +* `get_bar`: Meant to be added after `get_title`, underlines the title +* `get_os`: Prints your operating system (including distrobution) +* `get_host`: Prints the model of computer +* `get_kernel`: Prints the version of the linux kernel +* `get_uptime`: Shows how long linux has been running +* `get_packages`: Shows how many packages you have installed. Currently only works for pacman. +* `get_shell`: Shows which shell you are using +* `get_resolution`: Prints your screen resolution +* `get_terminal`: Prints the name of your current terminal +* `get_cpu`: Prints the name of your CPU, number of cores, and maximum frequency +* `get_gpu1`, `get_gpu2`: Print the GPU on your system. If you don't have both integrated graphics and an external GPU, `get_gpu2` will likely be blank +* `get_gpu`: (Tries to) print your current GPU +* `get_colors1`, `get_colors2`: Prints the colors of your terminal + +To include a blank line between entries, put `SPACER \` between the two lines +you want to separate. + +The booleans in `CONFIG` tell paleofetch whether you want to cache an entry. +When cached, paleofetch will save the value and not recompute it whenever you run paleofetch +(unless you specify the `--recache` option). + +The CPU and GPU name can be configured as well. This is done under the CPU_CONFIG and GPU_CONFIG section +in the config.h file. Two macros are provided to customize and tidy up the model names: + +* `REMOVE(string)`: removes the first occurence of `string` +* `REPLACE(string1, string2)`: replaces the first occurence of `string1` with `string2` + +Don't forget to run paleofetch with the --recache flag after compiling it with your new +configuration, otherwise it will still show the old name for already cached entries. + +FAQ +--- + +Q: Do you really run neofetch every time you open a terminal? +A: Yes, I like the way it looks and like that it causes my prompt to start midway +down the screen. I do acknowledge that the information it presents is not actually useful. diff --git a/paleofetch/config.h b/paleofetch/config.h new file mode 100644 index 0000000..c788258 --- /dev/null +++ b/paleofetch/config.h @@ -0,0 +1,43 @@ +#include "logos/arch.h" +#define COLOR "\e[1;36m" + +#define CONFIG \ +{ \ + /* name function cached */\ + { "", get_title, false }, \ + { "", get_bar, false }, \ + { "OS: ", get_os, true }, \ + { "Host: ", get_host, true }, \ + { "Kernel: ", get_kernel, false }, \ + { "Uptime: ", get_uptime, false }, \ + SPACER \ + { "Packages: ", get_packages_pacman, false }, \ + { "Shell: ", get_shell, false }, \ + { "Resolution: ", get_resolution, false }, \ + { "Terminal: ", get_terminal, false }, \ + SPACER \ + { "CPU: ", get_cpu, true }, \ + { "GPU: ", get_gpu1, true }, \ + { "Memory: ", get_memory, false }, \ + SPACER \ + { "", get_colors1, false }, \ + { "", get_colors2, false }, \ +} + +#define CPU_CONFIG \ +{ \ + REMOVE("(R)"), \ + REMOVE("(TM)"), \ + REMOVE("Dual-Core"), \ + REMOVE("Quad-Core"), \ + REMOVE("Six-Core"), \ + REMOVE("Eight-Core"), \ + REMOVE("Core"), \ + REMOVE("CPU"), \ +} + +#define GPU_CONFIG \ +{ \ + REMOVE("Corporation"), \ +} + diff --git a/paleofetch/config_scripts/battery_config.sh b/paleofetch/config_scripts/battery_config.sh new file mode 100755 index 0000000..0a49740 --- /dev/null +++ b/paleofetch/config_scripts/battery_config.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +BATTERY_DIRECTORY=`ls /sys/class/power_supply | grep -i "^bat" | head -n 1` + +echo "BATTERY_DIRECTORY='\"/sys/class/power_supply/$BATTERY_DIRECTORY\"'" diff --git a/paleofetch/example.png b/paleofetch/example.png new file mode 100644 index 0000000..b9e71c6 Binary files /dev/null and b/paleofetch/example.png differ diff --git a/paleofetch/logos/arch.h b/paleofetch/logos/arch.h new file mode 100644 index 0000000..d055ea1 --- /dev/null +++ b/paleofetch/logos/arch.h @@ -0,0 +1,22 @@ +// This was stolen from neofetch +char *LOGO[] = { + " -` ", + " .o+` ", + " `ooo/ ", + " `+oooo: ", + " `+oooooo: ", + " -+oooooo+: ", + " `/:-:++oooo+: ", + " `/++++/+++++++: ", + " `/++++++++++++++: ", + " `/+++ooooooooooooo/` ", + " ./ooosssso++osssssso+` ", + " .oossssso-````/ossssss+` ", + " -osssssso. :ssssssso. ", + " :osssssss/ osssso+++. ", + " /ossssssss/ +ssssooo/- ", + " `/ossssso+/:- -:/+osssso+- ", + " `+sso+:-` `.-/+oso: ", + "`++:. `-/+/ ", + ".` `/ " +}; diff --git a/paleofetch/logos/arch2.h b/paleofetch/logos/arch2.h new file mode 100644 index 0000000..e06cfe2 --- /dev/null +++ b/paleofetch/logos/arch2.h @@ -0,0 +1,21 @@ +// And this was stolen from archey3 +char *LOGO[] = { + " + ", + " # ", + " ### ", + " ##### ", + " ###### ", + " ; #####; ", + " +##.##### ", + " +########## ", + " #############; ", + " ###############+ ", + " ####### ####### ", + " .######; ;###;`'. ", + " .#######; ;#####. ", + " #########. .########` ", + " ######' '###### ", + " ;#### ####; ", + " ##' '## ", + "#' `# " +}; diff --git a/paleofetch/logos/arch3.h b/paleofetch/logos/arch3.h new file mode 100644 index 0000000..8175ab9 --- /dev/null +++ b/paleofetch/logos/arch3.h @@ -0,0 +1,22 @@ +// made by Reddit user LnLcFlx2 +char *LOGO[] = { +" ▄ ", +" ▟█▙ ", +" ▟███▙ ", +" ▟█████▙ ", +" ▟███████▙ ", +" ▂▔▀▜██████▙ ", +" ▟██▅▂▝▜█████▙ ", +" ▟█████████████▙ ", +" ▟███████████████▙ ", +" ▟█████████████████▙ ", +" ▟███████████████████▙ ", +" ▟█████████▛▀▀▜████████▙ ", +" ▟████████▛ ▜███████▙ ", +" ▟█████████ ████████▙ ", +" ▟██████████ █████▆▅▄▃▂ ", +" ▟██████████▛ ▜█████████▙ ", +" ▟██████▀▀▀ ▀▀██████▙ ", +" ▟███▀▘ ▝▀███▙ ", +" ▟▛▀ ▀▜▙ " +}; diff --git a/paleofetch/paleofetch.c b/paleofetch/paleofetch.c new file mode 100644 index 0000000..72ce826 --- /dev/null +++ b/paleofetch/paleofetch.c @@ -0,0 +1,756 @@ +#pragma GCC diagnostic ignored "-Wunused-function" +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include + +#include "paleofetch.h" +#include "config.h" + +#define BUF_SIZE 150 +#define COUNT(x) (int)(sizeof x / sizeof *x) + +#define halt_and_catch_fire(fmt, ...) \ + do { \ + if(status != 0) { \ + fprintf(stderr, "paleofetch: " fmt "\n", ##__VA_ARGS__); \ + exit(status); \ + } \ + } while(0) + +struct conf { + char *label, *(*function)(); + bool cached; +} config[] = CONFIG; + +struct { + char *substring; + char *repl_str; + size_t length; + size_t repl_len; +} cpu_config[] = CPU_CONFIG, gpu_config[] = GPU_CONFIG; + +Display *display; +struct statvfs file_stats; +struct utsname uname_info; +struct sysinfo my_sysinfo; +int title_length, status; + +/* + * Replaces the first newline character with null terminator + */ +void remove_newline(char *s) { + while (*s != '\0' && *s != '\n') + s++; + *s = '\0'; +} + +/* + * Replaces the first newline character with null terminator + * and returns the length of the string + */ +int remove_newline_get_length(char *s) { + int i; + for (i = 0; *s != '\0' && *s != '\n'; s++, i++); + *s = '\0'; + return i; +} + +/* + * Cleans up repeated spaces in a string + * Trim spaces at the front of a string + */ +void truncate_spaces(char *str) { + int src = 0, dst = 0; + while(*(str + dst) == ' ') dst++; + + while(*(str + dst) != '\0') { + *(str + src) = *(str + dst); + if(*(str + (dst++)) == ' ') + while(*(str + dst) == ' ') dst++; + + src++; + } + + *(str + src) = '\0'; +} + +/* + * Removes the first len characters of substring from str + * Assumes that strlen(substring) >= len + * Returns index where substring was found, or -1 if substring isn't found + */ +void remove_substring(char *str, const char* substring, size_t len) { + /* shift over the rest of the string to remove substring */ + char *sub = strstr(str, substring); + if(sub == NULL) return; + + int i = 0; + do *(sub+i) = *(sub+i+len); + while(*(sub+(++i)) != '\0'); +} + +/* + * Replaces the first sub_len characters of sub_str from str + * with the first repl_len characters of repl_str + */ +void replace_substring(char *str, const char *sub_str, const char *repl_str, size_t sub_len, size_t repl_len) { + char buffer[BUF_SIZE / 2]; + char *start = strstr(str, sub_str); + if (start == NULL) return; // substring not found + + /* check if we have enough space for new substring */ + if (strlen(str) - sub_len + repl_len >= BUF_SIZE / 2) { + status = -1; + halt_and_catch_fire("new substring too long to replace"); + } + + strcpy(buffer, start + sub_len); + strncpy(start, repl_str, repl_len); + strcpy(start + repl_len, buffer); +} + +static char *get_title() { + // reduce the maximum size for these, so that we don't over-fill the title string + char hostname[BUF_SIZE / 3]; + status = gethostname(hostname, BUF_SIZE / 3); + halt_and_catch_fire("unable to retrieve host name"); + + char username[BUF_SIZE / 3]; + status = getlogin_r(username, BUF_SIZE / 3); + halt_and_catch_fire("unable to retrieve login name"); + + title_length = strlen(hostname) + strlen(username) + 1; + + char *title = malloc(BUF_SIZE); + snprintf(title, BUF_SIZE, COLOR"%s\e[0m@"COLOR"%s", username, hostname); + + return title; +} + +static char *get_bar() { + char *bar = malloc(BUF_SIZE); + char *s = bar; + for(int i = 0; i < title_length; i++) *(s++) = '-'; + *s = '\0'; + return bar; +} + +static char *get_os() { + char *os = malloc(BUF_SIZE), + *name = malloc(BUF_SIZE), + *line = NULL; + size_t len; + FILE *os_release = fopen("/etc/os-release", "r"); + if(os_release == NULL) { + status = -1; + halt_and_catch_fire("unable to open /etc/os-release"); + } + + while (getline(&line, &len, os_release) != -1) { + if (sscanf(line, "NAME=\"%[^\"]+", name) > 0) break; + } + + free(line); + fclose(os_release); + snprintf(os, BUF_SIZE, "%s %s", name, uname_info.machine); + free(name); + + return os; +} + +static char *get_kernel() { + char *kernel = malloc(BUF_SIZE); + strncpy(kernel, uname_info.release, BUF_SIZE); + return kernel; +} + +static char *get_host() { + char *host = malloc(BUF_SIZE), buffer[BUF_SIZE/2]; + FILE *product_name, *product_version, *model; + + if((product_name = fopen("/sys/devices/virtual/dmi/id/product_name", "r")) != NULL) { + if((product_version = fopen("/sys/devices/virtual/dmi/id/product_version", "r")) != NULL) { + fread(host, 1, BUF_SIZE/2, product_name); + remove_newline(host); + strcat(host, " "); + fread(buffer, 1, BUF_SIZE/2, product_version); + remove_newline(buffer); + strcat(host, buffer); + fclose(product_version); + } else { + fclose(product_name); + goto model_fallback; + } + fclose(product_name); + return host; + } + +model_fallback: + if((model = fopen("/sys/firmware/devicetree/base/model", "r")) != NULL) { + fread(host, 1, BUF_SIZE, model); + remove_newline(host); + return host; + } + + status = -1; + halt_and_catch_fire("unable to get host"); + return NULL; +} + +static char *get_uptime() { + long seconds = my_sysinfo.uptime; + struct { char *name; int secs; } units[] = { + { "day", 60 * 60 * 24 }, + { "hour", 60 * 60 }, + { "min", 60 }, + }; + + int n, len = 0; + char *uptime = malloc(BUF_SIZE); + for (int i = 0; i < 3; ++i ) { + if ((n = seconds / units[i].secs) || i == 2) /* always print minutes */ + len += snprintf(uptime + len, BUF_SIZE - len, + "%d %s%s, ", n, units[i].name, n != 1 ? "s": ""); + seconds %= units[i].secs; + } + + // null-terminate at the trailing comma + uptime[len - 2] = '\0'; + return uptime; +} + +// returns "% []" +// Credit: allisio - https://gist.github.com/allisio/1e850b93c81150124c2634716fbc4815 +static char *get_battery_percentage() { + int battery_capacity; + FILE *capacity_file, *status_file; + char battery_status[12] = "Unknown"; + + if ((capacity_file = fopen(BATTERY_DIRECTORY "/capacity", "r")) == NULL) { + status = ENOENT; + halt_and_catch_fire("Unable to get battery information"); + } + + fscanf(capacity_file, "%d", &battery_capacity); + fclose(capacity_file); + + if ((status_file = fopen(BATTERY_DIRECTORY "/status", "r")) != NULL) { + fscanf(status_file, "%s", battery_status); + fclose(status_file); + } + + // max length of resulting string is 19 + // one byte for padding incase there is a newline + // 100% [Discharging] + // 1234567890123456789 + char *battery = malloc(20); + + snprintf(battery, 20, "%d%% [%s]", battery_capacity, battery_status); + + return battery; +} + +static char *get_packages(const char* dirname, const char* pacname, int num_extraneous) { + int num_packages = 0; + DIR * dirp; + struct dirent *entry; + + dirp = opendir(dirname); + + if(dirp == NULL) { + status = -1; + halt_and_catch_fire("You may not have %s installed", dirname); + } + + while((entry = readdir(dirp)) != NULL) { + if(entry->d_type == DT_DIR) num_packages++; + } + num_packages -= (2 + num_extraneous); // accounting for . and .. + + status = closedir(dirp); + + char *packages = malloc(BUF_SIZE); + snprintf(packages, BUF_SIZE, "%d (%s)", num_packages, pacname); + + return packages; +} + +static char *get_packages_pacman() { + return get_packages("/var/lib/pacman/local", "pacman", 0); +} + +static char *get_shell() { + char *shell = malloc(BUF_SIZE); + char *shell_path = getenv("SHELL"); + char *shell_name = strrchr(getenv("SHELL"), '/'); + + if(shell_name == NULL) /* if $SHELL doesn't have a '/' */ + strncpy(shell, shell_path, BUF_SIZE); /* copy the whole thing over */ + else + strncpy(shell, shell_name + 1, BUF_SIZE); /* o/w copy past the last '/' */ + + return shell; +} + +static char *get_resolution() { + int screen, width, height; + char *resolution = malloc(BUF_SIZE); + + if (display != NULL) { + screen = DefaultScreen(display); + + width = DisplayWidth(display, screen); + height = DisplayHeight(display, screen); + + snprintf(resolution, BUF_SIZE, "%dx%d", width, height); + } else { + DIR *dir; + struct dirent *entry; + char dir_name[] = "/sys/class/drm"; + char modes_file_name[BUF_SIZE * 2]; + FILE *modes; + char *line = NULL; + size_t len; + + /* preload resolution with empty string, in case we cant find a resolution through parsing */ + strncpy(resolution, "", BUF_SIZE); + + dir = opendir(dir_name); + if (dir == NULL) { + status = -1; + halt_and_catch_fire("Could not open /sys/class/drm to determine resolution in tty mode."); + } + /* parse through all directories and look for a non empty modes file */ + while ((entry = readdir(dir)) != NULL) { + if (entry->d_type == DT_LNK) { + snprintf(modes_file_name, BUF_SIZE * 2, "%s/%s/modes", dir_name, entry->d_name); + + modes = fopen(modes_file_name, "r"); + if (modes != NULL) { + if (getline(&line, &len, modes) != -1) { + strncpy(resolution, line, BUF_SIZE); + remove_newline(resolution); + + free(line); + fclose(modes); + + break; + } + + fclose(modes); + } + } + } + + closedir(dir); + } + + return resolution; +} + +static char *get_terminal() { + unsigned char *prop; + char *terminal = malloc(BUF_SIZE); + + /* check if xserver is running or if we are running in a straight tty */ + if (display != NULL) { + + unsigned long _, // not unused, but we don't need the results + window = RootWindow(display, XDefaultScreen(display)); + Atom a, + active = XInternAtom(display, "_NET_ACTIVE_WINDOW", True), + class = XInternAtom(display, "WM_CLASS", True); + +#define GetProp(property) \ + XGetWindowProperty(display, window, property, 0, 64, 0, 0, &a, (int *)&_, &_, &_, &prop); + + GetProp(active); + window = (prop[3] << 24) + (prop[2] << 16) + (prop[1] << 8) + prop[0]; + free(prop); + if(!window) goto terminal_fallback; + GetProp(class); + +#undef GetProp + + snprintf(terminal, BUF_SIZE, "%s", prop); + free(prop); + } else { +terminal_fallback: + strncpy(terminal, getenv("TERM"), BUF_SIZE); /* fallback to old method */ + /* in tty, $TERM is simply returned as "linux"; in this case get actual tty name */ + if (strcmp(terminal, "linux") == 0) { + strncpy(terminal, ttyname(STDIN_FILENO), BUF_SIZE); + } + } + + return terminal; +} + +static char *get_cpu() { + FILE *cpuinfo = fopen("/proc/cpuinfo", "r"); /* read from cpu info */ + if(cpuinfo == NULL) { + status = -1; + halt_and_catch_fire("Unable to open cpuinfo"); + } + + char *cpu_model = malloc(BUF_SIZE / 2); + char *line = NULL; + size_t len; /* unused */ + int num_cores = 0, cpu_freq, prec = 3; + double freq; + char freq_unit[] = "GHz"; + + /* read the model name into cpu_model, and increment num_cores every time model name is found */ + while(getline(&line, &len, cpuinfo) != -1) { + num_cores += sscanf(line, "model name : %[^\n@]", cpu_model); + } + free(line); + fclose(cpuinfo); + + FILE *cpufreq = fopen("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq", "r"); + line = NULL; + + if (cpufreq != NULL) { + if (getline(&line, &len, cpufreq) != -1) { + sscanf(line, "%d", &cpu_freq); + cpu_freq /= 1000; // convert kHz to MHz + } else { + fclose(cpufreq); + free(line); + goto cpufreq_fallback; + } + } else { +cpufreq_fallback: + cpufreq = fopen("/proc/cpuinfo", "r"); /* read from cpu info */ + if (cpufreq == NULL) { + status = -1; + halt_and_catch_fire("Unable to open cpuinfo"); + } + + while (getline(&line, &len, cpufreq) != -1) { + if (sscanf(line, "cpu MHz : %lf", &freq) > 0) break; + } + + cpu_freq = (int) freq; + } + + free(line); + fclose(cpufreq); + + if (cpu_freq < 1000) { + freq = (double) cpu_freq; + freq_unit[0] = 'M'; // make MHz from GHz + prec = 0; // show frequency as integer value + } else { + freq = cpu_freq / 1000.0; // convert MHz to GHz and cast to double + + while (cpu_freq % 10 == 0) { + --prec; + cpu_freq /= 10; + } + + if (prec == 0) prec = 1; // we don't want zero decimal places + } + + /* remove unneeded information */ + for (int i = 0; i < COUNT(cpu_config); ++i) { + if (cpu_config[i].repl_str == NULL) { + remove_substring(cpu_model, cpu_config[i].substring, cpu_config[i].length); + } else { + replace_substring(cpu_model, cpu_config[i].substring, cpu_config[i].repl_str, cpu_config[i].length, cpu_config[i].repl_len); + } + } + + char *cpu = malloc(BUF_SIZE); + snprintf(cpu, BUF_SIZE, "%s (%d) @ %.*f%s", cpu_model, num_cores, prec, freq, freq_unit); + free(cpu_model); + + truncate_spaces(cpu); + + if(num_cores == 0) + *cpu = '\0'; + return cpu; +} + +static char *find_gpu(int index) { + // inspired by https://github.com/pciutils/pciutils/edit/master/example.c + /* it seems that pci_lookup_name needs to be given a buffer, but I can't for the life of my figure out what its for */ + char buffer[BUF_SIZE], *device_class, *gpu = malloc(BUF_SIZE); + struct pci_access *pacc; + struct pci_dev *dev; + int gpu_index = 0; + bool found = false; + + pacc = pci_alloc(); + pci_init(pacc); + pci_scan_bus(pacc); + dev = pacc->devices; + + while(dev != NULL) { + pci_fill_info(dev, PCI_FILL_IDENT); + device_class = pci_lookup_name(pacc, buffer, sizeof(buffer), PCI_LOOKUP_CLASS, dev->device_class); + if(strcmp("VGA compatible controller", device_class) == 0 || strcmp("3D controller", device_class) == 0) { + strncpy(gpu, pci_lookup_name(pacc, buffer, sizeof(buffer), PCI_LOOKUP_DEVICE | PCI_LOOKUP_VENDOR, dev->vendor_id, dev->device_id), BUF_SIZE); + if(gpu_index == index) { + found = true; + break; + } else { + gpu_index++; + } + } + + dev = dev->next; + } + + if (found == false) *gpu = '\0'; // empty string, so it will not be printed + + pci_cleanup(pacc); + + /* remove unneeded information */ + for (int i = 0; i < COUNT(gpu_config); ++i) { + if (gpu_config[i].repl_str == NULL) { + remove_substring(gpu, gpu_config[i].substring, gpu_config[i].length); + } else { + replace_substring(gpu, gpu_config[i].substring, gpu_config[i].repl_str, gpu_config[i].length, gpu_config[i].repl_len); + } + } + + truncate_spaces(gpu); + + return gpu; +} + +static char *get_gpu1() { + return find_gpu(0); +} + +static char *get_gpu2() { + return find_gpu(1); +} + +static char *get_memory() { + int total_memory, used_memory; + int total, shared, memfree, buffers, cached, reclaimable; + + FILE *meminfo = fopen("/proc/meminfo", "r"); /* get infomation from meminfo */ + if(meminfo == NULL) { + status = -1; + halt_and_catch_fire("Unable to open meminfo"); + } + + /* We parse through all lines of meminfo and scan for the information we need */ + char *line = NULL; // allocation handled automatically by getline() + size_t len; /* unused */ + + /* parse until EOF */ + while (getline(&line, &len, meminfo) != -1) { + /* if sscanf doesn't find a match, pointer is untouched */ + sscanf(line, "MemTotal: %d", &total); + sscanf(line, "Shmem: %d", &shared); + sscanf(line, "MemFree: %d", &memfree); + sscanf(line, "Buffers: %d", &buffers); + sscanf(line, "Cached: %d", &cached); + sscanf(line, "SReclaimable: %d", &reclaimable); + } + + free(line); + + fclose(meminfo); + + /* use same calculation as neofetch */ + used_memory = (total + shared - memfree - buffers - cached - reclaimable) / 1024; + total_memory = total / 1024; + int percentage = (int) (100 * (used_memory / (double) total_memory)); + + char *memory = malloc(BUF_SIZE); + snprintf(memory, BUF_SIZE, "%dMiB / %dMiB (%d%%)", used_memory, total_memory, percentage); + + return memory; +} + +static char *get_disk_usage(const char *folder) { + char *disk_usage = malloc(BUF_SIZE); + long total, used, free; + int percentage; + status = statvfs(folder, &file_stats); + halt_and_catch_fire("Error getting disk usage for %s", folder); + total = file_stats.f_blocks * file_stats.f_frsize; + free = file_stats.f_bfree * file_stats.f_frsize; + used = total - free; + percentage = (used / (double) total) * 100; +#define TO_GB(A) ((A) / (1024.0 * 1024 * 1024)) + snprintf(disk_usage, BUF_SIZE, "%.1fGiB / %.1fGiB (%d%%)", TO_GB(used), TO_GB(total), percentage); +#undef TO_GB + return disk_usage; +} + +static char *get_disk_usage_root() { + return get_disk_usage("/"); +} + +static char *get_disk_usage_home() { + return get_disk_usage("/home"); +} + +static char *get_colors1() { + char *colors1 = malloc(BUF_SIZE); + char *s = colors1; + + for(int i = 0; i < 8; i++) { + sprintf(s, "\e[4%dm ", i); + s += 8; + } + snprintf(s, 5, "\e[0m"); + + return colors1; +} + +static char *get_colors2() { + char *colors2 = malloc(BUF_SIZE); + char *s = colors2; + + for(int i = 8; i < 16; i++) { + sprintf(s, "\e[48;5;%dm ", i); + s += 12 + (i >= 10 ? 1 : 0); + } + snprintf(s, 5, "\e[0m"); + + return colors2; +} + +static char *spacer() { + return calloc(1, 1); // freeable, null-terminated string of length 1 +} + +char *get_cache_file() { + char *cache_file = malloc(BUF_SIZE); + char *env = getenv("XDG_CACHE_HOME"); + if(env == NULL) + snprintf(cache_file, BUF_SIZE, "%s/.cache/paleofetch", getenv("HOME")); + else + snprintf(cache_file, BUF_SIZE, "%s/paleofetch", env); + + return cache_file; +} + +/* This isn't especially robust, but as long as we're the only one writing + * to our cache file, the format is simple, effective, and fast. One way + * we might get in trouble would be if the user decided not to have any + * sort of sigil (like ':') after their labels. */ +char *search_cache(char *cache_data, char *label) { + char *start = strstr(cache_data, label); + if(start == NULL) { + status = ENODATA; + halt_and_catch_fire("cache miss on key '%s'; need to --recache?", label); + } + start += strlen(label); + char *end = strchr(start, ';'); + char *buf = calloc(1, BUF_SIZE); + // skip past the '=' and stop just before the ';' + strncpy(buf, start + 1, end - start - 1); + + return buf; +} + +char *get_value(struct conf c, int read_cache, char *cache_data) { + char *value; + + // If the user's config specifies that this value should be cached + if(c.cached && read_cache) // and we have a cache to read from + value = search_cache(cache_data, c.label); // grab it from the cache + else { + // Otherwise, call the associated function to get the value + value = c.function(); + if(c.cached) { // and append it to our cache data if appropriate + char *buf = malloc(BUF_SIZE); + sprintf(buf, "%s=%s;", c.label, value); + strcat(cache_data, buf); + free(buf); + } + } + + return value; +} + +int main(int argc, char *argv[]) { + char *cache, *cache_data = NULL; + FILE *cache_file; + int read_cache; + + status = uname(&uname_info); + halt_and_catch_fire("uname failed"); + status = sysinfo(&my_sysinfo); + halt_and_catch_fire("sysinfo failed"); + display = XOpenDisplay(NULL); + + cache = get_cache_file(); + if(argc == 2 && strcmp(argv[1], "--recache") == 0) + read_cache = 0; + else { + cache_file = fopen(cache, "r"); + read_cache = cache_file != NULL; + } + + if(!read_cache) + cache_data = calloc(4, BUF_SIZE); // should be enough + else { + size_t len; /* unused */ + getline(&cache_data, &len, cache_file); + fclose(cache_file); // We just need the first (and only) line. + } + + int offset = 0; + + for (int i = 0; i < COUNT(LOGO); i++) { + // If we've run out of information to show... + if(i >= COUNT(config) - offset) // just print the next line of the logo + printf(COLOR"%s\n", LOGO[i]); + else { + // Otherwise, we've got a bit of work to do. + char *label = config[i+offset].label, + *value = get_value(config[i+offset], read_cache, cache_data); + if (strcmp(value, "") != 0) { // check if value is an empty string + printf(COLOR"%s%s\e[0m%s\n", LOGO[i], label, value); // just print if not empty + } else { + if (strcmp(label, "") != 0) { // check if label is empty, otherwise it's a spacer + ++offset; // print next line of information + free(value); // free memory allocated for empty value + label = config[i+offset].label; // read new label and value + value = get_value(config[i+offset], read_cache, cache_data); + } + printf(COLOR"%s%s\e[0m%s\n", LOGO[i], label, value); + } + free(value); + + } + } + puts("\e[0m"); + + /* Write out our cache data (if we have any). */ + if(!read_cache && *cache_data) { + cache_file = fopen(cache, "w"); + fprintf(cache_file, "%s", cache_data); + fclose(cache_file); + } + + free(cache); + free(cache_data); + if(display != NULL) { + XCloseDisplay(display); + } + + return 0; +} diff --git a/paleofetch/paleofetch.h b/paleofetch/paleofetch.h new file mode 100644 index 0000000..0e4578f --- /dev/null +++ b/paleofetch/paleofetch.h @@ -0,0 +1,27 @@ +/* Forward-declare our functions so users can mention them in their + * configs at the top of the file rather than near the bottom. */ + +static char *get_title(), + *get_bar(), + *get_os(), + *get_kernel(), + *get_host(), + *get_uptime(), + *get_battery_percentage(), + *get_packages_pacman(), + *get_shell(), + *get_resolution(), + *get_terminal(), + *get_cpu(), + *get_gpu1(), + *get_gpu2(), + *get_memory(), + *get_disk_usage_root(), + *get_disk_usage_home(), + *get_colors1(), + *get_colors2(), + *spacer(); + +#define SPACER {"", spacer, false}, +#define REMOVE(A) { (A), NULL, sizeof(A) - 1 , 0 } +#define REPLACE(A, B) { (A), (B), sizeof(A) - 1, sizeof(B) - 1 } -- cgit v1.2.3