text: #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 "sf.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;
  }
  
  
  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;
  }
  