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