summaryrefslogtreecommitdiff
path: root/paleofetch
diff options
context:
space:
mode:
Diffstat (limited to 'paleofetch')
-rw-r--r--paleofetch/.gitignore1
-rw-r--r--paleofetch/CHANGELOG.md20
-rw-r--r--paleofetch/LICENSE21
-rw-r--r--paleofetch/Makefile17
-rw-r--r--paleofetch/README.md102
-rw-r--r--paleofetch/config.h43
-rwxr-xr-xpaleofetch/config_scripts/battery_config.sh5
-rw-r--r--paleofetch/example.pngbin0 -> 109097 bytes
-rw-r--r--paleofetch/logos/arch.h22
-rw-r--r--paleofetch/logos/arch2.h21
-rw-r--r--paleofetch/logos/arch3.h22
-rw-r--r--paleofetch/paleofetch.c756
-rw-r--r--paleofetch/paleofetch.h27
13 files changed, 1057 insertions, 0 deletions
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
--- /dev/null
+++ b/paleofetch/example.png
Binary files 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 <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <dirent.h>
+#include <errno.h>
+
+#include <sys/utsname.h>
+#include <sys/sysinfo.h>
+#include <sys/statvfs.h>
+
+#include <pci/pci.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+
+#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 "<Battery Percentage>% [<Charging | Discharging | Unknown>]"
+// 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 }