1 #include <errno.h>
2 #include <stdio.h>
3 #include <fcntl.h>
4 #include <string.h>
5 #include <unistd.h>
6 #include <locale.h>
7 #include <assert.h>
8 #include <strings.h>
9 #include <pthread.h>
10 #include <semaphore.h>
11 #include <sys/param.h>
12 #include <sys/types.h>
13 #if defined(__linux__) || defined(__gnu_hurd__)
14 #include <langinfo.h>
15 #include <sys/utsname.h>
16 #include <sys/sysinfo.h>
17 #elif !defined(__MINGW64__)
18 #include <langinfo.h>
19 #include <sys/sysctl.h>
20 #include <sys/utsname.h>
21 #else
22 #include <sysinfoapi.h>
23 #endif
24 #include <notcurses/notcurses.h>
25 #include "compat/compat.h"
26 #include "builddef.h"
27 #include "ncart.h"
28 
29 static inline char*
find_data(const char * datum)30 find_data(const char* datum){
31   char* datadir = notcurses_data_dir();
32   if(datadir == NULL){
33     return NULL;
34   }
35   const size_t dlen = strlen(datadir);
36   char* path = malloc(dlen + 1 + strlen(datum) + 1);
37   if(path == NULL){
38     free(datadir);
39     return NULL;
40   }
41   strcpy(path, datadir);
42   path[dlen] = path_separator();
43   strcpy(path + dlen + 1, datum);
44   free(datadir);
45   return path;
46 }
47 
48 typedef struct distro_info {
49   const char* name;            // must match 'lsb_release -i'
50   const char* logofile;        // kept at original aspect ratio, lain atop bg
51 } distro_info;
52 
53 typedef struct fetched_info {
54   char* username;              // we borrow a reference
55   char* hostname;
56   const distro_info* distro;
57   char* logo;                  // strdup() from /etc/os-release
58   char* distro_pretty;         // strdup() from /etc/os-release
59   char* kernel;                // strdup(uname(2)->name)
60   char* kernver;               // strdup(uname(2)->version);
61   char* desktop;               // getenv("XDG_CURRENT_DESKTOP")
62   const char* shell;           // getenv("SHELL")
63   char* term;                  // notcurses_detected_terminal(), heap-alloced
64   char* lang;                  // getenv("LANG")
65   char* cpu_model;             // FIXME don't handle hetero setups yet
66   int core_count;
67   // if there is no other logo found, fall back to a logo filched from neofetch
68   const char* neologo;         // text with color substitution templates
69 } fetched_info;
70 
71 static void
free_fetched_info(fetched_info * fi)72 free_fetched_info(fetched_info* fi){
73   free(fi->cpu_model);
74   free(fi->hostname);
75   free(fi->username);
76   free(fi->kernel);
77   free(fi->kernver);
78   free(fi->distro_pretty);
79   free(fi->term);
80 }
81 
82 static int
fetch_env_vars(struct notcurses * nc,fetched_info * fi)83 fetch_env_vars(struct notcurses* nc, fetched_info* fi){
84 #if defined(__APPLE__)
85   fi->desktop = "Aqua";
86 #elif defined(__MINGW64__)
87   fi->desktop = "Metro";
88 #else
89   fi->desktop = getenv("XDG_CURRENT_DESKTOP");
90 #endif
91   fi->shell = getenv("SHELL");
92   fi->term = notcurses_detected_terminal(nc);
93   fi->lang = getenv("LANG");
94   if(fi->lang == NULL){
95     fi->lang = nl_langinfo(CODESET);
96   }
97   return 0;
98 }
99 
100 // if nothing else is available, use a generic architecture based on compile
101 static const char*
fallback_cpuinfo(void)102 fallback_cpuinfo(void){
103 #if defined(__amd64__) || defined(_M_AMD64)
104   return "amd64";
105 #elif defined(__aarch64__)
106   return "aarch64";
107 #elif defined(__arm__)
108   return "arm";
109 #elif defined(__i386__) || defined(_M_IX86)
110   return "i386";
111 #elif defined(__ia64__)
112   return "ia64";
113 #else
114   #warning "unknown target architecture"
115   return "unknown";
116 #endif
117 }
118 
119 static int
fetch_bsd_cpuinfo(fetched_info * fi)120 fetch_bsd_cpuinfo(fetched_info* fi){
121 #if defined(__linux__) || defined(__gnu_hurd__) || defined(__MINGW64__)
122   (void)fi;
123 #else
124   size_t len = sizeof(fi->core_count);
125   int mib[2] = { CTL_HW, HW_NCPU };
126   if(sysctl(mib, sizeof(mib) / sizeof(*mib), &fi->core_count, &len, NULL, 0)){
127     fprintf(stderr, "Coudln't acquire CTL_HW+HW_NCPU sysctl (%s)\n", strerror(errno));
128     return -1;
129   }
130   mib[1] = HW_MODEL;
131   size_t modellen = 80; // FIXME?
132   fi->cpu_model = malloc(modellen);
133   if(sysctl(mib, sizeof(mib) / sizeof(*mib), fi->cpu_model, &modellen, NULL, 0)){
134     fprintf(stderr, "Coudln't acquire CTL_HW+HW_MODEL sysctl (%s)\n", strerror(errno));
135     return -1;
136   }
137 #endif
138   return 0;
139 }
140 
141 static int
fetch_windows_cpuinfo(fetched_info * fi)142 fetch_windows_cpuinfo(fetched_info* fi){
143 #ifdef __MINGW64__
144   SYSTEM_INFO info = {};
145   GetSystemInfo(&info);
146   switch(info.wProcessorArchitecture){
147     case PROCESSOR_ARCHITECTURE_AMD64:
148       fi->cpu_model = strdup("amd64"); break;
149     case PROCESSOR_ARCHITECTURE_ARM:
150       fi->cpu_model = strdup("ARM"); break;
151     case PROCESSOR_ARCHITECTURE_ARM64:
152       fi->cpu_model = strdup("AArch64"); break;
153     case PROCESSOR_ARCHITECTURE_IA64:
154       fi->cpu_model = strdup("Itanium"); break;
155     case PROCESSOR_ARCHITECTURE_INTEL:
156       fi->cpu_model = strdup("i386"); break;
157     default:
158       fi->cpu_model = strdup("Unknown processor"); break;
159   }
160   fi->core_count = info.dwNumberOfProcessors;
161 #else
162   (void)fi;
163 #endif
164   return 0;
165 }
166 
167 // guess what? the form of /proc/cpuinfo is arch-dependent!
168 // that's right, fuck you!
169 static int
fetch_cpu_info(fetched_info * fi)170 fetch_cpu_info(fetched_info* fi){
171   FILE* cpuinfo = fopen("/proc/cpuinfo", "re");
172   if(cpuinfo == NULL){
173     fprintf(stderr, "Error opening /proc/cpuinfo (%s)\n", strerror(errno));
174     return -1;
175   }
176   char buf[BUFSIZ];
177   while(fgets(buf, sizeof(buf), cpuinfo)){
178 // works for both amd64 and ARM
179 #define CORE "processor"
180 // model name doesn't appear on all architectures, so fall back to vendor_id
181 #define TAG "model name"
182 #define VEND "vendor_id"
183     if(strncmp(buf, TAG, strlen(TAG)) == 0){
184       // model name trumps vendor_id
185       char* start = strchr(buf + strlen(TAG), ':');
186       if(start){
187         ++start;
188         char* nl = strchr(start, '\n');
189         *nl = '\0';
190         free(fi->cpu_model);
191         fi->cpu_model = strdup(start);
192       }
193     }else if(strncmp(buf, VEND, strlen(VEND)) == 0){
194       // vendor_id ought only be used in the absence of model name
195       if(fi->cpu_model == NULL){
196         char* start = strchr(buf + strlen(VEND), ':');
197         if(start){
198           ++start;
199           char* nl = strchr(start, '\n');
200           *nl = '\0';
201           fi->cpu_model = strdup(start);
202         }
203       }
204       // need strncasecmp() because ARM cpuinfo uses "Processor" ugh
205     }else if(strncasecmp(buf, CORE, strlen(CORE)) == 0){
206       ++fi->core_count;
207     }
208 #undef VEND
209 #undef TAG
210 #undef CORE
211   }
212   return 0;
213 }
214 
215 #ifdef __linux__
216 // Given a filename, check for its existence in the directories specified by
217 // https://specifications.freedesktop.org/icon-theme-spec/latest/ar01s03.html.
218 // Returns NULL if no such file can be found. Return value is heap-allocated.
219 static char *
get_xdg_logo(const char * spec)220 get_xdg_logo(const char *spec){
221   const char* logopath = "/usr/share/pixmaps/";
222   int dfd = open(logopath, O_CLOEXEC | O_DIRECTORY);
223   if(dfd < 0){
224     return NULL;
225   }
226   int r = faccessat(dfd, spec, R_OK, 0);
227   close(dfd);
228   if(r){
229     return NULL;
230   }
231   char* p = malloc(strlen(spec) + strlen(logopath) + 1);
232   strcpy(p, logopath);
233   strcat(p, spec);
234   return p;
235 }
236 #endif
237 
238 // FIXME deal more forgivingly with quotation marks
239 static const distro_info*
linux_ncneofetch(fetched_info * fi)240 linux_ncneofetch(fetched_info* fi){
241   const distro_info* dinfo = NULL;
242 #ifdef __linux__
243   static const distro_info distros[] = {
244     {
245       .name = "arch",
246       // from core/filesystem
247       .logofile = "/usr/share/pixmaps/archlinux-logo.png",
248     }, {
249       .name = "artix",
250       // from system/filesystem
251       .logofile = "/usr/share/pixmaps/artixlinux-logo.png",
252     }, {
253       .name = "debian",
254       // from desktop-base package
255       .logofile = "/usr/share/desktop-base/debian-logos/logo-text-256.png",
256     }, {
257       .name = "fedora",
258       // from redhat-lsb-core package
259       .logofile = "/usr/share/pixmaps/fedora-logo.png",
260     }, {
261       .name = NULL,
262       .logofile = NULL,
263     },
264   };
265   FILE* osinfo = fopen("/etc/os-release", "re");
266   if(osinfo == NULL){
267     return NULL;
268   }
269   char buf[BUFSIZ];
270   char* distro = NULL;
271   while(fgets(buf, sizeof(buf), osinfo)){
272 #define PRETTY "PRETTY_NAME=\""
273 #define ID "ID=" // no quotes on this one
274 #define LOGOQ "LOGO=\""
275 #define LOGO "LOGO=" // handle LOGO sans quotes
276     if(strncmp(buf, ID, strlen(ID)) == 0){
277       char* nl = strchr(buf + strlen(ID), '\n');
278       if(nl){
279         *nl = '\0';
280         distro = strdup(buf + strlen(ID));
281       }
282     }else if(!fi->distro_pretty && strncmp(buf, PRETTY, strlen(PRETTY)) == 0){
283       char* nl = strchr(buf + strlen(PRETTY), '"');
284       if(nl){
285         *nl = '\0';
286         fi->distro_pretty = strdup(buf + strlen(PRETTY));
287       }
288     }else if(!fi->logo && strncmp(buf, LOGOQ, strlen(LOGOQ)) == 0){
289       char* nl = strchr(buf + strlen(LOGOQ), '"');
290       if(nl){
291         *nl = '\0';
292         fi->logo = get_xdg_logo(buf + strlen(LOGOQ));
293       }
294     }else if(!fi->logo && strncmp(buf, LOGO, strlen(LOGO)) == 0){
295       char* nl = strchr(buf + strlen(LOGO), '\n');
296       if(nl){
297         *nl = '\0';
298         fi->logo = get_xdg_logo(buf + strlen(LOGO));
299       }
300     }
301   }
302 #undef LOGO
303 #undef LOGOQ
304 #undef ID
305 #undef PRETTY
306   fclose(osinfo);
307   if(distro == NULL){
308     return NULL;
309   }
310   for(dinfo = distros ; dinfo->name ; ++dinfo){
311     if(strcmp(dinfo->name, distro) == 0){
312       break;
313     }
314   }
315   if(fi->logo == NULL){
316     fi->neologo = get_neofetch_art(distro);
317   }
318   free(distro);
319 #else
320   (void)fi;
321 #endif
322   return dinfo;
323 }
324 
325 typedef enum {
326   NCNEO_LINUX,
327   NCNEO_FREEBSD,
328   NCNEO_DRAGONFLY,
329   NCNEO_XNU,
330   NCNEO_WINDOWS,
331   NCNEO_UNKNOWN,
332 } ncneo_kernel_e;
333 
334 static ncneo_kernel_e
get_kernel(fetched_info * fi)335 get_kernel(fetched_info* fi){
336 #ifndef __MINGW64__
337   struct utsname uts;
338   if(uname(&uts)){
339     fprintf(stderr, "Failure invoking uname (%s)\n", strerror(errno));
340     return -1;
341   }
342   fi->kernel = strdup(uts.sysname);
343   fi->kernver = strdup(uts.release);
344   if(strcmp(uts.sysname, "Linux") == 0){
345     return NCNEO_LINUX;
346   }else if(strcmp(uts.sysname, "FreeBSD") == 0){
347     return NCNEO_FREEBSD;
348   }else if(strcmp(uts.sysname, "DragonFly") == 0){
349     return NCNEO_DRAGONFLY;
350   }else if(strcmp(uts.sysname, "Darwin") == 0){
351     return NCNEO_XNU;
352   }
353   fprintf(stderr, "Unknown operating system via uname: %s\n", uts.sysname);
354 #else
355   OSVERSIONINFOEX osvi;
356   ZeroMemory(&osvi, sizeof(osvi));
357   osvi.dwOSVersionInfoSize = sizeof(osvi);
358   GetVersionExA((LPOSVERSIONINFOA)&osvi);
359   char ver[20]; // sure why not
360   snprintf(ver, sizeof(ver), "%lu.%lu", osvi.dwMajorVersion, osvi.dwMinorVersion);
361   fi->kernver = strdup(ver);
362   fi->kernel = strdup("WNT");
363   DWORD ptype;
364   if(GetProductInfo(osvi.dwMajorVersion,
365                     osvi.dwMinorVersion,
366                     osvi.wServicePackMajor,
367                     osvi.wServicePackMinor,
368                     &ptype)){
369     switch(ptype){
370       case PRODUCT_BUSINESS: fi->distro_pretty = strdup("Business"); break;
371       case PRODUCT_BUSINESS_N: fi->distro_pretty = strdup("Business N"); break;
372       case PRODUCT_CLUSTER_SERVER: fi->distro_pretty = strdup("HPC Edition"); break;
373       case PRODUCT_CLUSTER_SERVER_V: fi->distro_pretty = strdup("Server Hyper Core V"); break;
374       case PRODUCT_CORE: fi->distro_pretty = strdup("Windows 10 Home"); break;
375       case PRODUCT_CORE_COUNTRYSPECIFIC: fi->distro_pretty = strdup("Windows 10 Home China"); break;
376       case PRODUCT_CORE_N: fi->distro_pretty = strdup("Windows 10 Home N"); break;
377       case PRODUCT_CORE_SINGLELANGUAGE: fi->distro_pretty = strdup("Windows 10 Home Single Language"); break;
378       case PRODUCT_EDUCATION: fi->distro_pretty = strdup("Windows 10 Education"); break;
379       case PRODUCT_EDUCATION_N: fi->distro_pretty = strdup("Windows 10 Education N"); break;
380       case PRODUCT_ENTERPRISE: fi->distro_pretty = strdup("Windows 10 Enterprise"); break;
381       case PRODUCT_ENTERPRISE_EVALUATION: fi->distro_pretty = strdup("Windows 10 Enterprise Eval"); break;
382       case PRODUCT_ENTERPRISE_E: fi->distro_pretty = strdup("Windows 10 Enterprise E"); break;
383       case PRODUCT_ENTERPRISE_N: fi->distro_pretty = strdup("Windows 10 Enterprise N"); break;
384       case PRODUCT_ENTERPRISE_N_EVALUATION: fi->distro_pretty = strdup("Windows 10 Enterprise N Eval"); break;
385       case PRODUCT_ENTERPRISE_S: fi->distro_pretty = strdup("Windows 10 Enterprise 2015 LTSB"); break;
386       case PRODUCT_ENTERPRISE_S_EVALUATION: fi->distro_pretty = strdup("Windows 10 Enterprise 2015 LTSB Eval"); break;
387       case PRODUCT_ENTERPRISE_S_N: fi->distro_pretty = strdup("Windows 10 Enterprise 2015 LTSB N"); break;
388       case PRODUCT_ENTERPRISE_S_N_EVALUATION: fi->distro_pretty = strdup("Windows 10 Enterprise 2015 LTSB N Eval"); break;
389       case PRODUCT_HOME_BASIC: fi->distro_pretty = strdup("Home Basic"); break;
390       case PRODUCT_HOME_BASIC_N: fi->distro_pretty = strdup("Home Basic N"); break;
391       case PRODUCT_HOME_PREMIUM: fi->distro_pretty = strdup("Home Premium"); break;
392       case PRODUCT_HOME_PREMIUM_N: fi->distro_pretty = strdup("Home Premium N"); break;
393       case PRODUCT_HOME_PREMIUM_SERVER: fi->distro_pretty = strdup("Windows Home Server 2011"); break;
394       case PRODUCT_HOME_SERVER: fi->distro_pretty = strdup("Windows Storage Server 2008 R2 Essentials"); break;
395       case PRODUCT_HYPERV: fi->distro_pretty = strdup("Windows Hyper-V Server"); break;
396       case PRODUCT_IOTUAP: fi->distro_pretty = strdup("Windows 10 IoT Core"); break;
397       /*case PRODUCT_IOTUAPCOMMERCIAL: fi->distro_pretty = strdup("Windows 10 IoT Core Commercial"); break;
398       case PRODUCT_PRO_WORKSTATION: fi->distro_pretty = strdup("Windows 10 Pro for Workstations"); break;
399       case PRODUCT_PRO_WORKSTATION_N: fi->distro_pretty = strdup("Windows 10 Pro for Workstations N"); break;*/
400       case PRODUCT_PROFESSIONAL: fi->distro_pretty = strdup("Windows 10 Pro"); break;
401       case PRODUCT_PROFESSIONAL_N: fi->distro_pretty = strdup("Windows 10 Pro N"); break;
402       case PRODUCT_PROFESSIONAL_WMC: fi->distro_pretty = strdup("Professional with Media Center"); break;
403       case PRODUCT_ULTIMATE: fi->distro_pretty = strdup("Ultimate"); break;
404       case PRODUCT_ULTIMATE_N: fi->distro_pretty = strdup("Ultimate N"); break;
405       default: fi->distro_pretty = strdup("Unknown product"); break;
406     }
407   }
408   return NCNEO_WINDOWS;
409 #endif
410   return NCNEO_UNKNOWN;
411 }
412 
413 // windows distro_pretty gets handled in get_kernel() above
414 static const distro_info*
windows_ncneofetch(fetched_info * fi)415 windows_ncneofetch(fetched_info* fi){
416   static distro_info mswin = {
417     .name = "Windows",
418   };
419   mswin.logofile = find_data("Windows10Logo.png"),
420   fi->neologo = get_neofetch_art("Windows");
421   return &mswin;
422 }
423 
424 static const distro_info*
freebsd_ncneofetch(fetched_info * fi)425 freebsd_ncneofetch(fetched_info* fi){
426   static distro_info fbsd = {
427     .name = "FreeBSD",
428   };
429   fbsd.logofile = find_data("freebsd.png"),
430   fi->neologo = get_neofetch_art("BSD"); // use big daemon logo
431   fi->distro_pretty = NULL;
432   return &fbsd;
433 }
434 
435 static const distro_info*
dragonfly_ncneofetch(fetched_info * fi)436 dragonfly_ncneofetch(fetched_info* fi){
437   static distro_info fbsd = {
438     .name = "DragonFly BSD",
439     .logofile = NULL, // FIXME
440   };
441   fi->neologo = get_neofetch_art("dragonfly");
442   fi->distro_pretty = NULL;
443   return &fbsd;
444 }
445 
446 static const distro_info*
xnu_ncneofetch(fetched_info * fi)447 xnu_ncneofetch(fetched_info* fi){
448   static distro_info xnu = {
449     .name = "OS X",
450     .logofile = "/System/Library/PrivateFrameworks/LoginUIKit.framework/Versions/A/Frameworks/LoginUICore.framework/Versions/A/Resources/apple@2x.png",
451   };
452   fi->neologo = get_neofetch_art("Darwin");
453   fi->distro_pretty = notcurses_osversion();
454   return &xnu;
455 }
456 
457 static int
drawpalette(struct notcurses * nc)458 drawpalette(struct notcurses* nc){
459   int showpl = 64; // show this many per line
460   int psize = notcurses_palette_size(nc);
461   if(psize > NCPALETTESIZE){
462     psize = NCPALETTESIZE;
463   }
464   unsigned dimy, dimx;
465   struct ncplane* n = notcurses_stddim_yx(nc, &dimy, &dimx);
466   if(dimx < 64){
467     return -1;
468   }
469   int scale = notcurses_canutf8(nc) ? 2 : 1; // use half blocks for 2*showpl
470   ncplane_cursor_move_yx(n, -1, 0);
471   for(int y = 0 ; y < (psize + (showpl * scale) - 1) / showpl / scale ; ++y){
472     // we show a maximum of showpl * scale palette entries per line
473     int toshow = psize - y * showpl;
474     if(toshow > showpl){
475       toshow = showpl;
476     }
477     if(ncplane_cursor_move_yx(n, -1, 0)){
478       return -1;
479     }
480     ncplane_set_fg_default(n);
481     ncplane_set_bg_default(n);
482     for(unsigned x = 0 ; x < (dimx - toshow) / 2 ; ++x){
483       if(ncplane_putchar(n, ' ') < 0){
484         return -1;
485       }
486     }
487     // center based on the number being shown on this line
488     for(unsigned x = (dimx - toshow) / 2 ; x < dimx / 2 + 32 ; ++x){
489       const int truex = x - (dimx - toshow) / 2;
490       if(y * showpl * scale + truex >= psize){
491         break;
492       }
493       if(ncplane_set_bg_palindex(n, y * showpl * scale + truex)){
494         return -1;
495       }
496       if(scale == 2){
497         if(ncplane_set_fg_palindex(n, (y * showpl * scale + truex + showpl) % psize)){
498           return -1;
499         }
500         if(ncplane_putegc(n, u8"▄", NULL) == EOF){
501           return -1;
502         }
503       }else{
504         if(ncplane_putchar(n, ' ') == EOF){
505           return -1;
506         }
507       }
508     }
509     ncplane_set_fg_default(n);
510     ncplane_set_bg_default(n);
511     for(unsigned x = dimx / 2 + 32 ; x < dimx - 1 ; ++x){
512       if(ncplane_putchar(n, ' ') < 0){
513         return -1;
514       }
515     }
516     if(ncplane_putchar(n, '\n') == EOF){
517       return -1;
518     }
519   }
520   return 0;
521 }
522 
523 static int
newline_past(struct ncplane * std,struct ncplane * p)524 newline_past(struct ncplane* std, struct ncplane* p){
525   if(ncplane_putchar_yx(std, ncplane_abs_y(p) + ncplane_dim_y(p) - 1,
526                         ncplane_abs_x(p) + ncplane_dim_x(p) + 1, '\n') != 1){
527     return -1;
528   }
529   return 0;
530 }
531 
532 static int
infoplane_notcurses(struct notcurses * nc,const fetched_info * fi,int planeheight,int nextline)533 infoplane_notcurses(struct notcurses* nc, const fetched_info* fi,
534                     int planeheight, int nextline){
535   const int planewidth = 72;
536   unsigned dimy;
537   struct ncplane* std = notcurses_stddim_yx(nc, &dimy, NULL);
538   struct ncplane_options nopts = {
539     .y = nextline,
540     .x = NCALIGN_CENTER,
541     .rows = planeheight,
542     .cols = planewidth,
543     .userptr = NULL,
544     .name = "info",
545     .flags = NCPLANE_OPTION_HORALIGNED,
546   };
547   struct ncplane* infop = ncplane_create(std, &nopts);
548   if(infop == NULL){
549     return -1;
550   }
551   ncplane_set_fg_rgb8(infop, 0xd0, 0xd0, 0xd0);
552   ncplane_set_styles(infop, NCSTYLE_UNDERLINE);
553   ncplane_printf_aligned(infop, 1, NCALIGN_LEFT, " %s %s", fi->kernel, fi->kernver);
554   if(fi->distro_pretty){
555     ncplane_printf_aligned(infop, 1, NCALIGN_RIGHT, "%s ", fi->distro_pretty);
556   }
557   ncplane_set_styles(infop, NCSTYLE_BOLD);
558 #if defined(__linux__)
559   struct sysinfo sinfo;
560   sysinfo(&sinfo);
561   char totalmet[NCBPREFIXSTRLEN + 1], usedmet[NCBPREFIXSTRLEN + 1];
562   ncbprefix(sinfo.totalram, 1, totalmet, 1);
563   ncbprefix(sinfo.totalram - sinfo.freeram, 1, usedmet, 1);
564   ncplane_printf_aligned(infop, 2, NCALIGN_RIGHT, "Processes: %hu ", sinfo.procs);
565   ncplane_printf_aligned(infop, 2, NCALIGN_LEFT, " RAM: %sB/%sB", usedmet, totalmet);
566 #elif defined(BSD)
567   uint64_t ram;
568   size_t oldlenp = sizeof(ram);
569   if(sysctlbyname("hw.memsize", &ram, &oldlenp, NULL, 0) == 0){
570     char tram[NCBPREFIXSTRLEN + 1];
571     ncbprefix(ram, 1, tram, 1);
572     ncplane_printf_aligned(infop, 2, NCALIGN_LEFT, " RAM: %sB", tram);
573   }
574 #endif
575   ncplane_printf_aligned(infop, 3, NCALIGN_LEFT, " DM: %s", fi->desktop ? fi->desktop : "n/a");
576   ncplane_printf_aligned(infop, 3, NCALIGN_RIGHT, "Shell: %s ", fi->shell ? fi->shell : "n/a");
577   if(notcurses_cantruecolor(nc)){
578     ncplane_printf_aligned(infop, 4, NCALIGN_LEFT, " RGB TERM: %s", fi->term);
579     nccell c = NCCELL_CHAR_INITIALIZER('R');
580     nccell_set_styles(&c, NCSTYLE_BOLD);
581     nccell_set_fg_rgb8(&c, 0xf0, 0xa0, 0xa0);
582     ncplane_putc_yx(infop, 4, 1, &c);
583     nccell_load_char(infop, &c, 'G');
584     nccell_set_fg_rgb8(&c, 0xa0, 0xf0, 0xa0);
585     ncplane_putc_yx(infop, 4, 2, &c);
586     nccell_load_char(infop, &c, 'B');
587     nccell_set_fg_rgb8(&c, 0xa0, 0xa0, 0xf0);
588     ncplane_putc_yx(infop, 4, 3, &c);
589     nccell_set_styles(&c, NCSTYLE_NONE);
590   }else{
591     ncplane_printf_aligned(infop, 4, NCALIGN_LEFT, " TERM: %s", fi->term);
592   }
593   ncplane_printf_aligned(infop, 4, NCALIGN_RIGHT, " LANG: %s ", fi->lang);
594   ncplane_set_styles(infop, NCSTYLE_ITALIC | NCSTYLE_BOLD);
595   ncplane_printf_aligned(infop, 5, NCALIGN_CENTER, "%s (%d cores)",
596                          fi->cpu_model ? fi->cpu_model : fallback_cpuinfo(),
597                          fi->core_count);
598   nccell ul = NCCELL_TRIVIAL_INITIALIZER, ur = NCCELL_TRIVIAL_INITIALIZER;
599   nccell ll = NCCELL_TRIVIAL_INITIALIZER, lr = NCCELL_TRIVIAL_INITIALIZER;
600   nccell hl = NCCELL_TRIVIAL_INITIALIZER, vl = NCCELL_TRIVIAL_INITIALIZER;
601   if(nccells_rounded_box(infop, 0, 0, &ul, &ur, &ll, &lr, &hl, &vl)){
602     return -1;
603   }
604   nccell_set_fg_rgb8(&ul, 0x90, 0x90, 0x90);
605   nccell_set_fg_rgb8(&ur, 0x90, 0x90, 0x90);
606   nccell_set_fg_rgb8(&ll, 0, 0, 0);
607   nccell_set_fg_rgb8(&lr, 0, 0, 0);
608   unsigned ctrlword = NCBOXGRAD_BOTTOM | NCBOXGRAD_LEFT | NCBOXGRAD_RIGHT;
609   if(ncplane_perimeter(infop, &ul, &ur, &ll, &lr, &hl, &vl, ctrlword)){
610     return -1;
611   }
612   ncplane_home(infop);
613   uint64_t channels = 0;
614   ncchannels_set_fg_rgb8(&channels, 0, 0xff, 0);
615   ncplane_hline_interp(infop, &hl, planewidth / 2, ul.channels, channels);
616   ncplane_hline_interp(infop, &hl, planewidth / 2, channels, ur.channels);
617   nccell_release(infop, &ul); nccell_release(infop, &ur);
618   nccell_release(infop, &ll); nccell_release(infop, &lr);
619   nccell_release(infop, &hl); nccell_release(infop, &vl);
620   ncplane_set_fg_rgb8(infop, 0xff, 0xff, 0xff);
621   ncplane_set_styles(infop, NCSTYLE_BOLD);
622   if(ncplane_printf_aligned(infop, 0, NCALIGN_CENTER, "[ %s@%s ]",
623                             fi->username, fi->hostname) < 0){
624     return -1;
625   }
626   ncchannels_set_fg_rgb8(&channels, 0, 0, 0);
627   ncchannels_set_bg_rgb8(&channels, 0x50, 0x50, 0x50);
628   ncplane_set_base(infop, " ", 0, channels);
629   ncplane_scrollup_child(std, infop);
630   int parend = ncplane_abs_y(std) + ncplane_dim_y(std) - 1; // where parent ends
631   int chend = ncplane_abs_y(infop) + ncplane_dim_y(infop) - 1; // where child ends
632   if(chend > parend){
633     ncplane_move_rel(infop, -(chend - parend), 0);
634   }
635   newline_past(std, infop);
636   if(notcurses_render(nc)){
637     return -1;
638   }
639   return 0;
640 }
641 
642 static int
infoplane(struct notcurses * nc,const fetched_info * fi,int nextline)643 infoplane(struct notcurses* nc, const fetched_info* fi, int nextline){
644   const int planeheight = 7;
645   int r = infoplane_notcurses(nc, fi, planeheight, nextline);
646   return r;
647 }
648 
649 struct marshal {
650   int nextline; // line following display plane, where info plane ought be placed
651   struct notcurses* nc;
652   const distro_info* dinfo;
653   const char* logo; // read from /etc/os-release (or builtin), may be NULL
654   const char* neologo; // fallback from neofetch, text with color sub templates
655 };
656 
657 // present a neofetch-style logo. we want to substitute colors for ${cN} inline
658 // sequences, and center the logo.
659 static int
neologo_present(struct notcurses * nc,const char * nlogo)660 neologo_present(struct notcurses* nc, const char* nlogo){
661   // find the maximum line length in columns by iterating over the logo
662   size_t maxlinelen = 0;
663   size_t linelen; // length in bytes, including newline
664   char** lines = NULL;
665   int linecount = 0;
666   for(const char* cur = nlogo ; *cur ; cur += linelen + 1){
667     const char* nl = strchr(cur, '\n');
668     if(nl){
669       linelen = nl - cur;
670     }else{
671       linelen = strlen(cur);
672     }
673     char** tmpl;
674     if((tmpl = realloc(lines, sizeof(*lines) * (linecount + 1))) == NULL){
675       free(lines);
676       return -1;
677     }
678     lines = tmpl;
679     lines[linecount++] = strndup(cur, linelen);
680     if(nl){ // chomp any newline
681       lines[linecount - 1][linelen] = '\0';
682     }
683     size_t collen = ncstrwidth(lines[linecount - 1], NULL, NULL);
684     if(collen > maxlinelen){
685       maxlinelen = collen;
686     }
687   }
688   unsigned dimy, dimx;
689   struct ncplane* n = notcurses_stddim_yx(nc, &dimy, &dimx);
690   const int leftpad = (dimx - maxlinelen) / 2;
691   for(int i = 0 ; i < linecount ; ++i){
692     int cols = ncplane_printf(n, "%*.*s%s", leftpad, leftpad, "", lines[i]);
693     if(cols >= 0 && (unsigned)cols < dimx){
694 	    ncplane_printf(n, "%*.*s", dimx - cols, dimx - cols, "");
695     }
696     free(lines[i]);
697   }
698   free(lines);
699   ncplane_set_fg_default(n);
700   ncplane_set_styles(n, NCSTYLE_BOLD | NCSTYLE_ITALIC);
701   if(notcurses_canopen_images(nc)){
702     ncplane_putstr_aligned(n, -1, NCALIGN_CENTER, "(no image file is known for your distro)\n");
703   }else{
704     ncplane_putstr_aligned(n, -1, NCALIGN_CENTER, "(notcurses was compiled without image support)\n");
705   }
706   ncplane_off_styles(n, NCSTYLE_BOLD | NCSTYLE_ITALIC);
707   return 0;
708 }
709 
710 static void*
display_thread(void * vmarshal)711 display_thread(void* vmarshal){
712   struct marshal* m = vmarshal;
713   drawpalette(m->nc);
714   notcurses_render(m->nc);
715   ncplane_set_bg_default(notcurses_stdplane(m->nc));
716   ncplane_set_fg_default(notcurses_stdplane(m->nc));
717   // we've just rendered, so any necessary scrolling has been performed. draw
718   // our image wherever the palette ended, and then scroll as necessary to
719   // make that new plane visible.
720   if(notcurses_canopen_images(m->nc)){
721     struct ncvisual* ncv = NULL;
722     if(m->logo){
723       ncv = ncvisual_from_file(m->logo);
724     }else if(m->dinfo && m->dinfo->logofile){
725       ncv = ncvisual_from_file(m->dinfo->logofile);
726     }
727     if(ncv){
728       unsigned y;
729       ncplane_cursor_yx(notcurses_stdplane_const(m->nc), &y, NULL);
730       bool pixeling = false;
731       if(notcurses_check_pixel_support(m->nc) >= 1){
732         pixeling = true;
733       }
734       struct ncvisual_options vopts = {
735         .n = notcurses_stdplane(m->nc),
736         .y = y,
737         .x = NCALIGN_CENTER,
738         .blitter = pixeling ? NCBLIT_PIXEL : NCBLIT_3x2,
739         .scaling = pixeling ? NCSCALE_NONE : NCSCALE_SCALE_HIRES,
740         .flags = NCVISUAL_OPTION_HORALIGNED | NCVISUAL_OPTION_CHILDPLANE,
741       };
742       struct ncplane* iplane = ncvisual_blit(m->nc, ncv, &vopts);
743       ncvisual_destroy(ncv);
744       if(iplane){
745         ncplane_scrollup_child(notcurses_stdplane(m->nc), iplane);
746         newline_past(notcurses_stdplane(m->nc), iplane);
747         notcurses_render(m->nc);
748         m->nextline = ncplane_y(iplane) + ncplane_dim_y(iplane);
749         return NULL;
750       }
751     }
752   }
753   if(m->neologo){
754     if(neologo_present(m->nc, m->neologo) == 0){
755       unsigned nl;
756       ncplane_cursor_yx(notcurses_stdplane(m->nc), &nl, NULL);
757       m->nextline = nl;
758       return NULL;
759     }
760   }
761   return NULL;
762 }
763 
764 static int
ncneofetch(struct notcurses * nc)765 ncneofetch(struct notcurses* nc){
766   fetched_info fi = {};
767   ncneo_kernel_e kern = get_kernel(&fi);
768   switch(kern){
769     case NCNEO_LINUX:
770       fi.distro = linux_ncneofetch(&fi);
771       break;
772     case NCNEO_FREEBSD:
773       fi.distro = freebsd_ncneofetch(&fi);
774       break;
775     case NCNEO_DRAGONFLY:
776       fi.distro = dragonfly_ncneofetch(&fi);
777       break;
778     case NCNEO_XNU:
779       fi.distro = xnu_ncneofetch(&fi);
780       break;
781     case NCNEO_WINDOWS:
782       fi.distro = windows_ncneofetch(&fi);
783       break;
784     case NCNEO_UNKNOWN:
785       break;
786   }
787   // go ahead and spin the image load + render into its own thread while the
788   // rest of the fetching continues. cuts total runtime.
789   struct marshal display_marshal = {
790     .nc = nc,
791     .dinfo = fi.distro,
792     .logo = fi.logo,
793     .neologo = fi.neologo,
794     .nextline = -1,
795   };
796   pthread_t tid;
797   const bool launched = !pthread_create(&tid, NULL, display_thread, &display_marshal);
798   fi.hostname = notcurses_hostname();
799   fi.username = notcurses_accountname();
800   fetch_env_vars(nc, &fi);
801   if(kern == NCNEO_LINUX){
802     fetch_cpu_info(&fi);
803   }else if(kern == NCNEO_WINDOWS){
804     fetch_windows_cpuinfo(&fi);
805   }else{
806     fetch_bsd_cpuinfo(&fi);
807   }
808   if(launched){
809     pthread_join(tid, NULL);
810   }
811   assert(display_marshal.nextline >= 0);
812   if(infoplane(nc, &fi, display_marshal.nextline)){
813     free_fetched_info(&fi);
814     return -1;
815   }
816   free_fetched_info(&fi);
817   return notcurses_stop(nc);
818 }
819 
820 static void
usage(const char * arg0,FILE * fp)821 usage(const char* arg0, FILE* fp){
822   fprintf(fp, "usage: %s [ -v ]\n", arg0);
823   if(fp == stderr){
824     exit(EXIT_FAILURE);
825   }
826   exit(EXIT_SUCCESS);
827 }
828 
main(int argc,char ** argv)829 int main(int argc, char** argv){
830   struct notcurses_options opts = {
831     .flags = NCOPTION_SUPPRESS_BANNERS
832              | NCOPTION_NO_ALTERNATE_SCREEN
833              | NCOPTION_NO_CLEAR_BITMAPS
834              | NCOPTION_PRESERVE_CURSOR
835              | NCOPTION_DRAIN_INPUT,
836   };
837   if(argc > 2){
838     usage(argv[0], stderr);
839   }else if(argc == 2){
840     if(strcmp(argv[1], "-v") == 0){
841       opts.loglevel = NCLOGLEVEL_TRACE;
842     }else{
843       usage(argv[0], stderr);
844     }
845   }
846   struct notcurses* nc = notcurses_init(&opts, NULL);
847   if(nc == NULL){
848     return EXIT_FAILURE;
849   }
850   struct ncplane* stdn = notcurses_stdplane(nc);
851   ncplane_set_scrolling(stdn, true);
852   int r = ncneofetch(nc);
853   return r ? EXIT_FAILURE : EXIT_SUCCESS;
854 }
855