paleofetch.c (22513B)
1 #pragma GCC diagnostic ignored "-Wunused-function" 2 #include <unistd.h> 3 #include <stdio.h> 4 #include <stdlib.h> 5 #include <stdbool.h> 6 #include <string.h> 7 #include <dirent.h> 8 #include <errno.h> 9 10 #include <sys/utsname.h> 11 #include <sys/sysinfo.h> 12 #include <sys/statvfs.h> 13 14 #include <pci/pci.h> 15 16 #include <X11/Xlib.h> 17 #include <X11/Xatom.h> 18 19 #include "paleofetch.h" 20 #include "config.h" 21 22 #define BUF_SIZE 150 23 #define COUNT(x) (int)(sizeof x / sizeof *x) 24 25 #define halt_and_catch_fire(fmt, ...) \ 26 do { \ 27 if(status != 0) { \ 28 fprintf(stderr, "paleofetch: " fmt "\n", ##__VA_ARGS__); \ 29 exit(status); \ 30 } \ 31 } while(0) 32 33 struct conf { 34 char *label, *(*function)(); 35 bool cached; 36 } config[] = CONFIG; 37 38 struct { 39 char *substring; 40 char *repl_str; 41 size_t length; 42 size_t repl_len; 43 } cpu_config[] = CPU_CONFIG, gpu_config[] = GPU_CONFIG; 44 45 Display *display; 46 struct statvfs file_stats; 47 struct utsname uname_info; 48 struct sysinfo my_sysinfo; 49 int title_length, status; 50 51 /* 52 * Replaces the first newline character with null terminator 53 */ 54 void remove_newline(char *s) { 55 while (*s != '\0' && *s != '\n') 56 s++; 57 *s = '\0'; 58 } 59 60 /* 61 * Replaces the first newline character with null terminator 62 * and returns the length of the string 63 */ 64 int remove_newline_get_length(char *s) { 65 int i; 66 for (i = 0; *s != '\0' && *s != '\n'; s++, i++); 67 *s = '\0'; 68 return i; 69 } 70 71 /* 72 * Cleans up repeated spaces in a string 73 * Trim spaces at the front of a string 74 */ 75 void truncate_spaces(char *str) { 76 int src = 0, dst = 0; 77 while(*(str + dst) == ' ') dst++; 78 79 while(*(str + dst) != '\0') { 80 *(str + src) = *(str + dst); 81 if(*(str + (dst++)) == ' ') 82 while(*(str + dst) == ' ') dst++; 83 84 src++; 85 } 86 87 *(str + src) = '\0'; 88 } 89 90 /* 91 * Removes the first len characters of substring from str 92 * Assumes that strlen(substring) >= len 93 * Returns index where substring was found, or -1 if substring isn't found 94 */ 95 void remove_substring(char *str, const char* substring, size_t len) { 96 /* shift over the rest of the string to remove substring */ 97 char *sub = strstr(str, substring); 98 if(sub == NULL) return; 99 100 int i = 0; 101 do *(sub+i) = *(sub+i+len); 102 while(*(sub+(++i)) != '\0'); 103 } 104 105 /* 106 * Replaces the first sub_len characters of sub_str from str 107 * with the first repl_len characters of repl_str 108 */ 109 void replace_substring(char *str, const char *sub_str, const char *repl_str, size_t sub_len, size_t repl_len) { 110 char buffer[BUF_SIZE / 2]; 111 char *start = strstr(str, sub_str); 112 if (start == NULL) return; // substring not found 113 114 /* check if we have enough space for new substring */ 115 if (strlen(str) - sub_len + repl_len >= BUF_SIZE / 2) { 116 status = -1; 117 halt_and_catch_fire("new substring too long to replace"); 118 } 119 120 strcpy(buffer, start + sub_len); 121 strncpy(start, repl_str, repl_len); 122 strcpy(start + repl_len, buffer); 123 } 124 125 static char *get_title() { 126 // reduce the maximum size for these, so that we don't over-fill the title string 127 char hostname[BUF_SIZE / 3]; 128 status = gethostname(hostname, BUF_SIZE / 3); 129 halt_and_catch_fire("unable to retrieve host name"); 130 131 char username[BUF_SIZE / 3]; 132 status = getlogin_r(username, BUF_SIZE / 3); 133 halt_and_catch_fire("unable to retrieve login name"); 134 135 title_length = strlen(hostname) + strlen(username) + 1; 136 137 char *title = malloc(BUF_SIZE); 138 snprintf(title, BUF_SIZE, COLOR"%s\e[0m@"COLOR"%s", username, hostname); 139 140 return title; 141 } 142 143 static char *get_bar() { 144 char *bar = malloc(BUF_SIZE); 145 char *s = bar; 146 /* for(int i = 0; i < title_length; i++) *(s) = "├──────────────────────────────"; 147 for(int i = 0; i < title_length; i++) *(s++) = '='; */ 148 for(int i = 0; i < title_length; i++) *(s++) = '\0'; 149 *s = '\0'; 150 return bar; 151 } 152 153 static char *get_os() { 154 char *os = malloc(BUF_SIZE), 155 *name = malloc(BUF_SIZE), 156 *line = NULL; 157 size_t len; 158 FILE *os_release = fopen("/etc/os-release", "r"); 159 if(os_release == NULL) { 160 status = -1; 161 halt_and_catch_fire("unable to open /etc/os-release"); 162 } 163 164 while (getline(&line, &len, os_release) != -1) { 165 if (sscanf(line, "NAME=\"%[^\"]+", name) > 0) break; 166 } 167 168 free(line); 169 fclose(os_release); 170 snprintf(os, BUF_SIZE, "%s %s", name, uname_info.machine); 171 free(name); 172 173 return os; 174 } 175 176 static char *get_kernel() { 177 char *kernel = malloc(BUF_SIZE); 178 strncpy(kernel, uname_info.release, BUF_SIZE); 179 return kernel; 180 } 181 182 static char *get_host() { 183 char *host = malloc(BUF_SIZE), buffer[BUF_SIZE/2]; 184 FILE *product_name, *product_version, *model; 185 186 if((product_name = fopen("/sys/devices/virtual/dmi/id/product_name", "r")) != NULL) { 187 if((product_version = fopen("/sys/devices/virtual/dmi/id/product_version", "r")) != NULL) { 188 fread(host, 1, BUF_SIZE/2, product_name); 189 remove_newline(host); 190 strcat(host, " "); 191 fread(buffer, 1, BUF_SIZE/2, product_version); 192 remove_newline(buffer); 193 strcat(host, buffer); 194 fclose(product_version); 195 } else { 196 fclose(product_name); 197 goto model_fallback; 198 } 199 fclose(product_name); 200 return host; 201 } 202 203 model_fallback: 204 if((model = fopen("/sys/firmware/devicetree/base/model", "r")) != NULL) { 205 fread(host, 1, BUF_SIZE, model); 206 remove_newline(host); 207 return host; 208 } 209 210 status = -1; 211 halt_and_catch_fire("unable to get host"); 212 return NULL; 213 } 214 215 static char *get_uptime() { 216 long seconds = my_sysinfo.uptime; 217 struct { char *name; int secs; } units[] = { 218 { "day", 60 * 60 * 24 }, 219 { "hour", 60 * 60 }, 220 { "min", 60 }, 221 }; 222 223 int n, len = 0; 224 char *uptime = malloc(BUF_SIZE); 225 for (int i = 0; i < 3; ++i ) { 226 if ((n = seconds / units[i].secs) || i == 2) /* always print minutes */ 227 len += snprintf(uptime + len, BUF_SIZE - len, 228 "%d %s%s, ", n, units[i].name, n != 1 ? "s": ""); 229 seconds %= units[i].secs; 230 } 231 232 // null-terminate at the trailing comma 233 uptime[len - 2] = '\0'; 234 return uptime; 235 } 236 237 // returns "<Battery Percentage>% [<Charging | Discharging | Unknown>]" 238 // Credit: allisio - https://gist.github.com/allisio/1e850b93c81150124c2634716fbc4815 239 static char *get_battery_percentage() { 240 int battery_capacity; 241 FILE *capacity_file, *status_file; 242 char battery_status[12] = "Unknown"; 243 244 if ((capacity_file = fopen(BATTERY_DIRECTORY "/capacity", "r")) == NULL) { 245 status = ENOENT; 246 halt_and_catch_fire("Unable to get battery information"); 247 } 248 249 fscanf(capacity_file, "%d", &battery_capacity); 250 fclose(capacity_file); 251 252 if ((status_file = fopen(BATTERY_DIRECTORY "/status", "r")) != NULL) { 253 fscanf(status_file, "%s", battery_status); 254 fclose(status_file); 255 } 256 257 // max length of resulting string is 19 258 // one byte for padding incase there is a newline 259 // 100% [Discharging] 260 // 1234567890123456789 261 char *battery = malloc(20); 262 263 snprintf(battery, 20, "%d%% [%s]", battery_capacity, battery_status); 264 265 return battery; 266 } 267 268 static char *get_packages(const char* dirname, const char* pacname, int num_extraneous) { 269 int num_packages = 0; 270 DIR * dirp; 271 struct dirent *entry; 272 273 dirp = opendir(dirname); 274 275 if(dirp == NULL) { 276 status = -1; 277 halt_and_catch_fire("You may not have %s installed", dirname); 278 } 279 280 while((entry = readdir(dirp)) != NULL) { 281 if(entry->d_type == DT_DIR) num_packages++; 282 } 283 num_packages -= (2 + num_extraneous); // accounting for . and .. 284 285 status = closedir(dirp); 286 287 char *packages = malloc(BUF_SIZE); 288 snprintf(packages, BUF_SIZE, "%d (%s)", num_packages, pacname); 289 290 return packages; 291 } 292 293 static char *get_packages_pacman() { 294 return get_packages("/var/lib/pacman/local", "pacman", 0); 295 } 296 297 static char *get_shell() { 298 char *shell = malloc(BUF_SIZE); 299 char *shell_path = getenv("SHELL"); 300 char *shell_name = strrchr(getenv("SHELL"), '/'); 301 302 if(shell_name == NULL) /* if $SHELL doesn't have a '/' */ 303 strncpy(shell, shell_path, BUF_SIZE); /* copy the whole thing over */ 304 else 305 strncpy(shell, shell_name + 1, BUF_SIZE); /* o/w copy past the last '/' */ 306 307 return shell; 308 } 309 310 static char *get_resolution() { 311 int screen, width, height; 312 char *resolution = malloc(BUF_SIZE); 313 314 if (display != NULL) { 315 screen = DefaultScreen(display); 316 317 width = DisplayWidth(display, screen); 318 height = DisplayHeight(display, screen); 319 320 snprintf(resolution, BUF_SIZE, "%dx%d", width, height); 321 } else { 322 DIR *dir; 323 struct dirent *entry; 324 char dir_name[] = "/sys/class/drm"; 325 char modes_file_name[BUF_SIZE * 2]; 326 FILE *modes; 327 char *line = NULL; 328 size_t len; 329 330 /* preload resolution with empty string, in case we cant find a resolution through parsing */ 331 strncpy(resolution, "", BUF_SIZE); 332 333 dir = opendir(dir_name); 334 if (dir == NULL) { 335 status = -1; 336 halt_and_catch_fire("Could not open /sys/class/drm to determine resolution in tty mode."); 337 } 338 /* parse through all directories and look for a non empty modes file */ 339 while ((entry = readdir(dir)) != NULL) { 340 if (entry->d_type == DT_LNK) { 341 snprintf(modes_file_name, BUF_SIZE * 2, "%s/%s/modes", dir_name, entry->d_name); 342 343 modes = fopen(modes_file_name, "r"); 344 if (modes != NULL) { 345 if (getline(&line, &len, modes) != -1) { 346 strncpy(resolution, line, BUF_SIZE); 347 remove_newline(resolution); 348 349 free(line); 350 fclose(modes); 351 352 break; 353 } 354 355 fclose(modes); 356 } 357 } 358 } 359 360 closedir(dir); 361 } 362 363 return resolution; 364 } 365 366 static char *get_terminal() { 367 unsigned char *prop; 368 char *terminal = malloc(BUF_SIZE); 369 370 /* check if xserver is running or if we are running in a straight tty */ 371 if (display != NULL) { 372 373 unsigned long _, // not unused, but we don't need the results 374 window = RootWindow(display, XDefaultScreen(display)); 375 Atom a, 376 active = XInternAtom(display, "_NET_ACTIVE_WINDOW", True), 377 class = XInternAtom(display, "WM_CLASS", True); 378 379 #define GetProp(property) \ 380 XGetWindowProperty(display, window, property, 0, 64, 0, 0, &a, (int *)&_, &_, &_, &prop); 381 382 GetProp(active); 383 window = (prop[3] << 24) + (prop[2] << 16) + (prop[1] << 8) + prop[0]; 384 free(prop); 385 if(!window) goto terminal_fallback; 386 GetProp(class); 387 388 #undef GetProp 389 390 snprintf(terminal, BUF_SIZE, "%s", prop); 391 free(prop); 392 } else { 393 terminal_fallback: 394 strncpy(terminal, getenv("TERM"), BUF_SIZE); /* fallback to old method */ 395 /* in tty, $TERM is simply returned as "linux"; in this case get actual tty name */ 396 if (strcmp(terminal, "linux") == 0) { 397 strncpy(terminal, ttyname(STDIN_FILENO), BUF_SIZE); 398 } 399 } 400 401 return terminal; 402 } 403 404 static char *get_cpu() { 405 FILE *cpuinfo = fopen("/proc/cpuinfo", "r"); /* read from cpu info */ 406 if(cpuinfo == NULL) { 407 status = -1; 408 halt_and_catch_fire("Unable to open cpuinfo"); 409 } 410 411 char *cpu_model = malloc(BUF_SIZE / 2); 412 char *line = NULL; 413 size_t len; /* unused */ 414 int num_cores = 0, cpu_freq, prec = 3; 415 double freq; 416 char freq_unit[] = "GHz"; 417 418 /* read the model name into cpu_model, and increment num_cores every time model name is found */ 419 while(getline(&line, &len, cpuinfo) != -1) { 420 num_cores += sscanf(line, "model name : %[^\n@]", cpu_model); 421 } 422 free(line); 423 fclose(cpuinfo); 424 425 FILE *cpufreq = fopen("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq", "r"); 426 line = NULL; 427 428 if (cpufreq != NULL) { 429 if (getline(&line, &len, cpufreq) != -1) { 430 sscanf(line, "%d", &cpu_freq); 431 cpu_freq /= 1000; // convert kHz to MHz 432 } else { 433 fclose(cpufreq); 434 free(line); 435 goto cpufreq_fallback; 436 } 437 } else { 438 cpufreq_fallback: 439 cpufreq = fopen("/proc/cpuinfo", "r"); /* read from cpu info */ 440 if (cpufreq == NULL) { 441 status = -1; 442 halt_and_catch_fire("Unable to open cpuinfo"); 443 } 444 445 while (getline(&line, &len, cpufreq) != -1) { 446 if (sscanf(line, "cpu MHz : %lf", &freq) > 0) break; 447 } 448 449 cpu_freq = (int) freq; 450 } 451 452 free(line); 453 fclose(cpufreq); 454 455 if (cpu_freq < 1000) { 456 freq = (double) cpu_freq; 457 freq_unit[0] = 'M'; // make MHz from GHz 458 prec = 0; // show frequency as integer value 459 } else { 460 freq = cpu_freq / 1000.0; // convert MHz to GHz and cast to double 461 462 while (cpu_freq % 10 == 0) { 463 --prec; 464 cpu_freq /= 10; 465 } 466 467 if (prec == 0) prec = 1; // we don't want zero decimal places 468 } 469 470 /* remove unneeded information */ 471 for (int i = 0; i < COUNT(cpu_config); ++i) { 472 if (cpu_config[i].repl_str == NULL) { 473 remove_substring(cpu_model, cpu_config[i].substring, cpu_config[i].length); 474 } else { 475 replace_substring(cpu_model, cpu_config[i].substring, cpu_config[i].repl_str, cpu_config[i].length, cpu_config[i].repl_len); 476 } 477 } 478 479 char *cpu = malloc(BUF_SIZE); 480 snprintf(cpu, BUF_SIZE, "%s (%d) @ %.*f%s", cpu_model, num_cores, prec, freq, freq_unit); 481 free(cpu_model); 482 483 truncate_spaces(cpu); 484 485 if(num_cores == 0) 486 *cpu = '\0'; 487 return cpu; 488 } 489 490 static char *find_gpu(int index) { 491 // inspired by https://github.com/pciutils/pciutils/edit/master/example.c 492 /* 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 */ 493 char buffer[BUF_SIZE], *device_class, *gpu = malloc(BUF_SIZE); 494 struct pci_access *pacc; 495 struct pci_dev *dev; 496 int gpu_index = 0; 497 bool found = false; 498 499 pacc = pci_alloc(); 500 pci_init(pacc); 501 pci_scan_bus(pacc); 502 dev = pacc->devices; 503 504 while(dev != NULL) { 505 pci_fill_info(dev, PCI_FILL_IDENT); 506 device_class = pci_lookup_name(pacc, buffer, sizeof(buffer), PCI_LOOKUP_CLASS, dev->device_class); 507 if(strcmp("VGA compatible controller", device_class) == 0 || strcmp("3D controller", device_class) == 0) { 508 strncpy(gpu, pci_lookup_name(pacc, buffer, sizeof(buffer), PCI_LOOKUP_DEVICE | PCI_LOOKUP_VENDOR, dev->vendor_id, dev->device_id), BUF_SIZE); 509 if(gpu_index == index) { 510 found = true; 511 break; 512 } else { 513 gpu_index++; 514 } 515 } 516 517 dev = dev->next; 518 } 519 520 if (found == false) *gpu = '\0'; // empty string, so it will not be printed 521 522 pci_cleanup(pacc); 523 524 /* remove unneeded information */ 525 for (int i = 0; i < COUNT(gpu_config); ++i) { 526 if (gpu_config[i].repl_str == NULL) { 527 remove_substring(gpu, gpu_config[i].substring, gpu_config[i].length); 528 } else { 529 replace_substring(gpu, gpu_config[i].substring, gpu_config[i].repl_str, gpu_config[i].length, gpu_config[i].repl_len); 530 } 531 } 532 533 truncate_spaces(gpu); 534 535 return gpu; 536 } 537 538 static char *get_gpu1() { 539 return find_gpu(0); 540 } 541 542 static char *get_gpu2() { 543 return find_gpu(1); 544 } 545 546 static char *get_memory() { 547 int total_memory, used_memory; 548 int total, shared, memfree, buffers, cached, reclaimable; 549 550 FILE *meminfo = fopen("/proc/meminfo", "r"); /* get infomation from meminfo */ 551 if(meminfo == NULL) { 552 status = -1; 553 halt_and_catch_fire("Unable to open meminfo"); 554 } 555 556 /* We parse through all lines of meminfo and scan for the information we need */ 557 char *line = NULL; // allocation handled automatically by getline() 558 size_t len; /* unused */ 559 560 /* parse until EOF */ 561 while (getline(&line, &len, meminfo) != -1) { 562 /* if sscanf doesn't find a match, pointer is untouched */ 563 sscanf(line, "MemTotal: %d", &total); 564 sscanf(line, "Shmem: %d", &shared); 565 sscanf(line, "MemFree: %d", &memfree); 566 sscanf(line, "Buffers: %d", &buffers); 567 sscanf(line, "Cached: %d", &cached); 568 sscanf(line, "SReclaimable: %d", &reclaimable); 569 } 570 571 free(line); 572 573 fclose(meminfo); 574 575 /* use same calculation as neofetch */ 576 used_memory = (total + shared - memfree - buffers - cached - reclaimable) / 1024; 577 total_memory = total / 1024; 578 int percentage = (int) (100 * (used_memory / (double) total_memory)); 579 580 char *memory = malloc(BUF_SIZE); 581 snprintf(memory, BUF_SIZE, "%dMiB / %dMiB (%d%%)", used_memory, total_memory, percentage); 582 583 return memory; 584 } 585 586 static char *get_disk_usage(const char *folder) { 587 char *disk_usage = malloc(BUF_SIZE); 588 long total, used, free; 589 int percentage; 590 status = statvfs(folder, &file_stats); 591 halt_and_catch_fire("Error getting disk usage for %s", folder); 592 total = file_stats.f_blocks * file_stats.f_frsize; 593 free = file_stats.f_bfree * file_stats.f_frsize; 594 used = total - free; 595 percentage = (used / (double) total) * 100; 596 #define TO_GB(A) ((A) / (1024.0 * 1024 * 1024)) 597 snprintf(disk_usage, BUF_SIZE, "%.1fGiB / %.1fGiB (%d%%)", TO_GB(used), TO_GB(total), percentage); 598 #undef TO_GB 599 return disk_usage; 600 } 601 602 static char *get_disk_usage_root() { 603 return get_disk_usage("/"); 604 } 605 606 static char *get_disk_usage_home() { 607 return get_disk_usage("/home"); 608 } 609 610 static char *get_colors1() { 611 char *colors1 = malloc(BUF_SIZE); 612 char *s = colors1; 613 614 for(int i = 0; i < 8; i++) { 615 sprintf(s, "\e[4%dm ", i); 616 s += 8; 617 } 618 snprintf(s, 5, "\e[0m"); 619 620 return colors1; 621 } 622 623 static char *get_colors2() { 624 char *colors2 = malloc(BUF_SIZE); 625 char *s = colors2; 626 627 for(int i = 8; i < 16; i++) { 628 sprintf(s, "\e[48;5;%dm ", i); 629 s += 12 + (i >= 10 ? 1 : 0); 630 } 631 snprintf(s, 5, "\e[0m"); 632 633 return colors2; 634 } 635 636 static char *spacer() { 637 return calloc(1, 1); // freeable, null-terminated string of length 1 638 } 639 640 char *get_cache_file() { 641 char *cache_file = malloc(BUF_SIZE); 642 char *env = getenv("XDG_CACHE_HOME"); 643 if(env == NULL) 644 snprintf(cache_file, BUF_SIZE, "%s/.cache/paleofetch", getenv("HOME")); 645 else 646 snprintf(cache_file, BUF_SIZE, "%s/paleofetch", env); 647 648 return cache_file; 649 } 650 651 /* This isn't especially robust, but as long as we're the only one writing 652 * to our cache file, the format is simple, effective, and fast. One way 653 * we might get in trouble would be if the user decided not to have any 654 * sort of sigil (like ':') after their labels. */ 655 char *search_cache(char *cache_data, char *label) { 656 char *start = strstr(cache_data, label); 657 if(start == NULL) { 658 status = ENODATA; 659 halt_and_catch_fire("cache miss on key '%s'; need to --recache?", label); 660 } 661 start += strlen(label); 662 char *end = strchr(start, ';'); 663 char *buf = calloc(1, BUF_SIZE); 664 // skip past the '=' and stop just before the ';' 665 strncpy(buf, start + 1, end - start - 1); 666 667 return buf; 668 } 669 670 char *get_value(struct conf c, int read_cache, char *cache_data) { 671 char *value; 672 673 // If the user's config specifies that this value should be cached 674 if(c.cached && read_cache) // and we have a cache to read from 675 value = search_cache(cache_data, c.label); // grab it from the cache 676 else { 677 // Otherwise, call the associated function to get the value 678 value = c.function(); 679 if(c.cached) { // and append it to our cache data if appropriate 680 char *buf = malloc(BUF_SIZE); 681 sprintf(buf, "%s=%s;", c.label, value); 682 strcat(cache_data, buf); 683 free(buf); 684 } 685 } 686 687 return value; 688 } 689 690 int main(int argc, char *argv[]) { 691 char *cache, *cache_data = NULL; 692 FILE *cache_file; 693 int read_cache; 694 695 status = uname(&uname_info); 696 halt_and_catch_fire("uname failed"); 697 status = sysinfo(&my_sysinfo); 698 halt_and_catch_fire("sysinfo failed"); 699 display = XOpenDisplay(NULL); 700 701 cache = get_cache_file(); 702 if(argc == 2 && strcmp(argv[1], "--recache") == 0) 703 read_cache = 0; 704 else { 705 cache_file = fopen(cache, "r"); 706 read_cache = cache_file != NULL; 707 } 708 709 if(!read_cache) 710 cache_data = calloc(4, BUF_SIZE); // should be enough 711 else { 712 size_t len; /* unused */ 713 getline(&cache_data, &len, cache_file); 714 fclose(cache_file); // We just need the first (and only) line. 715 } 716 717 int offset = 0; 718 719 for (int i = 0; i < COUNT(LOGO); i++) { 720 // If we've run out of information to show... 721 if(i >= COUNT(config) - offset) // just print the next line of the logo 722 printf(COLOR"%s\n", LOGO[i]); 723 else { 724 // Otherwise, we've got a bit of work to do. 725 char *label = config[i+offset].label, 726 *value = get_value(config[i+offset], read_cache, cache_data); 727 if (strcmp(value, "") != 0) { // check if value is an empty string 728 printf(COLOR"%s%s\e[0m%s\n", LOGO[i], label, value); // just print if not empty 729 } else { 730 if (strcmp(label, "") != 0) { // check if label is empty, otherwise it's a spacer 731 ++offset; // print next line of information 732 free(value); // free memory allocated for empty value 733 label = config[i+offset].label; // read new label and value 734 value = get_value(config[i+offset], read_cache, cache_data); 735 } 736 printf(COLOR"%s%s\e[0m%s\n", LOGO[i], label, value); 737 } 738 free(value); 739 740 } 741 } 742 puts("\e[0m"); 743 744 /* Write out our cache data (if we have any). */ 745 if(!read_cache && *cache_data) { 746 cache_file = fopen(cache, "w"); 747 fprintf(cache_file, "%s", cache_data); 748 fclose(cache_file); 749 } 750 751 free(cache); 752 free(cache_data); 753 if(display != NULL) { 754 XCloseDisplay(display); 755 } 756 757 return 0; 758 }