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.
606 lines
20 KiB
C
606 lines
20 KiB
C
#include <time.h>
|
|
#include <wchar.h>
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
#include <string.h>
|
|
#include <locale.h>
|
|
#include <unistd.h>
|
|
#include <getopt.h>
|
|
#include <stdlib.h>
|
|
#include <inttypes.h>
|
|
#include <stdatomic.h>
|
|
#include <notcurses/direct.h>
|
|
#include "demo.h"
|
|
|
|
// (non-)ansi terminal definition-4-life
|
|
static const int MIN_SUPPORTED_ROWS = 24;
|
|
static const int MIN_SUPPORTED_COLS = 76; // allow a bit of margin, sigh
|
|
|
|
static char *datadir;
|
|
static int democount;
|
|
static demoresult* results;
|
|
|
|
static const char DEFAULT_DEMO[] = "ixetunchmdbkywjgarvlsfqzo";
|
|
|
|
atomic_bool interrupted = ATOMIC_VAR_INIT(false);
|
|
// checked following demos, whether aborted, failed, or otherwise
|
|
static atomic_bool restart_demos = ATOMIC_VAR_INIT(false);
|
|
|
|
void interrupt_demo(void){
|
|
atomic_store(&interrupted, true);
|
|
}
|
|
|
|
void interrupt_and_restart_demos(void){
|
|
atomic_store(&restart_demos, true);
|
|
atomic_store(&interrupted, true);
|
|
}
|
|
|
|
const demoresult* demoresult_lookup(int idx){
|
|
if(idx < 0 || idx >= democount){
|
|
return NULL;
|
|
}
|
|
return &results[idx];
|
|
}
|
|
|
|
char* find_data(const char* datum){
|
|
return notcurses_data_path(datadir, datum);
|
|
}
|
|
|
|
float delaymultiplier = 1;
|
|
|
|
// scaled in getopt() by delaymultiplier
|
|
struct timespec demodelay = {
|
|
.tv_sec = 1,
|
|
.tv_nsec = 0,
|
|
};
|
|
|
|
// the "jungle" demo has non-free material embedded into it, and is thus
|
|
// entirely absent (can't just be disabled). supply a stub here.
|
|
#ifdef DFSG_BUILD
|
|
int jungle_demo(struct notcurses* nc, uint64_t startns){
|
|
(void)nc;
|
|
(void)startns;
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
// Demos can be disabled either due to a DFSG build (any non-free material must
|
|
// be disabled) or a non-multimedia build (all images/videos must be disabled).
|
|
static struct {
|
|
const char* name;
|
|
int (*fxn)(struct notcurses*, uint64_t startns);
|
|
bool dfsg_disabled; // disabled for DFSG builds
|
|
} demos[26] = {
|
|
{ "animate", animate_demo, false, },
|
|
{ "box", box_demo, false, },
|
|
{ "chunli", chunli_demo, true, },
|
|
{ "dragon", dragon_demo, false, },
|
|
{ "eagle", eagle_demo, true, },
|
|
{ "fission", fission_demo, false, },
|
|
{ "grid", grid_demo, false, },
|
|
{ "highcon", highcon_demo, false, },
|
|
{ "intro", intro_demo, false, },
|
|
{ "jungle", jungle_demo, true, },
|
|
{ "keller", keller_demo, true, },
|
|
{ "luigi", luigi_demo, true, },
|
|
{ "mojibake", mojibake_demo, false, },
|
|
{ "normal", normal_demo, false, },
|
|
{ "outro", outro_demo, false, },
|
|
{ NULL, NULL, false, }, // it's a secret to everyone
|
|
{ "qrcode", qrcode_demo, false, }, // is blank without USE_QRCODEGEN
|
|
{ "reel", reel_demo, false, },
|
|
{ "sliders", sliders_demo, false, },
|
|
{ "trans", trans_demo, false, },
|
|
{ "uniblock", uniblock_demo, false, },
|
|
{ "view", view_demo, true, },
|
|
{ "whiteout", whiteout_demo, false, },
|
|
{ "xray", xray_demo, false, },
|
|
{ "yield", yield_demo, false, },
|
|
{ "zoo", zoo_demo, false, },
|
|
};
|
|
|
|
static void
|
|
usage_option(FILE* out, struct ncdirect* n, const char* op){
|
|
if(n) ncdirect_set_fg_rgb8(n, 0x80, 0x80, 0x80);
|
|
fprintf(out, " [ ");
|
|
if(n) ncdirect_set_fg_rgb8(n, 0xff, 0xff, 0x80);
|
|
fprintf(out, "%s", op);
|
|
if(n) ncdirect_set_fg_rgb8(n, 0x80, 0x80, 0x80);
|
|
fprintf(out, " ] ");
|
|
if(n) ncdirect_set_fg_rgb8(n, 0xff, 0xff, 0xff);
|
|
}
|
|
|
|
static void
|
|
usage_expo(FILE* out, struct ncdirect* n, const char* op, const char* expo){
|
|
if(n) ncdirect_set_fg_rgb8(n, 0xff, 0xff, 0x80);
|
|
fprintf(out, " %s: ", op);
|
|
if(n) ncdirect_set_fg_rgb8(n, 0xff, 0xff, 0xff);
|
|
fprintf(out, "%s\n", expo);
|
|
}
|
|
|
|
static void
|
|
usage(const char* exe, int status){
|
|
FILE* out = status == EXIT_SUCCESS ? stdout : stderr;
|
|
struct ncdirect* n = ncdirect_init(NULL, out, 0);
|
|
if(n) ncdirect_set_fg_rgb8(n, 0x00, 0xc0, 0xc0);
|
|
fprintf(out, "usage: ");
|
|
if(n) ncdirect_set_fg_rgb8(n, 0x80, 0xff, 0x80);
|
|
fprintf(out, "%s ", exe);
|
|
const char* options[] = { "-hVkc", "-m margins", "-p path", "-l loglevel",
|
|
"-d mult", "-J jsonfile", "demospec",
|
|
NULL };
|
|
for(const char** op = options ; *op ; ++op){
|
|
usage_option(out, n, *op);
|
|
}
|
|
fprintf(out, "\n\n");
|
|
if(n) ncdirect_set_fg_rgb8(n, 0xff, 0xff, 0xff);
|
|
const char* optexpo[] = {
|
|
"-h|--help", "this message",
|
|
"-V|--version", "print program name and version",
|
|
"-k", "keep screen; do not switch to alternate",
|
|
"-d", "delay multiplier (non-negative float)",
|
|
"-J", "emit JSON summary to file",
|
|
"-c", "constant PRNG seed, useful for benchmarking",
|
|
"-m", "margin, or 4 comma-separated margins",
|
|
NULL
|
|
};
|
|
for(const char** op = optexpo ; *op ; op += 2){
|
|
const char* expo = op[1];
|
|
usage_expo(out, n, *op, expo);
|
|
}
|
|
if(n) ncdirect_set_fg_rgb8(n, 0xff, 0xff, 0x80);
|
|
fprintf(out, " -l:");
|
|
if(n) ncdirect_set_fg_rgb8(n, 0xff, 0xff, 0xff);
|
|
fprintf(out, " logging level (%d: silent..%d: manic)\n", NCLOGLEVEL_SILENT, NCLOGLEVEL_TRACE);
|
|
if(n) ncdirect_set_fg_rgb8(n, 0xff, 0xff, 0x80);
|
|
fprintf(out, " -p:");
|
|
if(n) ncdirect_set_fg_rgb8(n, 0xff, 0xff, 0xff);
|
|
fprintf(out, " data file path (default: %s)\n", NOTCURSES_SHARE);
|
|
fprintf(out, "\nspecify demos via their first letter. repetitions are allowed.\n");
|
|
if(n) ncdirect_set_fg_rgb8(n, 0x80, 0xff, 0x80);
|
|
fprintf(out, " default spec: %s\n\n", DEFAULT_DEMO);
|
|
if(n) ncdirect_set_fg_rgb8(n, 0xff, 0xff, 0xff);
|
|
int printed = 0;
|
|
for(size_t i = 0 ; i < sizeof(demos) / sizeof(*demos) ; ++i){
|
|
if(demos[i].name){
|
|
if(printed % 5 == 0){
|
|
fprintf(out, " ");
|
|
}
|
|
// U+24D0: CIRCLED LATIN SMALL LETTER A
|
|
if(n) ncdirect_set_fg_rgb8(n, 0xff, 0xff, 0x80);
|
|
fprintf(out, "%lc ", *demos[i].name - 'a' + 0x24d0);
|
|
if(n) ncdirect_set_fg_rgb8(n, 0xff, 0xff, 0xff);
|
|
fprintf(out, "%-*.*s", 8, 8, demos[i].name + 1);
|
|
if(++printed % 5 == 0){
|
|
fprintf(out, "\n");
|
|
}
|
|
}
|
|
}
|
|
if(printed % 5){
|
|
fprintf(out, "\n");
|
|
}
|
|
ncdirect_stop(n);
|
|
exit(status);
|
|
}
|
|
|
|
static int
|
|
ext_demos(struct notcurses* nc, const char* spec){
|
|
int ret = 0;
|
|
results = malloc(sizeof(*results) * strlen(spec));
|
|
if(results == NULL){
|
|
return -1;
|
|
}
|
|
memset(results, 0, sizeof(*results) * strlen(spec));
|
|
democount = strlen(spec);
|
|
uint64_t prevns = clock_getns(CLOCK_MONOTONIC);
|
|
for(size_t i = 0 ; i < strlen(spec) ; ++i){
|
|
results[i].selector = spec[i];
|
|
}
|
|
struct ncplane* n = notcurses_stdplane(nc);
|
|
for(size_t i = 0 ; i < strlen(spec) ; ++i){
|
|
if(interrupted){
|
|
break;
|
|
}
|
|
int idx = spec[i] - 'a';
|
|
#ifdef DFSG_BUILD
|
|
if(demos[idx].dfsg_disabled){
|
|
continue;
|
|
}
|
|
#endif
|
|
// set the standard plane's base character to an opaque black, but don't
|
|
// erase the plane (we let one demo bleed through to the next, an effect
|
|
// we exploit in a few transitions).
|
|
uint64_t stdc = NCCHANNELS_INITIALIZER(0, 0, 0, 0, 0, 0);
|
|
ncplane_set_base(n, "", 0, stdc);
|
|
|
|
hud_schedule(demos[idx].name, prevns);
|
|
ret = demos[idx].fxn(nc, prevns);
|
|
notcurses_stats_reset(nc, &results[i].stats);
|
|
uint64_t nowns = clock_getns(CLOCK_MONOTONIC);
|
|
results[i].timens = nowns - prevns;
|
|
prevns = nowns;
|
|
results[i].result = ret;
|
|
hud_completion_notify(&results[i]);
|
|
if(ret){
|
|
break;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// returns the demos to be run as a string. on error, returns NULL. on no
|
|
// specification, also returns NULL, heh. determine this by argv[optind];
|
|
// if it's NULL, there were valid options, but no spec.
|
|
static const char*
|
|
handle_opts(int argc, char** argv, notcurses_options* opts, FILE** json_output){
|
|
bool constant_seed = false;
|
|
*json_output = NULL;
|
|
int c;
|
|
const struct option longopts[] = {
|
|
{ .name = "help", .has_arg = 0, .flag = NULL, .val = 'h', },
|
|
{ .name = "version", .has_arg = 0, .flag = NULL, .val = 'V', },
|
|
{ .name = NULL, .has_arg = 0, .flag = NULL, .val = 0, },
|
|
};
|
|
int lidx;
|
|
while((c = getopt_long(argc, argv, "VhckJ:l:d:p:m:", longopts, &lidx)) != EOF){
|
|
switch(c){
|
|
case 'h':
|
|
usage(*argv, EXIT_SUCCESS);
|
|
break;
|
|
case 'l':{
|
|
int loglevel;
|
|
if(sscanf(optarg, "%d", &loglevel) != 1){
|
|
fprintf(stderr, "Couldn't get an int from %s\n", optarg);
|
|
usage(*argv, EXIT_FAILURE);
|
|
}
|
|
opts->loglevel = loglevel;
|
|
if(opts->loglevel < NCLOGLEVEL_SILENT || opts->loglevel > NCLOGLEVEL_TRACE){
|
|
fprintf(stderr, "Invalid log level: %d\n", opts->loglevel);
|
|
usage(*argv, EXIT_FAILURE);
|
|
}
|
|
break;
|
|
}case 'm':{
|
|
if(opts->margin_t || opts->margin_r || opts->margin_b || opts->margin_l){
|
|
fprintf(stderr, "Provided margins twice!\n");
|
|
usage(*argv, EXIT_FAILURE);
|
|
}
|
|
if(notcurses_lex_margins(optarg, opts)){
|
|
usage(*argv, EXIT_FAILURE);
|
|
}
|
|
break;
|
|
}case 'V':
|
|
printf("notcurses-demo version %s\n", notcurses_version());
|
|
exit(EXIT_SUCCESS);
|
|
case 'J':
|
|
if(*json_output){
|
|
fprintf(stderr, "Supplied -J twice: %s\n", optarg);
|
|
usage(*argv, EXIT_FAILURE);
|
|
}
|
|
if((*json_output = fopen(optarg, "wb")) == NULL){
|
|
fprintf(stderr, "Error opening %s for JSON (%s?)\n", optarg, strerror(errno));
|
|
usage(*argv, EXIT_FAILURE);
|
|
}
|
|
break;
|
|
case 'c':
|
|
constant_seed = true;
|
|
break;
|
|
case 'k':
|
|
opts->flags |= NCOPTION_NO_ALTERNATE_SCREEN;
|
|
break;
|
|
case 'p':
|
|
datadir = optarg;
|
|
break;
|
|
case 'd':{
|
|
float f;
|
|
if(sscanf(optarg, "%f", &f) != 1){
|
|
fprintf(stderr, "Couldn't get a float from %s\n", optarg);
|
|
usage(*argv, EXIT_FAILURE);
|
|
}
|
|
if(f < 0){
|
|
fprintf(stderr, "Invalid multiplier: %f\n", f);
|
|
usage(*argv, EXIT_FAILURE);
|
|
}
|
|
delaymultiplier = f;
|
|
uint64_t ns = f * NANOSECS_IN_SEC;
|
|
demodelay.tv_sec = ns / NANOSECS_IN_SEC;
|
|
demodelay.tv_nsec = ns % NANOSECS_IN_SEC;
|
|
break;
|
|
}default:
|
|
usage(*argv, EXIT_FAILURE);
|
|
}
|
|
}
|
|
if(!constant_seed){
|
|
srand(time(NULL)); // a classic blunder lol
|
|
}
|
|
if(optind < argc - 1){
|
|
fprintf(stderr, "Extra argument: %s\n", argv[optind + 1]);
|
|
usage(*argv, EXIT_FAILURE);
|
|
}
|
|
const char* spec = argv[optind];
|
|
return spec;
|
|
}
|
|
|
|
static int
|
|
table_segment_color(struct ncplane* n, const char* str, const char* delim,
|
|
const char* ascdelim, unsigned color){
|
|
ncplane_set_fg_rgb(n, color);
|
|
if(ncplane_putstr(n, str) < 0){
|
|
return -1;
|
|
}
|
|
ncplane_set_fg_rgb8(n, 178, 102, 255);
|
|
if(notcurses_canutf8(ncplane_notcurses(n))){
|
|
if(ncplane_putstr(n, delim) < 0){
|
|
return -1;
|
|
}
|
|
}else{
|
|
if(ncplane_putstr(n, ascdelim) < 0){
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
table_segment(struct ncplane* n, const char* str, const char* delim,
|
|
const char* ascdelim){
|
|
return table_segment_color(n, str, delim, ascdelim, 0xffffff);
|
|
}
|
|
|
|
static int
|
|
table_printf(struct ncplane* n, const char* delim, const char* fmt, ...){
|
|
ncplane_set_fg_rgb8(n, 0xD4, 0xAF, 0x37);
|
|
va_list va;
|
|
va_start(va, fmt);
|
|
int r = ncplane_vprintf(n, fmt, va);
|
|
va_end(va);
|
|
ncplane_set_fg_rgb8(n, 178, 102, 255);
|
|
ncplane_putstr(n, delim);
|
|
return r;
|
|
}
|
|
|
|
static int
|
|
summary_json(FILE* f, const char* spec, int rows, int cols){
|
|
int ret = 0;
|
|
ret |= (fprintf(f, "{\"notcurses-demo\":{\"spec\":\"%s\",\"TERM\":\"%s\",\"rows\":\"%d\",\"cols\":\"%d\",\"runs\":{",
|
|
spec, getenv("TERM"), rows, cols) < 0);
|
|
for(size_t i = 0 ; i < strlen(spec) ; ++i){
|
|
if(results[i].result || !results[i].stats.renders){
|
|
continue;
|
|
}
|
|
ret |= (fprintf(f, "\"%s\":{\"bytes\":\"%"PRIu64"\",\"frames\":\"%"PRIu64"\",\"ns\":\"%"PRIu64"\"}%s",
|
|
demos[results[i].selector - 'a'].name, results[i].stats.raster_bytes,
|
|
results[i].stats.renders, results[i].timens, i < strlen(spec) - 1 ? "," : "") < 0);
|
|
}
|
|
ret |= (fprintf(f, "}}}\n") < 0);
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
summary_table(struct notcurses* nc, const char* spec, bool canimage, bool canvideo){
|
|
notcurses_leave_alternate_screen(nc);
|
|
struct ncplane* n = notcurses_stdplane(nc);
|
|
ncplane_set_bg_default(n);
|
|
ncplane_set_scrolling(n, true);
|
|
bool failed = false;
|
|
uint64_t totalbytes = 0;
|
|
long unsigned totalframes = 0;
|
|
ncplane_putchar(n, '\n');
|
|
// FIXME this shouldn't be necessary, but without it, late in 2.4.x we
|
|
// stopped printing the table header. see #2389.
|
|
notcurses_render(nc);
|
|
const char* sep;
|
|
if(notcurses_canutf8(ncplane_notcurses(n))){
|
|
sep = u8"│";
|
|
}else{
|
|
sep = "|";
|
|
}
|
|
table_segment(n, " runtime", "│", "|");
|
|
table_segment(n, " frames", "│", "|");
|
|
table_segment(n, "output(B)", "│", "|");
|
|
table_segment(n, " FPS", "│", "|");
|
|
table_segment(n, "%r", "│", "|");
|
|
table_segment(n, "%a", "│", "|");
|
|
table_segment(n, "%w", "│", "|");
|
|
table_segment(n, "TheoFPS", "║\n══╤════════╤════════╪═══════╪═════════╪═══════╪══╪══╪══╪═══════╣\n",
|
|
"|\n--+--------+--------+-------+---------+-------+--+--+--+-------|\n");
|
|
char timebuf[NCPREFIXSTRLEN + 1];
|
|
char tfpsbuf[NCPREFIXSTRLEN + 1];
|
|
char totalbuf[NCBPREFIXSTRLEN + 1];
|
|
uint64_t nsdelta = 0;
|
|
for(size_t i = 0 ; i < strlen(spec) ; ++i){
|
|
nsdelta += results[i].timens;
|
|
ncqprefix(results[i].timens, NANOSECS_IN_SEC, timebuf, 0);
|
|
ncbprefix(results[i].stats.raster_bytes, 1, totalbuf, 0);
|
|
uint64_t divisor = results[i].stats.render_ns + results[i].stats.writeout_ns + results[i].stats.raster_ns;
|
|
if(divisor){
|
|
ncqprefix((uintmax_t)results[i].stats.writeouts * NANOSECS_IN_SEC * 1000 / divisor,
|
|
1000, tfpsbuf, 0);
|
|
}else{
|
|
ncqprefix(0, NANOSECS_IN_SEC, tfpsbuf, 0);
|
|
}
|
|
uint32_t rescolor;
|
|
if(results[i].result < 0){
|
|
rescolor = 0xff303c;
|
|
}else if(results[i].result > 0){
|
|
rescolor = 0xffaa22;
|
|
}else if(!results[i].stats.renders){
|
|
rescolor = 0xbbbbbb;
|
|
}else{
|
|
rescolor = 0x32CD32;
|
|
}
|
|
ncplane_set_fg_rgb(n, rescolor);
|
|
ncplane_printf(n, "%2llu", (unsigned long long)(i + 1)); // windows has %zu problems
|
|
ncplane_set_fg_rgb8(n, 178, 102, 255);
|
|
ncplane_putegc(n, sep, NULL);
|
|
ncplane_set_fg_rgb(n, rescolor);
|
|
ncplane_printf(n, "%8s", demos[results[i].selector - 'a'].name);
|
|
ncplane_set_fg_rgb8(n, 178, 102, 255);
|
|
ncplane_printf(n, "%s%*ss%s%7" PRIu64 "%s%*s%s%7.1f%s%2" PRId64 "%s%2" PRId64 "%s%2" PRId64 "%s%*s%s",
|
|
sep, NCPREFIXFMT(timebuf), sep,
|
|
results[i].stats.renders, sep,
|
|
NCBPREFIXFMT(totalbuf), sep,
|
|
results[i].timens ?
|
|
results[i].stats.renders / ((double)results[i].timens / NANOSECS_IN_SEC) : 0.0, sep,
|
|
(results[i].timens ?
|
|
results[i].stats.render_ns * 100 / results[i].timens : 0), sep,
|
|
(results[i].timens ?
|
|
results[i].stats.raster_ns * 100 / results[i].timens : 0), sep,
|
|
(results[i].timens ?
|
|
results[i].stats.writeout_ns * 100 / results[i].timens : 0), sep,
|
|
NCPREFIXFMT(tfpsbuf),
|
|
notcurses_canutf8(ncplane_notcurses(n)) ? "║" : "|");
|
|
ncplane_set_fg_rgb(n, rescolor);
|
|
ncplane_printf(n, "%s\n", results[i].result < 0 ? "FAILED" :
|
|
results[i].result > 0 ? "ABORTED" :
|
|
!results[i].stats.renders ? "SKIPPED" : "");
|
|
if(results[i].result < 0){
|
|
failed = true;
|
|
}
|
|
totalframes += results[i].stats.renders;
|
|
totalbytes += results[i].stats.raster_bytes;
|
|
}
|
|
ncqprefix(nsdelta, NANOSECS_IN_SEC, timebuf, 0);
|
|
ncbprefix(totalbytes, 1, totalbuf, 0);
|
|
table_segment(n, "", "══╧════════╧════════╪═══════╪═════════╪═══════╧══╧══╧══╧═══════╝\n",
|
|
"--+--------+--------+-------+---------+-------+--+--+--+-------+\n");
|
|
ncplane_putstr(n, " ");
|
|
table_printf(n, sep, "%*ss", NCPREFIXFMT(timebuf));
|
|
table_printf(n, sep, "%7lu", totalframes);
|
|
table_printf(n, sep, "%*s", NCBPREFIXFMT(totalbuf));
|
|
//table_printf(nc, sep, "%7.1f", nsdelta ? totalframes / ((double)nsdelta / NANOSECS_IN_SEC) : 0);
|
|
ncplane_putchar(n, '\n');
|
|
ncplane_set_fg_rgb8(n, 0xfe, 0x20, 0x76); // PANTONE Strong Red C + 3x0x20
|
|
#ifdef DFSG_BUILD
|
|
ncplane_putstr(n, "\nDFSG version. Some demos are unavailable.\n");
|
|
#endif
|
|
if(!canimage){
|
|
ncplane_putstr(n, "\nNo multimedia support. Some demos are unavailable.\n");
|
|
}else if(!canvideo){
|
|
ncplane_putstr(n, "\nNo video support. Some demos are unavailable.\n");
|
|
}
|
|
ncplane_set_fg_rgb8(n, 0xff, 0xb0, 0xb0);
|
|
if(failed){
|
|
ncplane_printf(n, "\nError running demo.\nIs \"%s\" the correct data path? Supply it with -p.\n", datadir);
|
|
}
|
|
return failed;
|
|
}
|
|
|
|
static int
|
|
scrub_stdplane(struct notcurses* nc){
|
|
struct ncplane* n = notcurses_stdplane(nc);
|
|
uint64_t channels = 0;
|
|
ncchannels_set_fg_rgb(&channels, 0); // explicit black + opaque
|
|
ncchannels_set_bg_rgb(&channels, 0);
|
|
if(ncplane_set_base(n, "", 0, channels)){
|
|
return -1;
|
|
}
|
|
ncplane_erase(n);
|
|
return 0;
|
|
}
|
|
|
|
int main(int argc, char** argv){
|
|
#ifndef __MINGW32__
|
|
sigset_t sigmask;
|
|
// ensure SIGWINCH is delivered only to a thread doing input
|
|
sigemptyset(&sigmask);
|
|
sigaddset(&sigmask, SIGWINCH);
|
|
pthread_sigmask(SIG_BLOCK, &sigmask, NULL);
|
|
#endif
|
|
const char* spec;
|
|
FILE* json = NULL; // emit JSON summary to this file? (-J)
|
|
notcurses_options nopts = {0};
|
|
if((spec = handle_opts(argc, argv, &nopts, &json)) == NULL){
|
|
if(argv[optind] != NULL){
|
|
usage(*argv, EXIT_FAILURE);
|
|
}
|
|
spec = DEFAULT_DEMO;
|
|
}
|
|
for(size_t i = 0 ; i < strlen(spec) ; ++i){
|
|
int nameidx = spec[i] - 'a';
|
|
if(nameidx < 0 || nameidx > 25 || !demos[nameidx].name){
|
|
fprintf(stderr, "Invalid demo specification: %c\n", spec[i]);
|
|
usage(*argv, EXIT_FAILURE);
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
struct timespec starttime;
|
|
clock_gettime(CLOCK_MONOTONIC, &starttime);
|
|
struct notcurses* nc;
|
|
if((nc = notcurses_init(&nopts, NULL)) == NULL){
|
|
return EXIT_FAILURE;
|
|
}
|
|
notcurses_mice_enable(nc, NCMICE_BUTTON_EVENT | NCMICE_DRAG_EVENT);
|
|
const bool canimage = notcurses_canopen_images(nc);
|
|
const bool canvideo = notcurses_canopen_videos(nc);
|
|
unsigned dimx, dimy;
|
|
ncplane_dim_yx(notcurses_stdplane(nc), &dimy, &dimx);
|
|
if(input_dispatcher(nc)){
|
|
goto err;
|
|
}
|
|
if(dimy < MIN_SUPPORTED_ROWS || dimx < MIN_SUPPORTED_COLS){
|
|
goto err;
|
|
}
|
|
if((nopts.flags & NCOPTION_NO_ALTERNATE_SCREEN)){ // no one cares. 1s max.
|
|
if(demodelay.tv_sec >= 1){
|
|
sleep(1);
|
|
}else{
|
|
nanosleep(&demodelay, NULL);
|
|
}
|
|
}
|
|
struct ncmenu* menu = NULL;
|
|
do{
|
|
restart_demos = false;
|
|
interrupted = false;
|
|
notcurses_drop_planes(nc);
|
|
if(scrub_stdplane(nc)){
|
|
goto err;
|
|
}
|
|
if(!hud_create(nc)){
|
|
goto err;
|
|
}
|
|
if(fpsgraph_init(nc)){
|
|
goto err;
|
|
}
|
|
if((menu = menu_create(nc)) == NULL){
|
|
goto err;
|
|
}
|
|
if(notcurses_render(nc)){
|
|
goto err;
|
|
}
|
|
notcurses_stats_reset(nc, NULL);
|
|
if(ext_demos(nc, spec)){
|
|
goto err;
|
|
}
|
|
if(hud_destroy()){ // destroy here since notcurses_drop_planes will kill it
|
|
goto err;
|
|
}
|
|
if(fpsgraph_stop()){
|
|
goto err;
|
|
}
|
|
about_destroy(nc); // also kills debug window
|
|
}while(restart_demos);
|
|
ncmenu_destroy(menu);
|
|
stop_input();
|
|
notcurses_render(nc); // rid ourselves of any remaining demo output
|
|
int r = summary_table(nc, spec, canimage, canvideo);
|
|
notcurses_render(nc); // render our summary table
|
|
free(results);
|
|
if(notcurses_stop(nc)){
|
|
return EXIT_FAILURE;
|
|
}
|
|
if(json && summary_json(json, spec, dimy, dimx)){
|
|
return EXIT_FAILURE;
|
|
}
|
|
return r ? EXIT_FAILURE : EXIT_SUCCESS;
|
|
|
|
err:
|
|
stop_input();
|
|
notcurses_stop(nc);
|
|
if(dimy < MIN_SUPPORTED_ROWS || dimx < MIN_SUPPORTED_COLS){
|
|
fprintf(stderr, "At least a %dx%d terminal is required (current: %dx%d)\n",
|
|
MIN_SUPPORTED_ROWS, MIN_SUPPORTED_COLS, dimy, dimx);
|
|
}
|
|
return EXIT_FAILURE;
|
|
}
|