You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
notcurses/src/fetch/main.c

862 lines
28 KiB
C

#include <errno.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <locale.h>
#include <assert.h>
#include <strings.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/param.h>
#include <sys/types.h>
#if defined(__linux__) || defined(__gnu_hurd__)
#include <langinfo.h>
#include <sys/utsname.h>
#include <sys/sysinfo.h>
#elif !defined(__MINGW32__)
#include <langinfo.h>
#include <sys/sysctl.h>
#include <sys/utsname.h>
#else
#include <sysinfoapi.h>
#endif
#include <notcurses/notcurses.h>
#include "compat/compat.h"
#include "builddef.h"
#include "ncart.h"
static inline char*
find_data(const char* datum){
char* datadir = notcurses_data_dir();
if(datadir == NULL){
return NULL;
}
const size_t dlen = strlen(datadir);
char* path = malloc(dlen + 1 + strlen(datum) + 1);
if(path == NULL){
free(datadir);
return NULL;
}
strcpy(path, datadir);
path[dlen] = path_separator();
strcpy(path + dlen + 1, datum);
free(datadir);
return path;
}
typedef struct distro_info {
const char* name; // must match 'lsb_release -i'
const char* logofile; // kept at original aspect ratio, lain atop bg
} distro_info;
typedef struct fetched_info {
char* username; // we borrow a reference
char* hostname;
const distro_info* distro;
char* logo; // strdup() from /etc/os-release
char* distro_pretty; // strdup() from /etc/os-release
char* kernel; // strdup(uname(2)->name)
char* kernver; // strdup(uname(2)->version);
char* desktop; // getenv("XDG_CURRENT_DESKTOP")
const char* shell; // getenv("SHELL")
char* term; // notcurses_detected_terminal(), heap-alloced
char* lang; // getenv("LANG")
char* cpu_model; // FIXME don't handle hetero setups yet
int core_count;
// if there is no other logo found, fall back to a logo filched from neofetch
const char* neologo; // text with color substitution templates
} fetched_info;
static void
free_fetched_info(fetched_info* fi){
free(fi->cpu_model);
free(fi->hostname);
free(fi->username);
free(fi->kernel);
free(fi->kernver);
free(fi->distro_pretty);
free(fi->term);
}
static int
fetch_env_vars(struct notcurses* nc, fetched_info* fi){
#if defined(__APPLE__)
fi->desktop = "Aqua";
#elif defined(__MINGW32__)
fi->desktop = "Metro";
#else
fi->desktop = getenv("XDG_CURRENT_DESKTOP");
#endif
fi->shell = getenv("SHELL");
fi->term = notcurses_detected_terminal(nc);
fi->lang = getenv("LANG");
if(fi->lang == NULL){
fi->lang = nl_langinfo(CODESET);
}
return 0;
}
// if nothing else is available, use a generic architecture based on compile
static const char*
fallback_cpuinfo(void){
#if defined(__amd64__) || defined(_M_AMD64)
return "amd64";
#elif defined(__aarch64__)
return "aarch64";
#elif defined(__arm__)
return "arm";
#elif defined(__i386__) || defined(_M_IX86)
return "i386";
#elif defined(__ia64__)
return "ia64";
#else
#warning "unknown target architecture"
return "unknown";
#endif
}
static int
fetch_bsd_cpuinfo(fetched_info* fi){
#if defined(__linux__) || defined(__gnu_hurd__) || defined(__MINGW32__)
(void)fi;
#else
size_t len = sizeof(fi->core_count);
int mib[2] = { CTL_HW, HW_NCPU };
if(sysctl(mib, sizeof(mib) / sizeof(*mib), &fi->core_count, &len, NULL, 0)){
fprintf(stderr, "Coudln't acquire CTL_HW+HW_NCPU sysctl (%s)\n", strerror(errno));
return -1;
}
mib[1] = HW_MODEL;
size_t modellen = 80; // FIXME?
fi->cpu_model = malloc(modellen);
if(sysctl(mib, sizeof(mib) / sizeof(*mib), fi->cpu_model, &modellen, NULL, 0)){
fprintf(stderr, "Coudln't acquire CTL_HW+HW_MODEL sysctl (%s)\n", strerror(errno));
return -1;
}
#endif
return 0;
}
static int
fetch_windows_cpuinfo(fetched_info* fi){
#ifdef __MINGW32__
SYSTEM_INFO info = {0};
GetSystemInfo(&info);
switch(info.wProcessorArchitecture){
case PROCESSOR_ARCHITECTURE_AMD64:
fi->cpu_model = strdup("amd64"); break;
case PROCESSOR_ARCHITECTURE_ARM:
fi->cpu_model = strdup("ARM"); break;
case PROCESSOR_ARCHITECTURE_ARM64:
fi->cpu_model = strdup("AArch64"); break;
case PROCESSOR_ARCHITECTURE_IA64:
fi->cpu_model = strdup("Itanium"); break;
case PROCESSOR_ARCHITECTURE_INTEL:
fi->cpu_model = strdup("i386"); break;
default:
fi->cpu_model = strdup("Unknown processor"); break;
}
fi->core_count = info.dwNumberOfProcessors;
#else
(void)fi;
#endif
return 0;
}
// guess what? the form of /proc/cpuinfo is arch-dependent!
// that's right, fuck you!
static int
fetch_cpu_info(fetched_info* fi){
FILE* cpuinfo = fopen("/proc/cpuinfo", "re");
if(cpuinfo == NULL){
fprintf(stderr, "Error opening /proc/cpuinfo (%s)\n", strerror(errno));
return -1;
}
char buf[BUFSIZ];
while(fgets(buf, sizeof(buf), cpuinfo)){
// works for both amd64 and ARM
#define CORE "processor"
// model name doesn't appear on all architectures, so fall back to vendor_id
#define TAG "model name"
#define VEND "vendor_id"
if(strncmp(buf, TAG, strlen(TAG)) == 0){
// model name trumps vendor_id
char* start = strchr(buf + strlen(TAG), ':');
if(start){
++start;
char* nl = strchr(start, '\n');
*nl = '\0';
free(fi->cpu_model);
fi->cpu_model = strdup(start);
}
}else if(strncmp(buf, VEND, strlen(VEND)) == 0){
// vendor_id ought only be used in the absence of model name
if(fi->cpu_model == NULL){
char* start = strchr(buf + strlen(VEND), ':');
if(start){
++start;
char* nl = strchr(start, '\n');
*nl = '\0';
fi->cpu_model = strdup(start);
}
}
// need strncasecmp() because ARM cpuinfo uses "Processor" ugh
}else if(strncasecmp(buf, CORE, strlen(CORE)) == 0){
++fi->core_count;
}
#undef VEND
#undef TAG
#undef CORE
}
return 0;
}
#ifdef __linux__
// Given a filename, check for its existence in the directories specified by
// https://specifications.freedesktop.org/icon-theme-spec/latest/ar01s03.html.
// Returns NULL if no such file can be found. Return value is heap-allocated.
static char *
get_xdg_logo(const char *spec){
const char* logopath = "/usr/share/pixmaps/";
int dfd = open(logopath, O_CLOEXEC | O_DIRECTORY);
if(dfd < 0){
return NULL;
}
int r = faccessat(dfd, spec, R_OK, 0);
close(dfd);
if(r){
return NULL;
}
char* p = malloc(strlen(spec) + strlen(logopath) + 1);
strcpy(p, logopath);
strcat(p, spec);
return p;
}
#endif
// FIXME deal more forgivingly with quotation marks
static const distro_info*
linux_ncneofetch(fetched_info* fi){
const distro_info* dinfo = NULL;
#ifdef __linux__
static const distro_info distros[] = {
{
.name = "arch",
// from core/filesystem
.logofile = "/usr/share/pixmaps/archlinux-logo.png",
}, {
.name = "artix",
// from system/filesystem
.logofile = "/usr/share/pixmaps/artixlinux-logo.png",
}, {
.name = "debian",
// from desktop-base package
.logofile = "/usr/share/desktop-base/debian-logos/logo-text-256.png",
}, {
.name = "fedora",
// from redhat-lsb-core package
.logofile = "/usr/share/pixmaps/fedora-logo.png",
}, {
.name = "ubuntu",
// package source? FIXME
.logofile = "/usr/share/unity/icons/launcher_bfb.png",
}, {
.name = NULL,
.logofile = NULL,
},
};
FILE* osinfo = fopen("/etc/os-release", "re");
if(osinfo == NULL){
return NULL;
}
char buf[BUFSIZ];
char* distro = NULL;
while(fgets(buf, sizeof(buf), osinfo)){
#define PRETTY "PRETTY_NAME=\""
#define ID "ID=" // no quotes on this one
#define LOGOQ "LOGO=\""
#define LOGO "LOGO=" // handle LOGO sans quotes
if(strncmp(buf, ID, strlen(ID)) == 0){
char* nl = strchr(buf + strlen(ID), '\n');
if(nl){
*nl = '\0';
distro = strdup(buf + strlen(ID));
}
}else if(!fi->distro_pretty && strncmp(buf, PRETTY, strlen(PRETTY)) == 0){
char* nl = strchr(buf + strlen(PRETTY), '"');
if(nl){
*nl = '\0';
fi->distro_pretty = strdup(buf + strlen(PRETTY));
}
}else if(!fi->logo && strncmp(buf, LOGOQ, strlen(LOGOQ)) == 0){
char* nl = strchr(buf + strlen(LOGOQ), '"');
if(nl){
*nl = '\0';
fi->logo = get_xdg_logo(buf + strlen(LOGOQ));
}
}else if(!fi->logo && strncmp(buf, LOGO, strlen(LOGO)) == 0){
char* nl = strchr(buf + strlen(LOGO), '\n');
if(nl){
*nl = '\0';
fi->logo = get_xdg_logo(buf + strlen(LOGO));
}
}
}
#undef LOGO
#undef LOGOQ
#undef ID
#undef PRETTY
fclose(osinfo);
if(distro == NULL){
return NULL;
}
for(dinfo = distros ; dinfo->name ; ++dinfo){
if(strcmp(dinfo->name, distro) == 0){
break;
}
}
if(fi->logo == NULL){
fi->neologo = get_neofetch_art(distro);
}
free(distro);
#else
(void)fi;
#endif
return dinfo;
}
typedef enum {
NCNEO_LINUX,
NCNEO_FREEBSD,
NCNEO_DRAGONFLY,
NCNEO_XNU,
NCNEO_WINDOWS,
NCNEO_UNKNOWN,
} ncneo_kernel_e;
static ncneo_kernel_e
get_kernel(fetched_info* fi){
#ifndef __MINGW32__
struct utsname uts;
if(uname(&uts)){
fprintf(stderr, "Failure invoking uname (%s)\n", strerror(errno));
return -1;
}
fi->kernel = strdup(uts.sysname);
fi->kernver = strdup(uts.release);
if(strcmp(uts.sysname, "Linux") == 0){
return NCNEO_LINUX;
}else if(strcmp(uts.sysname, "FreeBSD") == 0){
return NCNEO_FREEBSD;
}else if(strcmp(uts.sysname, "DragonFly") == 0){
return NCNEO_DRAGONFLY;
}else if(strcmp(uts.sysname, "Darwin") == 0){
return NCNEO_XNU;
}
fprintf(stderr, "Unknown operating system via uname: %s\n", uts.sysname);
#else
OSVERSIONINFOEX osvi;
ZeroMemory(&osvi, sizeof(osvi));
osvi.dwOSVersionInfoSize = sizeof(osvi);
GetVersionExA((LPOSVERSIONINFOA)&osvi);
char ver[20]; // sure why not
snprintf(ver, sizeof(ver), "%lu.%lu", osvi.dwMajorVersion, osvi.dwMinorVersion);
fi->kernver = strdup(ver);
fi->kernel = strdup("WNT");
DWORD ptype;
if(GetProductInfo(osvi.dwMajorVersion,
osvi.dwMinorVersion,
osvi.wServicePackMajor,
osvi.wServicePackMinor,
&ptype)){
switch(ptype){
case PRODUCT_BUSINESS: fi->distro_pretty = strdup("Business"); break;
case PRODUCT_BUSINESS_N: fi->distro_pretty = strdup("Business N"); break;
case PRODUCT_CLUSTER_SERVER: fi->distro_pretty = strdup("HPC Edition"); break;
case PRODUCT_CLUSTER_SERVER_V: fi->distro_pretty = strdup("Server Hyper Core V"); break;
case PRODUCT_CORE: fi->distro_pretty = strdup("Windows 10 Home"); break;
case PRODUCT_CORE_COUNTRYSPECIFIC: fi->distro_pretty = strdup("Windows 10 Home China"); break;
case PRODUCT_CORE_N: fi->distro_pretty = strdup("Windows 10 Home N"); break;
case PRODUCT_CORE_SINGLELANGUAGE: fi->distro_pretty = strdup("Windows 10 Home Single Language"); break;
case PRODUCT_EDUCATION: fi->distro_pretty = strdup("Windows 10 Education"); break;
case PRODUCT_EDUCATION_N: fi->distro_pretty = strdup("Windows 10 Education N"); break;
case PRODUCT_ENTERPRISE: fi->distro_pretty = strdup("Windows 10 Enterprise"); break;
case PRODUCT_ENTERPRISE_EVALUATION: fi->distro_pretty = strdup("Windows 10 Enterprise Eval"); break;
case PRODUCT_ENTERPRISE_E: fi->distro_pretty = strdup("Windows 10 Enterprise E"); break;
case PRODUCT_ENTERPRISE_N: fi->distro_pretty = strdup("Windows 10 Enterprise N"); break;
case PRODUCT_ENTERPRISE_N_EVALUATION: fi->distro_pretty = strdup("Windows 10 Enterprise N Eval"); break;
case PRODUCT_ENTERPRISE_S: fi->distro_pretty = strdup("Windows 10 Enterprise 2015 LTSB"); break;
case PRODUCT_ENTERPRISE_S_EVALUATION: fi->distro_pretty = strdup("Windows 10 Enterprise 2015 LTSB Eval"); break;
case PRODUCT_ENTERPRISE_S_N: fi->distro_pretty = strdup("Windows 10 Enterprise 2015 LTSB N"); break;
case PRODUCT_ENTERPRISE_S_N_EVALUATION: fi->distro_pretty = strdup("Windows 10 Enterprise 2015 LTSB N Eval"); break;
case PRODUCT_HOME_BASIC: fi->distro_pretty = strdup("Home Basic"); break;
case PRODUCT_HOME_BASIC_N: fi->distro_pretty = strdup("Home Basic N"); break;
case PRODUCT_HOME_PREMIUM: fi->distro_pretty = strdup("Home Premium"); break;
case PRODUCT_HOME_PREMIUM_N: fi->distro_pretty = strdup("Home Premium N"); break;
case PRODUCT_HOME_PREMIUM_SERVER: fi->distro_pretty = strdup("Windows Home Server 2011"); break;
case PRODUCT_HOME_SERVER: fi->distro_pretty = strdup("Windows Storage Server 2008 R2 Essentials"); break;
case PRODUCT_HYPERV: fi->distro_pretty = strdup("Windows Hyper-V Server"); break;
case PRODUCT_IOTUAP: fi->distro_pretty = strdup("Windows 10 IoT Core"); break;
/*case PRODUCT_IOTUAPCOMMERCIAL: fi->distro_pretty = strdup("Windows 10 IoT Core Commercial"); break;
case PRODUCT_PRO_WORKSTATION: fi->distro_pretty = strdup("Windows 10 Pro for Workstations"); break;
case PRODUCT_PRO_WORKSTATION_N: fi->distro_pretty = strdup("Windows 10 Pro for Workstations N"); break;*/
case PRODUCT_PROFESSIONAL: fi->distro_pretty = strdup("Windows 10 Pro"); break;
case PRODUCT_PROFESSIONAL_N: fi->distro_pretty = strdup("Windows 10 Pro N"); break;
case PRODUCT_PROFESSIONAL_WMC: fi->distro_pretty = strdup("Professional with Media Center"); break;
case PRODUCT_ULTIMATE: fi->distro_pretty = strdup("Ultimate"); break;
case PRODUCT_ULTIMATE_N: fi->distro_pretty = strdup("Ultimate N"); break;
default: fi->distro_pretty = strdup("Unknown product"); break;
}
}
return NCNEO_WINDOWS;
#endif
return NCNEO_UNKNOWN;
}
// windows distro_pretty gets handled in get_kernel() above
static const distro_info*
windows_ncneofetch(fetched_info* fi){
static distro_info mswin = {
.name = "Windows",
};
mswin.logofile = find_data("Windows10Logo.png"),
fi->neologo = get_neofetch_art("Windows");
return &mswin;
}
static const distro_info*
freebsd_ncneofetch(fetched_info* fi){
static distro_info fbsd = {
.name = "FreeBSD",
};
fbsd.logofile = find_data("freebsd.png"),
fi->neologo = get_neofetch_art("BSD"); // use big daemon logo
fi->distro_pretty = NULL;
return &fbsd;
}
static const distro_info*
dragonfly_ncneofetch(fetched_info* fi){
static distro_info fbsd = {
.name = "DragonFly BSD",
.logofile = NULL, // FIXME
};
fi->neologo = get_neofetch_art("dragonfly");
fi->distro_pretty = NULL;
return &fbsd;
}
static const distro_info*
xnu_ncneofetch(fetched_info* fi){
static distro_info xnu = {
.name = "OS X",
.logofile = "/System/Library/PrivateFrameworks/LoginUIKit.framework/Versions/A/Frameworks/LoginUICore.framework/Versions/A/Resources/apple@2x.png",
};
fi->neologo = get_neofetch_art("Darwin");
fi->distro_pretty = notcurses_osversion();
return &xnu;
}
static int
drawpalette(struct notcurses* nc){
int showpl = 64; // show this many per line
int psize = notcurses_palette_size(nc);
if(psize > NCPALETTESIZE){
psize = NCPALETTESIZE;
}
unsigned dimy, dimx;
struct ncplane* n = notcurses_stddim_yx(nc, &dimy, &dimx);
if(dimx < 64){
return -1;
}
int scale = notcurses_canutf8(nc) ? 2 : 1; // use half blocks for 2*showpl
ncplane_cursor_move_yx(n, -1, 0);
for(int y = 0 ; y < (psize + (showpl * scale) - 1) / showpl / scale ; ++y){
// we show a maximum of showpl * scale palette entries per line
int toshow = psize - y * showpl;
if(toshow > showpl){
toshow = showpl;
}
if(ncplane_cursor_move_yx(n, -1, 0)){
return -1;
}
ncplane_set_fg_default(n);
ncplane_set_bg_default(n);
for(unsigned x = 0 ; x < (dimx - toshow) / 2 ; ++x){
if(ncplane_putchar(n, ' ') < 0){
return -1;
}
}
// center based on the number being shown on this line
for(unsigned x = (dimx - toshow) / 2 ; x < dimx / 2 + 32 ; ++x){
const int truex = x - (dimx - toshow) / 2;
if(y * showpl * scale + truex >= psize){
break;
}
if(ncplane_set_bg_palindex(n, y * showpl * scale + truex)){
return -1;
}
if(scale == 2){
if(ncplane_set_fg_palindex(n, (y * showpl * scale + truex + showpl) % psize)){
return -1;
}
if(ncplane_putegc(n, u8"", NULL) == EOF){
return -1;
}
}else{
if(ncplane_putchar(n, ' ') == EOF){
return -1;
}
}
}
ncplane_set_fg_default(n);
ncplane_set_bg_default(n);
for(unsigned x = dimx / 2 + 32 ; x < dimx - 1 ; ++x){
if(ncplane_putchar(n, ' ') < 0){
return -1;
}
}
if(ncplane_putchar(n, '\n') == EOF){
return -1;
}
}
return 0;
}
static int
newline_past(struct ncplane* std, struct ncplane* p){
if(ncplane_putchar_yx(std, ncplane_abs_y(p) + ncplane_dim_y(p) - 1,
ncplane_abs_x(p) + ncplane_dim_x(p) + 1, '\n') != 1){
return -1;
}
return 0;
}
static int
infoplane_notcurses(struct notcurses* nc, const fetched_info* fi,
int planeheight, int nextline){
const int planewidth = 72;
unsigned dimy;
struct ncplane* std = notcurses_stddim_yx(nc, &dimy, NULL);
struct ncplane_options nopts = {
.y = nextline,
.x = NCALIGN_CENTER,
.rows = planeheight,
.cols = planewidth,
.userptr = NULL,
.name = "info",
.flags = NCPLANE_OPTION_HORALIGNED,
};
struct ncplane* infop = ncplane_create(std, &nopts);
if(infop == NULL){
return -1;
}
ncplane_set_fg_rgb8(infop, 0xd0, 0xd0, 0xd0);
ncplane_set_styles(infop, NCSTYLE_UNDERLINE);
ncplane_printf_aligned(infop, 1, NCALIGN_LEFT, " %s %s", fi->kernel, fi->kernver);
if(fi->distro_pretty){
ncplane_printf_aligned(infop, 1, NCALIGN_RIGHT, "%s ", fi->distro_pretty);
}
ncplane_set_styles(infop, NCSTYLE_BOLD);
#if defined(__linux__)
struct sysinfo sinfo;
sysinfo(&sinfo);
char totalmet[NCBPREFIXSTRLEN + 1], usedmet[NCBPREFIXSTRLEN + 1];
ncbprefix(sinfo.totalram, 1, totalmet, 1);
ncbprefix(sinfo.totalram - sinfo.freeram, 1, usedmet, 1);
ncplane_printf_aligned(infop, 2, NCALIGN_RIGHT, "Processes: %hu ", sinfo.procs);
ncplane_printf_aligned(infop, 2, NCALIGN_LEFT, " RAM: %sB/%sB", usedmet, totalmet);
#elif defined(BSD)
uint64_t ram;
size_t oldlenp = sizeof(ram);
if(sysctlbyname("hw.memsize", &ram, &oldlenp, NULL, 0) == 0){
char tram[NCBPREFIXSTRLEN + 1];
ncbprefix(ram, 1, tram, 1);
ncplane_printf_aligned(infop, 2, NCALIGN_LEFT, " RAM: %sB", tram);
}
#endif
ncplane_printf_aligned(infop, 3, NCALIGN_LEFT, " DM: %s", fi->desktop ? fi->desktop : "n/a");
ncplane_printf_aligned(infop, 3, NCALIGN_RIGHT, "Shell: %s ", fi->shell ? fi->shell : "n/a");
if(notcurses_cantruecolor(nc)){
ncplane_printf_aligned(infop, 4, NCALIGN_LEFT, " RGB TERM: %s", fi->term);
nccell c = NCCELL_CHAR_INITIALIZER('R');
nccell_set_styles(&c, NCSTYLE_BOLD);
nccell_set_fg_rgb8(&c, 0xf0, 0xa0, 0xa0);
ncplane_putc_yx(infop, 4, 1, &c);
nccell_load_char(infop, &c, 'G');
nccell_set_fg_rgb8(&c, 0xa0, 0xf0, 0xa0);
ncplane_putc_yx(infop, 4, 2, &c);
nccell_load_char(infop, &c, 'B');
nccell_set_fg_rgb8(&c, 0xa0, 0xa0, 0xf0);
ncplane_putc_yx(infop, 4, 3, &c);
nccell_set_styles(&c, NCSTYLE_NONE);
}else{
ncplane_printf_aligned(infop, 4, NCALIGN_LEFT, " TERM: %s", fi->term);
}
ncplane_printf_aligned(infop, 4, NCALIGN_RIGHT, " LANG: %s ", fi->lang);
ncplane_set_styles(infop, NCSTYLE_ITALIC | NCSTYLE_BOLD);
ncplane_printf_aligned(infop, 5, NCALIGN_CENTER, "%s (%d cores)",
fi->cpu_model ? fi->cpu_model : fallback_cpuinfo(),
fi->core_count);
nccell ul = NCCELL_TRIVIAL_INITIALIZER, ur = NCCELL_TRIVIAL_INITIALIZER;
nccell ll = NCCELL_TRIVIAL_INITIALIZER, lr = NCCELL_TRIVIAL_INITIALIZER;
nccell hl = NCCELL_TRIVIAL_INITIALIZER, vl = NCCELL_TRIVIAL_INITIALIZER;
if(nccells_rounded_box(infop, 0, 0, &ul, &ur, &ll, &lr, &hl, &vl)){
return -1;
}
nccell_set_fg_rgb8(&ul, 0x90, 0x90, 0x90);
nccell_set_fg_rgb8(&ur, 0x90, 0x90, 0x90);
nccell_set_fg_rgb8(&ll, 0, 0, 0);
nccell_set_fg_rgb8(&lr, 0, 0, 0);
unsigned ctrlword = NCBOXGRAD_BOTTOM | NCBOXGRAD_LEFT | NCBOXGRAD_RIGHT;
if(ncplane_perimeter(infop, &ul, &ur, &ll, &lr, &hl, &vl, ctrlword)){
return -1;
}
ncplane_home(infop);
uint64_t channels = 0;
ncchannels_set_fg_rgb8(&channels, 0, 0xff, 0);
ncplane_hline_interp(infop, &hl, planewidth / 2, ul.channels, channels);
ncplane_hline_interp(infop, &hl, planewidth / 2, channels, ur.channels);
nccell_release(infop, &ul); nccell_release(infop, &ur);
nccell_release(infop, &ll); nccell_release(infop, &lr);
nccell_release(infop, &hl); nccell_release(infop, &vl);
ncplane_set_fg_rgb8(infop, 0xff, 0xff, 0xff);
ncplane_set_styles(infop, NCSTYLE_BOLD);
if(ncplane_printf_aligned(infop, 0, NCALIGN_CENTER, "[ %s@%s ]",
fi->username, fi->hostname) < 0){
return -1;
}
ncchannels_set_fg_rgb8(&channels, 0, 0, 0);
ncchannels_set_bg_rgb8(&channels, 0x50, 0x50, 0x50);
ncplane_set_base(infop, " ", 0, channels);
ncplane_scrollup_child(std, infop);
int parend = ncplane_abs_y(std) + ncplane_dim_y(std) - 1; // where parent ends
int chend = ncplane_abs_y(infop) + ncplane_dim_y(infop) - 1; // where child ends
if(chend > parend){
ncplane_move_rel(infop, -(chend - parend), 0);
}
newline_past(std, infop);
if(notcurses_render(nc)){
return -1;
}
return 0;
}
static int
infoplane(struct notcurses* nc, const fetched_info* fi, int nextline){
const int planeheight = 7;
int r = infoplane_notcurses(nc, fi, planeheight, nextline);
return r;
}
struct marshal {
int nextline; // line following display plane, where info plane ought be placed
struct notcurses* nc;
const distro_info* dinfo;
const char* logo; // read from /etc/os-release (or builtin), may be NULL
const char* neologo; // fallback from neofetch, text with color sub templates
};
// present a neofetch-style logo. we want to substitute colors for ${cN} inline
// sequences, and center the logo.
static int
neologo_present(struct notcurses* nc, const char* nlogo){
// find the maximum line length in columns by iterating over the logo
size_t maxlinelen = 0;
size_t linelen; // length in bytes, including newline
char** lines = NULL;
int linecount = 0;
for(const char* cur = nlogo ; *cur ; cur += linelen + 1){
const char* nl = strchr(cur, '\n');
if(nl){
linelen = nl - cur;
}else{
linelen = strlen(cur);
}
char** tmpl;
if((tmpl = realloc(lines, sizeof(*lines) * (linecount + 1))) == NULL){
free(lines);
return -1;
}
lines = tmpl;
lines[linecount++] = strndup(cur, linelen);
if(nl){ // chomp any newline
lines[linecount - 1][linelen] = '\0';
}
size_t collen = ncstrwidth(lines[linecount - 1], NULL, NULL);
if(collen > maxlinelen){
maxlinelen = collen;
}
}
unsigned dimy, dimx;
struct ncplane* n = notcurses_stddim_yx(nc, &dimy, &dimx);
const int leftpad = (dimx - maxlinelen) / 2;
for(int i = 0 ; i < linecount ; ++i){
int cols = ncplane_printf(n, "%*.*s%s", leftpad, leftpad, "", lines[i]);
if(cols >= 0 && (unsigned)cols < dimx){
ncplane_printf(n, "%*.*s", dimx - cols, dimx - cols, "");
}
free(lines[i]);
}
free(lines);
ncplane_set_fg_default(n);
ncplane_set_styles(n, NCSTYLE_BOLD | NCSTYLE_ITALIC);
if(notcurses_canopen_images(nc)){
ncplane_putstr_aligned(n, -1, NCALIGN_CENTER, "(no bitmap is known for your distro)\n");
}else{
ncplane_putstr_aligned(n, -1, NCALIGN_CENTER, "(notcurses was compiled without image support)\n");
}
ncplane_off_styles(n, NCSTYLE_BOLD | NCSTYLE_ITALIC);
return 0;
}
static void*
display_thread(void* vmarshal){
struct marshal* m = vmarshal;
drawpalette(m->nc);
notcurses_render(m->nc);
ncplane_set_bg_default(notcurses_stdplane(m->nc));
ncplane_set_fg_default(notcurses_stdplane(m->nc));
// we've just rendered, so any necessary scrolling has been performed. draw
// our image wherever the palette ended, and then scroll as necessary to
// make that new plane visible.
if(notcurses_canopen_images(m->nc)){
struct ncvisual* ncv = NULL;
if(m->logo){
ncv = ncvisual_from_file(m->logo);
}else if(m->dinfo && m->dinfo->logofile){
ncv = ncvisual_from_file(m->dinfo->logofile);
}
if(ncv){
unsigned y;
ncplane_cursor_yx(notcurses_stdplane_const(m->nc), &y, NULL);
bool pixeling = false;
if(notcurses_check_pixel_support(m->nc) >= 1){
pixeling = true;
}
struct ncvisual_options vopts = {
.n = notcurses_stdplane(m->nc),
.y = y,
.x = NCALIGN_CENTER,
.blitter = pixeling ? NCBLIT_PIXEL : NCBLIT_3x2,
.scaling = pixeling ? NCSCALE_NONE : NCSCALE_SCALE_HIRES,
.flags = NCVISUAL_OPTION_HORALIGNED | NCVISUAL_OPTION_CHILDPLANE,
};
struct ncplane* iplane = ncvisual_blit(m->nc, ncv, &vopts);
ncvisual_destroy(ncv);
if(iplane){
ncplane_scrollup_child(notcurses_stdplane(m->nc), iplane);
newline_past(notcurses_stdplane(m->nc), iplane);
notcurses_render(m->nc);
m->nextline = ncplane_y(iplane) + ncplane_dim_y(iplane);
return NULL;
}
}
}
if(m->neologo){
if(neologo_present(m->nc, m->neologo) == 0){
unsigned nl;
ncplane_cursor_yx(notcurses_stdplane(m->nc), &nl, NULL);
m->nextline = nl;
return NULL;
}
}
unsigned nl;
ncplane_cursor_yx(notcurses_stdplane(m->nc), &nl, NULL);
m->nextline = nl;
return NULL;
}
static int
ncneofetch(struct notcurses* nc){
fetched_info fi = {0};
ncneo_kernel_e kern = get_kernel(&fi);
switch(kern){
case NCNEO_LINUX:
fi.distro = linux_ncneofetch(&fi);
break;
case NCNEO_FREEBSD:
fi.distro = freebsd_ncneofetch(&fi);
break;
case NCNEO_DRAGONFLY:
fi.distro = dragonfly_ncneofetch(&fi);
break;
case NCNEO_XNU:
fi.distro = xnu_ncneofetch(&fi);
break;
case NCNEO_WINDOWS:
fi.distro = windows_ncneofetch(&fi);
break;
case NCNEO_UNKNOWN:
break;
}
// go ahead and spin the image load + render into its own thread while the
// rest of the fetching continues. cuts total runtime.
struct marshal display_marshal = {
.nc = nc,
.dinfo = fi.distro,
.logo = fi.logo,
.neologo = fi.neologo,
.nextline = -1,
};
pthread_t tid;
const bool launched = !pthread_create(&tid, NULL, display_thread, &display_marshal);
fi.hostname = notcurses_hostname();
fi.username = notcurses_accountname();
fetch_env_vars(nc, &fi);
if(kern == NCNEO_LINUX){
fetch_cpu_info(&fi);
}else if(kern == NCNEO_WINDOWS){
fetch_windows_cpuinfo(&fi);
}else{
fetch_bsd_cpuinfo(&fi);
}
if(launched){
pthread_join(tid, NULL);
}
assert(display_marshal.nextline >= 0);
if(infoplane(nc, &fi, display_marshal.nextline)){
free_fetched_info(&fi);
return -1;
}
free_fetched_info(&fi);
return notcurses_stop(nc);
}
static void
usage(const char* arg0, FILE* fp){
fprintf(fp, "usage: %s [ -v ]\n", arg0);
if(fp == stderr){
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
int main(int argc, char** argv){
struct notcurses_options opts = {
.flags = NCOPTION_SUPPRESS_BANNERS
| NCOPTION_NO_ALTERNATE_SCREEN
| NCOPTION_NO_CLEAR_BITMAPS
| NCOPTION_PRESERVE_CURSOR
| NCOPTION_DRAIN_INPUT,
};
if(argc > 2){
usage(argv[0], stderr);
}else if(argc == 2){
if(strcmp(argv[1], "-v") == 0){
opts.loglevel = NCLOGLEVEL_TRACE;
}else{
usage(argv[0], stderr);
}
}
struct notcurses* nc = notcurses_init(&opts, NULL);
if(nc == NULL){
return EXIT_FAILURE;
}
struct ncplane* stdn = notcurses_stdplane(nc);
ncplane_set_scrolling(stdn, true);
int r = ncneofetch(nc);
return r ? EXIT_FAILURE : EXIT_SUCCESS;
}